1. 程式人生 > 程式設計 >Java記憶體區域與記憶體溢位異常

Java記憶體區域與記憶體溢位異常

一、概述

Java開發者在開發過程中基本不用關心記憶體的管理,因為虛擬機器器自動記憶體管理機制已幫你打理好一切。開發者只需要將重點放在程式碼實現業務邏輯上,基本可以將記憶體的管理交給虛擬機器器去完成。當然,也正是虛擬機器器的管理,如果我們不瞭解Java虛擬機器器如何管理、使用記憶體,一旦出現記憶體方面的問題,那排查錯誤也就成為一項艱難的工作。真的是讓人歡喜讓人憂啊!

二、執行時資料區域(Run-Time Data Areas

Java虛擬機器器定義了在程式執行期間使用的各種執行時資料區域。其中一些資料區域是在Java虛擬機器器啟動時建立的,僅在Java虛擬機器器退出時銷燬;其他資料區域是執行緒資料區域,執行緒資料區域隨著執行緒的啟動而建立,隨著執行緒的結束而銷燬。
根據最新的
Java ®虛擬機器器規範(Java SE 12 Edition
內容看,Java虛擬機器器執行時資料區域分為以下幾個區域:

程式計數器(The Program Counter Register)

Java虛擬機器器可以同時支援許多執行緒的執行。每個Java虛擬機器器執行緒都有自己的 pc暫存器(程式計數器)。在任何時候,每個Java虛擬機器器執行緒都在執行單個方法的程式碼,即該執行緒的當前方法。如果不是native方法 ,則pc暫存器記錄當前正在執行的Java虛擬機器器指令的地址。如果執行緒當前正在執行native方法,則Java虛擬機器器pc 暫存器的值未定義。Java虛擬機器器程式計數器功能足夠強大,可以在特定平臺上儲存返回地址或指向本機指標。
在虛擬機器器的概念模型裡(不同的虛擬機器器實現不同),位元組碼直譯器就是通過改變計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復(多執行緒執行緒輪流切換)等都是由程式計數器來完成。

Java虛擬機器器棧(Java Virtual Machine Stacks)

每個Java虛擬機器器執行緒都有一個私有Java虛擬機器器棧,與執行緒同時建立。Java虛擬機器器棧類似於傳統語言的堆疊,例如C:它儲存區域性變數和部分結果,並在方法呼叫和返回中起作用。由於除了推送和彈出幀之外,永遠不會直接操作Java虛擬機器器棧幀,因此可以對堆進行堆分配。Java虛擬機器器棧的記憶體不需要是連續的。

幀(Frame)用於儲存資料和部分結果,以及執行動態連結,對方法和排程異常返回值。 虛擬機器器棧是描述的是Java方法執行的記憶體模型。每次呼叫方法時都會建立一個新幀。當方法呼叫完成時,幀將被銷燬,無論該完成是正常還是突然中斷(它會丟擲未捕獲的異常)。幀是從建立幀的執行緒的Java

虛擬機器器棧中分配的。每個幀都有自己的區域性變數陣列,它自己的運算元堆疊,以及對當前方法類的執行時常量池的引用。

以下異常條件與Java虛擬機器器棧相關聯:

  • 如果執行緒中的計算需要比允許的更大的Java虛擬機器器堆疊,則Java虛擬機器器會丟擲一個StackOverflowError
  • 如果可以動態擴充套件Java虛擬機器器堆疊,並且嘗試進行擴充套件但可以使記憶體不足以實現擴充套件,或者可以使記憶體不足以為新執行緒建立初始Java虛擬機器器堆疊,則Java Virtual機器丟擲一個OutOfMemoryError

堆(Heap)

Java虛擬機器器具有在所有Java虛擬機器器執行緒之間共享的堆。堆是執行時資料區,從中分配所有類例項和陣列的記憶體。 堆是在虛擬機器器啟動時建立的。物件的堆儲存由自動儲存管理系統(稱為垃圾收集器)回收 ; 物件永遠不會被顯式釋放。Java虛擬機器器假設沒有特定型別的自動儲存管理系統,可以根據實現者的系統要求選擇儲存管理技術。堆可以具有固定大小,或者可以根據計算的需要進行擴充套件,並且如果不需要更大的堆,則可以收縮。堆的記憶體不需要是連續的。
Java虛擬機器器實現可以為程式設計師或使用者提供對堆的初始大小的控制,以及如果可以動態擴充套件或收縮堆,則控制最大和最小堆大小。
以下異常情況與堆相關聯:

  • 如果計算需要的堆量超過自動儲存管理系統可用的堆,則Java虛擬機器器會丟擲一個 OutOfMemoryError

方法區(Method Area)

Java虛擬機器器具有在所有Java虛擬機器器執行緒之間共享的方法區域。方法區域類似於傳統語言的編譯程式碼的儲存區域或類似於作業系統程式中的“文字”段。它儲存虛擬機器器載入類結構資訊,例如執行時常量池,欄位和方法資料,以及方法和建構函式的程式碼,包括類和介面初始化以及例項初始化中使用的特殊方法。
方法區域是在虛擬機器器啟動時建立的。雖然方法區域在邏輯上是的一部分,但是簡單的實現可能選擇不垃圾收集或壓縮它。Java ®虛擬機器器規範(Java SE 12 Edition)未規定方法區域的位置或用於管理編譯程式碼的策略。方法區域可以是固定大小的,或者可以根據計算的需要進行擴充套件,並且如果不需要更大的方法區域,則可以縮小方法區域。方法區域的記憶體不需要是連續的。
Java虛擬機器器實現可以提供程式設計師或使用者對方法區域的初始大小的控制,以及在變大小方法區域的情況下,控制最大和最小方法區域大小。
以下異常條件與方法區域相關聯:

  • 如果方法區域中的記憶體無法滿足分配請求,則Java虛擬機器器會丟擲一個OutOfMemoryError

執行時常量池(Run-Time Constant Pool)

執行時常量池是constant pool table在類或介面的Class檔案中的 表示。它包含幾種常量,從編譯時已知的數字文字到必須在執行時解析的方法和欄位引用。執行時常量池提供類似於傳統程式語言的符號表的功能,儘管它包含比典型符號表更寬範圍的資料。
每個執行時常量池都是從Java虛擬機器器的方法區域中分配的。當Java虛擬機器器建立類或介面時,將構造類或介面的執行時常量池。

以下異常條件與類或介面的執行時常量池的構造相關聯:

  • 在建立類或介面時,如果執行時常量池的構造需要的記憶體比Java虛擬機器器的方法區域中可用的記憶體多,則Java虛擬機器器會丟擲一個OutOfMemoryError

本地方法棧(Native Method Stacks)

Java虛擬機器器的實現可以使用常規堆疊(俗稱"C堆疊")來支援native方法(用Java程式語言以外的語言編寫的方法)。本機方法堆疊也可以通過以諸如C語言的Java虛擬機器器的指令集的直譯器的實現來使用。無法載入native方法並且本身不依賴於傳統堆疊的Java虛擬機器器實現不需要提供本機方法堆疊。如果提供,則通常在建立每個執行緒時為每個執行緒分配本機方法堆疊。
Java ®虛擬機器器規範(Java SE 12 Edition允許本機方法堆疊具有固定大小或根據計算的需要動態擴充套件和收縮。如果本機方法堆疊具有固定大小,則可以在建立該堆疊時獨立地選擇每個本機方法堆疊的大小。 Java虛擬機器器實現可以為程式設計師或使用者提供對本機方法堆疊的初始大小的控制,以及在不同大小的本機方法堆疊的情況下,控制最大和最小方法堆疊大小。

以下異常條件與本機方法堆疊相關聯:

  • 如果執行緒中的計算需要比允許的更大的本機方法堆疊,則Java虛擬機器器會丟擲一個StackOverflowError
  • 如果可以動態擴充套件本機方法堆疊並嘗試進行本機方法堆疊擴充套件,但可以使記憶體不足,或者如果沒有足夠的記憶體可用於為新執行緒建立初始本機方法堆疊,則Java虛擬機器器會丟擲OutOfMemoryError

三、HotSpot虛擬機器器物件探祕

物件在虛擬機器器中如何建立?如何儲存?如何訪問適用物件?

物件的建立

物件的建立過程基本流程如上圖。
當然具體的細節比較複雜,這裡沒有展開。因為很小的一個點深入的研究會發現更多特別的東西。

物件的記憶體佈局

物件在記憶體中的儲存佈局可以分為3塊區域:

物件頭

物件頭包括以下兩部分:
第一部分:用於儲存物件自身執行時資料;雜湊碼、GC分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向執行緒ID、偏向時間戳等。 這部分的資料的長度在32位虛擬機器器和64為虛擬機器器中分別為32bit64bit請記住這裡的長度】,官方稱為"Mark Word"Mark Word根據物件的狀態不同,複用自己的儲存空間儲存不同的需要資訊。
第二部分:型別指標;即物件指向它的類元資料的指標,虛擬機器器通過該指標確認時那個類的例項物件。
如果是陣列,物件頭中還需包括記錄陣列長度的資料。

例項資料

例項資料部分是儲存的可用物件的有效資訊,程式中所定義的各種型別的欄位內容。這部分儲存的順序受虛擬機器器分配策略引數和Java原始碼中欄位定義順序的影響。

對齊填充

對齊填充並不是必然會存在的。僅僅是佔位符的作用。因為HostSpot虛擬機器器的自動記憶體管理系統要求物件起始地址必須是8位元組的整數倍,物件頭的長度正好是8的整數倍(1或者2倍)。當物件例項資料部分沒有對齊時,就需要對齊填充的補全對齊。

物件的訪問定位

Java程式需要通過棧上的reference資料(Java虛擬機器器棧中建立的棧幀中的區域性變數陣列中儲存reference資料)操作堆上的具體物件。

主流的訪問物件的方式:

使用控制程式碼訪問

使用控制程式碼訪問則區域性變數陣列中儲存的是穩定的控制程式碼地址,在物件被移動(垃圾回收時移動物件)時只會改變控制程式碼中的例項資料指標。

直接指標訪問

使用直接指標訪問方式則訪問物件的速度更快,節省了指標定位到物件例項資料的時間,由於Java程式中的類例項非常多,而且物件訪問十分頻繁,所以節省這部分時間是十分有必要的。Sun HotSpot採用的就是此種方式。
以上內容來自《深入理解Java虛擬機器器》第2版 周志明(著)學習筆記。由於本人理解水平有限,如有不妥之處,請不吝賜教!