深入理解Java虛擬機器筆記-自動記憶體管理機制

wenchieh發表於2019-02-20

執行時資料區域

Java 虛擬機器在執行 Java 程式的過程中會把所管理的記憶體區域分成如下區域:

深入理解Java虛擬機器筆記-自動記憶體管理機制

程式計數器

一塊較小的記憶體空間,它可以看作是當前執行緒所執行的位元組碼的行號指示器。位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成。

每個執行緒都有一個獨立的程式計數器,每個計數器互不影響,獨立儲存,這類記憶體區域是執行緒私有記憶體。

Java 虛擬機器棧

與程式計數器一樣,Java虛擬機器棧(Java Vir-tual Machine Stacks)也是執行緒私有的,它的生命週期與執行緒相同。虛擬機器棧描述的是Java方法執行的記憶體模型:每個方法在執行的同時都會建立一個棧幀(Stack Frame)用於儲存區域性變數表、運算元棧、動態連結、方法出口等資訊。每一個方法從呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中入棧到出棧的過程。

如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError異常;如果虛擬機器棧可以動態擴充套件(當前大部分的Java虛擬機器都可動態擴充套件,只不過Java虛擬機器規範中也允許固定長度的虛擬機器棧),如果擴充套件時無法申請到足夠的記憶體,就會丟擲OutOfMemoryError異常。

本地方法棧

虛擬機器棧是為 Java 方法服務的,本地方法棧是為 native 方法服務的。

Java 堆

heap 是記憶體管理中最大的一個記憶體區域。

基本上所有物件例項都在堆中分配記憶體。

但是隨著JIT編譯器的發展與逃逸分析技術逐漸成熟,棧上分配、標量替換優化技術將會導致一些微妙的變化發生,所有的物件都分配在堆上也漸漸變得不是那麼“絕對”了。

GC 主要回收的區域就在 heap 上。

如果在堆中沒有記憶體完成例項分配,並且堆也無法再擴充套件時,將會丟擲OutOfMemoryError異常。

方法區

方法區(Method Area)與Java堆一樣,是各個執行緒共享的記憶體區域,它用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。

執行時常量池

Runtime Constants Pool 是方法區的一部分。

Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池(Constant Pool Table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類載入後進入方法區的執行時常量池中存放。

直接記憶體

直接記憶體(Direct Memory)並不是虛擬機器執行時資料區的一部分,也不是Java虛擬機器規範中定義的記憶體區域。但是這部分記憶體也被頻繁地使用,而且也可能導致OutOfMemoryError異常出現。

物件

物件的建立

虛擬機器遇到一個 new 指令,首先去檢查這個指令的引數是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已經被載入、解析過。沒有的話就會先載入、解析。

然後為新生物件分配記憶體。

分配記憶體是隨機分配一款區域,所以 heap 中空閒記憶體和已用記憶體縱橫交錯。這種情況虛擬機器要維護一個空閒列表 (Free List),記錄哪些區域是可以用的,分配時從表中查出一塊足夠大的區域給新生物件。

如果垃圾回收器帶壓縮功能,可以把 heap 壓縮,所有用過的記憶體都放在一邊,空閒的記憶體放在另一邊,中間放著一個指標作為分界點的指示器,那所分配記憶體就僅僅是把那個指標向空閒空間那邊挪動一段與物件大小相等的距離,這種分配方式稱為“指標碰撞”(Bump the Pointer)。

分配時多執行緒會有併發問題,避免併發:

  1. 對每次分配進行同步
  2. 為每個執行緒分配 TLAB(Thread Loal Allocation Buffer) 快取

分配完記憶體後,把記憶體空間初始化零值,保證了物件例項中的欄位不賦值就有初始值。

然後對物件進行必要的設定,物件是哪個類的例項、如何才能找到類的後設資料資訊、物件的雜湊碼、物件的 GC 分代年齡。這些資訊存在物件頭中。

執行 new 指令後會接著執行 init 方法,按程式設計師的意願進行初始化。物件才完全建立完畢。

記憶體佈局

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

  1. 物件頭(Header)
  2. 例項資料(Instance Data)
  3. 對齊填充(Padding)

Hotspot 的 Header 包括:

  1. HashCode
  2. GC 分代年齡
  3. 鎖狀態標誌
  4. 執行緒持有的鎖
  5. 偏向執行緒 ID
  6. 偏向時間戳

物件的訪問定位

通過控制程式碼訪問物件:

深入理解Java虛擬機器筆記-自動記憶體管理機制

通過指標訪問物件:

深入理解Java虛擬機器筆記-自動記憶體管理機制

使用控制程式碼來訪問的最大好處就是reference中儲存的是穩定的控制程式碼地址,在物件被移動(垃圾收集時移動物件是非常普遍的行為)時只會改變控制程式碼中的例項資料指標,而reference本身不需要修改。

使用直接指標訪問方式的最大好處就是速度更快,它節省了一次指標定位的時間開銷,由於物件的訪問在Java中非常頻繁,因此這類開銷積少成多後也是一項非常可觀的執行成本。

Hotspot 使用的是指標。

相關文章