1. 程式人生 > >深入理解JVM:HotSpot虛擬機對象探秘

深入理解JVM:HotSpot虛擬機對象探秘

意願 對象分配 初始化 處理 失敗 每一個 面向 this 線程id

對象的創建
java是一門面向對象的語言。在Java程序執行過程中無時無刻有Java對象被創建出來。在語言層面上,創建對象(克隆、反序列化)一般是一個newkeyword而已,而在虛擬機中,對象的創建步驟例如以下:
1、當虛擬機遇到new指令時。首先將去檢查這個指令參數能否在常量池中定位到一個類的引用符號,而且檢查這個符號引用代表的類是否被載入、解析和初始化過。假設沒有。那必須先執行相應的類載入過程。
2、在類載入檢查通過以後。接下來虛擬機將為新生對象分配內存。對象所需的內存大小在類載入後便確定。為對象分配空間的任務等同於把一塊確定大小的內存從Java堆劃分出來。

假設Java堆中的內存並非規整的,已使用的內存和空暇內存相互交錯,那就沒辦法簡單的進行指針碰撞了,虛擬機就必須維護一個隊列表,記錄哪些內存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,並更新列表上的記錄,這樣的分配方式稱為:“空暇列表”。選擇哪種分配方式由Java堆是否規則決定。
除了怎樣劃分可用空間之外。還有另外一個須要考慮的問題是對象創建在虛擬機中是很頻繁的行為,即使是僅僅改動一個指針所指向的位置,在並發情況下也並非線程安全的。可能出現正在給對象A分配內存,指針還沒來得及改動,對象B又同一時候使用了原來的指針來分配內存的情況。解決這一問題的方案是:
2.1、對分配內存空間的動作進行同步處理——實際上虛擬機採用CAS配上失敗重試的方式,保證更新操作原子性
2.2、把內存分配的動作依照線程劃分在不同空間之中進行。即每一個線程在Java堆中預先分配一小塊內存。稱為本地線程分配緩存(TLAB)。哪個線程要分配內存,就在哪個線程的TLAB上分配,僅僅有TLAB用完並分配新的TLAB時,才須要同步鎖定。虛擬機是否使用TLAB,能夠通過-XX:+/-UseTLAB參數來設定。


3、內存分配完畢以後。虛擬機須要將分配到的內存空間都初始化為零值(不包括對象頭),假設使用TLAB,這一工作過程也能夠提前至TLAB分配時進行,這一步操作保證了對象實例字段在Java代碼中能夠不賦初始值就能直接使用,程序能訪問到這些字段的數據類型所相應的零值。


4、接下來虛擬機要對對象進行必要的設置,比如:這個對象是哪個類的實例、怎樣才幹找到類的元數據信息、對象的哈希碼、對象GC分代年齡信息等。這些信息存放在對象的信息頭之中。依據虛擬機執行狀態的不同。如是否使用偏向鎖等,對象頭會有不同的設置方式。
上述工作完畢以後,從虛擬機角度來看,一個新的對象已經產生了,可是從Java程序來看,對象才剛剛開始——(init)方法還沒有執行。全部的字段都還為零,所以,一般來說。執行new命令後。會接著執行init方法。把對象依照程序猿的意願進行初始化,這樣一個真正可用的對象才算全然產生出來。
對象的內存布局


還Hotspot虛擬機中,對象的內存中存儲的布局分為3塊區域:對象頭(header)、實例數據(Instance Data)、對其填充(Padding)
Hotspot虛擬機的對象頭包括兩部分信息,第一部分用於存儲自身執行時的數據,比如:哈希碼、GC分代年齡、鎖狀態標識、線程持有鎖、偏向線程id、偏向時間戳,這部分數據數據長度在32位和64位的虛擬機(未開啟指針壓縮)中分別為32bit和64bit,官方稱為’Mark word’。

對象須要存儲的執行時的數據許多。已經超出了32位、64位bitmap結構所能記錄的限度,可是對象頭信息是與對象自身定義的數據無關額外的存儲成本,考慮到虛擬機的空間效率。Mark work被設計成一個非固定的數據結構以便在極小空間內存儲盡可能多的信息,他會依據對象狀態復用自己的存儲空間。


對象頭的還有一部分是類型指針,即對象指向他的類元數據的指針。虛擬機通過這個指針來確定這個對象是哪個類的實例。

假設對象是一個Java數組,那在對象中還必須有一塊用於記錄數組長度的數據。因為虛擬機可通過普通 Java對象的元數據信息確定Java對象的大小,可是從數組的元數據中卻無法確定數組的大小。
接下來的實例數據是對象真正存儲的有效信息。也是在程序代碼中所定義的各種類型的字段內容。不管是從父類繼承下來的還是在子類中定義的,都須要記錄下來。這部分的存儲順序會受到虛擬機分配策略參數和字段在Java源代碼中定義順序的影響。

Hotspot虛擬機的分配策略是同樣寬度的字段總是被分配到一起。在滿足這個前提條件下。在父類中定義的變量會出如今子類之前。
對象的訪問定位
建立對象是為了使用對象,我們的Java程序須要通過棧上的reference數據來操作堆上的詳細對象。因為reference類型在Java虛擬機規範中僅僅規定了一個指向對象的引用。並未定義這個引用應該通過何種方式去定位、訪問堆中對象的詳細位置。所以對象訪問方式也是取決於虛擬機實現而定的。眼下主流的訪問方式有兩種使用句柄和直接指針
使用句柄
Java堆會中將會劃分出一塊內存作為句柄池,reference中存儲的就是對象的句柄地址,而句柄中包括了對象實例數據與類型數據各自詳細的地址信息。
Java堆會中將會劃分出一塊內存作為句柄池。reference中存儲的就是對象的句柄地址,而句柄中包括了對象實例數據與類型數據各自詳細的地址信息。


技術分享
使用直接指針
Java堆對象的布局中必須考慮怎樣設置訪問類型數據的信息,而reference中存儲的直接就是對象地址
技術分享
這兩種對象訪問方式各有優勢,使用句柄訪問的最大優點就是reference中存儲的是穩定的句柄地址,在對象被移動(垃圾收集時移動對象是很普遍的行為)時僅僅會改變句柄中實例數據指針。而reference本身不須要改動。
使用直接指針的最大優點是速度快,他節省了一次指針定位的時間開銷。因為對象的訪問在Java中很頻繁,因此這類開銷積少成多也是很可觀的。

深入理解JVM:HotSpot虛擬機對象探秘