java jvm对象_【Java】JVM
在 HotSpot 虛擬機中,一個對象在內存中存儲的布局可以分為三塊區域:對象頭(Object Header)、實例數據(Instance Data)和對齊填充(Padding)。
當我們在 Java 代碼中,使用 new 關鍵字創建一個對象的時候,JVM 會為這個對象創建一個對應的 instanceOopDesc 對象,這個 instanceOopDesc 對象中包含了對象頭(Header)以及實例數據。
instanceOopDesc 對象的結構1
2
3
4
5
6
7
8
9
10
11
12class{
friend class VMStructs;
friend class JVMCIVMStructs;
private:
volatile markOop _mark;
// 元數據
union _metadata {
// 對應的Klass對象
Klass* _klass;
narrowKlass _compressed_klass;
} _metadata;
對象頭(Object Header)結構
而對象頭(Object Header)包括兩部分數據:Mark Word(標記字段)用于存儲對象自身的運行時數據,如哈希碼(HashCode)、GC 分代年齡、鎖狀態標志、線程持有的鎖、偏向線程 ID、偏向時間戳等等。它是實現輕量級鎖和偏向鎖的關鍵。
對應于 instanceOopDesc 對象中的 _mark 字段。
Klass Pointer(類型指針)是對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。
對應于 instanceOopDesc 對象中的 _metadata 字段,而 _metadata 字段包含一個普通 _klass 和一個壓縮后的 _compressed_klass。
如果這個對象是數組對象的話,還會有一個額外的部分用于存儲數組的長度。
Java 對象頭長度
在 32 位虛擬機中,Java 對象頭一般占有兩個機器碼( 1 個機器碼等于 4 字節,也就是 32 bit),1
2
3
4
5|--------------------------------------------------------------|
| Object Header (64 bits) |
|------------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------------|-------------------------|
但是如果對象是數組類型,則需要三個機器碼(即 96 bit),因為 JVM 可以通過 Java 對象的元數據信息確定 Java 對象的大小,但是無法從數組的元數據來確認數組的大小,所以用一塊來記錄數組長度。1
2
3
4
5|---------------------------------------------------------------------------------|
| Object Header (96 bits) |
|--------------------------------|-----------------------|------------------------|
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
|--------------------------------|-----------------------|------------------------|
Mark Word(標記字段)
對 Mark Word(標記字段)的設計方式上,非常像網絡協議報文頭:將Mark Word(標記字段)劃分為多個比特位區間,并在不同的對象狀態下賦予比特位不同的含義。
Mark Word(標記字段)在 32 位 JVM 中的長度是32bit,在 64 位 JVM 中長度是64bit。
Mark Word(標記字段)用于存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標志、線程持有的鎖、偏向線程 ID、偏向時間戳等等。
對象頭信息是與對象自身定義的數據無關的額外存儲成本,但是考慮到虛擬機的空間效率,Mark Word被設計成一個非固定的數據結構以便在極小的空間內存存儲盡量多的數據,它會根據對象的狀態復用自己的存儲空間,也就是說,Mark Word會隨著程序的運行發生變化,變化狀態如下(32位虛擬機):
鎖的狀態
鎖的狀態總共有四種:無鎖狀態、偏向鎖、輕量級鎖和重量級鎖。隨著鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖(但是鎖的升級是單向的,也就是說只能從低到高升級,不會出現鎖的降級)。JDK 1.6中默認是開啟偏向鎖和輕量級鎖的,我們也可以通過-XX:-UseBiasedLocking來禁用偏向鎖。
JVM一般是這樣使用鎖和 Mark Word 的:
1,當一個對象沒有被當成鎖時,這就是一個普通的對象,Mark Word記錄對象的HashCode,鎖標志位是01,是否偏向鎖那一位是0。
2,當對象被當做同步鎖,并有一個線程A搶到了鎖時,鎖標志位還是01,但是否偏向鎖那一位改成1,前23bit記錄搶到鎖的線程id,表示進入偏向鎖狀態。
3,當線程A再次試圖來獲得鎖時,JVM發現同步鎖對象的標志位是01,是否偏向鎖是1,也就是偏向狀態,Mark Word中記錄的線程id就是線程A自己的id,表示線程A已經獲得了這個偏向鎖,可以執行同步鎖的代碼。
4,當線程B試圖獲得這個鎖時,JVM發現同步鎖處于偏向狀態,但是Mark Word中的線程id記錄的不是B,那么線程B會先用CAS操作試圖獲得鎖,這里的獲得鎖操作是有可能成功的,因為線程A一般不會自動釋放偏向鎖。如果搶鎖成功,就把Mark Word里的線程id改為線程B的id,代表線程B獲得了這個偏向鎖,可以執行同步鎖代碼。如果搶鎖失敗,則繼續執行步驟5。
5,偏向鎖狀態搶鎖失敗,代表當前鎖有一定的競爭,偏向鎖將升級為輕量級鎖。JVM會在當前線程的線程棧中開辟一塊單獨的空間,里面保存指向對象鎖Mark Word的指針,同時在對象鎖Mark Word中保存指向這片空間的指針。上述兩個保存操作都是CAS操作,如果保存成功,代表線程搶到了同步鎖,就把Mark Word中的鎖標志位改成00,可以執行同步鎖代碼。如果保存失敗,表示搶鎖失敗,競爭太激烈,繼續執行步驟6。
6,輕量級鎖搶鎖失敗,JVM會使用自旋鎖,自旋鎖不是一個鎖狀態,只是代表不斷的重試,嘗試搶鎖。從JDK1.7開始,自旋鎖默認啟用,自旋次數由JVM決定。如果搶鎖成功則執行同步鎖代碼,如果失敗則繼續執行步驟7。
7,自旋鎖重試之后如果搶鎖依然失敗,同步鎖會升級至重量級鎖,鎖標志位改為10。在這個狀態下,未搶到鎖的線程都會被阻塞。
Klass Pointer(類型指針)
對象頭(Header)的另外一部分是類型指針,即是對象指向它的類的元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。并不是所有的虛擬機實現都必須在對象數據上保留類型指針,換句話說查找對象的元數據信息并不一定要經過對象本身。另外,如果對象是一個Java數組,那在對象頭中還必須有一塊用于記錄數組長度的數據,因為虛擬機可以通過普通Java對象的元數據信息確定Java對象的大小,但是從數組的元數據中無法確定數組的大小。
Reference
總結
以上是生活随笔為你收集整理的java jvm对象_【Java】JVM的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 强制下线_【java】如何强制
- 下一篇: java魔法堂_Java魔法堂:调用外部