從零開始的jvm之記憶體管理機制
該篇源自於對《深入理解java虛擬機器》的學習和總結。大牛拍磚請輕點。
1、執行時資料區域
1.1 程式計數器
定義:當前執行緒所執行的位元組碼的行號指示器。
設計目的:為了執行緒切換後能恢復到正確的執行位置,所以需要每個執行緒都擁有一個獨立的程式計數器。
注意:
a、該記憶體區域執行緒私有,各執行緒的程式計數器互不影響,獨立儲存;
b、執行的是java方法,則計數器記錄的是正在執行的指令碼的地址。執行native方法,則計數器為空;
c、是唯一一個沒有規定oom的記憶體區域。
1.2 虛擬機器棧
定義:用以描述java方法執行的記憶體模型。即每個方法在執行時都會建立一個棧幀用於儲存區域性變量表、運算元棧、動態連結,方法出口等資訊。
區域性變量表存放基本資料型別、物件引用、returnAddress型別。
注意:
a、通常說到的java中的棧指的是這裡的區域性變量表;
b、區域性變量表用到的記憶體空間在編譯時完成分配,在方法執行時期不會改變區域性變量表的大小。
1.3 本地方法棧
定義:用以描述本地方法執行的記憶體模型。
區別:與虛擬機器棧的區別在於本地方法棧為虛擬機器使用到的native方法服務,而虛擬機器棧則是為虛擬機器使用到的java方法服務。
1.4 堆
定義:所有執行緒共享,用以存放例項物件的記憶體區域。
設計目的:唯一目的是存放例項物件。
注意:
a、該記憶體區域所有執行緒共享;
b、我們常指的gc一般都是指的對堆的回收,該處的回收率是最高的。
1.5 方法區
定義:用以儲存被載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼資料的記憶體區域。
注意:該記憶體區域所有執行緒共享。
1.5.1 執行時常量池
方法區的一部分。儲存類載入後class常量池的內容和執行時可能產生的新常量。
釋義:
class常量池指的是編譯器生成的各種字面量和符號引用。
2、物件
2.1 物件的建立
2.1.1 檢查
虛擬機器遇到一條new指令時,先檢測這個指令的引數能否在常量池中定位到一個類的符號引用,再檢測這個類符號引用代表的類是否被載入、解析、初始化。如果沒有,則先執行類載入過程。
2.1.2 分配記憶體
檢查完後,為物件分配記憶體(該大小在類載入完成時就能完全確定),如果記憶體規整,適合使用“記憶體碰撞”分配記憶體,否則適合使用“空閒列表”。其中記憶體是否規則由垃圾回收器是否帶有壓縮整理功能決定。
注意:
這裡的記憶體分配存在著安全問題,對併發的考慮和解決方案。
解決方案:
1、對分配記憶體空間的操作進行同步處理;
2、把記憶體分配的操作按照執行緒劃分到不同的空間中進行,即每個執行緒預先在堆中分配一塊記憶體,本地執行緒分配緩衝(TLAB),哪個執行緒要分配記憶體,在對應的TLAB中進行分配,只有TLAB用完需要再分配新的TLAB時,才進行同步處理。
2.1.3 初始化
記憶體分配完成後,將分配的記憶體空間初始化為零值。
設計目的:保證物件的例項欄位在java程式碼中可以不賦初始值就可以直接使用。
2.1.4 對物件進行設定
主要是對物件頭進行一些設定。
2.1.5 執行<init>方法
按照程式設計師的意願進行初始化操作,執行完後,才算是創建出一個真正可用的物件。
2.2 物件的記憶體佈局
2.2.1 物件頭
物件頭由兩部分資訊。
“Mark Word”:
其中一部分儲存物件的執行時資料,具體結構如圖:
型別指標:
物件指向它的類元資料指標,虛擬機器通過這個指標確定這個物件是哪個類的例項。
2.2.2 例項資料
例項資料是物件真正儲存有效資訊的區域,即程式碼中定義的各種型別的欄位內容。
注:儲存順序受虛擬機器分配策略引數和欄位在java原始碼中定義順序的影響。
2.2.3 對齊填充
不是必然存在,僅起佔位符的作用。
注意:
物件必須是8位元組的整數倍,物件頭是8位元組的整數倍,所以例項資料沒有對齊8的整數倍時,就需要對其填充來補全。
2.3 物件的訪問定位
物件訪問方式,取決於虛擬機器的實現而定。
2.3.1 控制代碼訪問
堆中劃分一塊記憶體作為控制代碼池,reference中儲存的是物件的控制代碼地址,控制代碼中包含了例項資料和型別資料各自的地址資訊。
優勢:reference中儲存的是穩定的控制代碼地址。
2.3.1 直接指標訪問
reference中直接儲存的是物件地址。
優勢:訪問速度快。