Java對象結構詳解【MarkWord 與鎖的實現原理】


Java對象存儲在Heap)內存。那么一個 Java對象到底包含什么呢?概括起來分為對象頭對象體對齊字節。如下圖所示:

【1】對象頭中的Mark Word(標記字)主要用來表示對象的線程鎖狀態,另外還可以用來配合GC、存放該對象的 hashCode
【2】Klass Word是一個指向方法區中 Class信息的指針,意味着該對象可隨時知道自己是哪個 Class的實例;
【3】數組長度也是占用64位(8字節)的空間,這是可選的,只有當本對象是一個數組對象時才會有這個部分;
【4】對象體是用於保存對象屬性的主體部分,占用內存空間取決於對象的屬性數量和類型;
【5】對齊字是為了減少堆內存的碎片空間(不一定准確)。

Mark Word(標記字)

以上是 Java對象處於5種不同狀態時,Mark Word中 64位的表現形式,上面每一行代表對象處於某種狀態時的樣子。其中各部分的含義如下:
【1】lock:2位的鎖狀態標記位,由於希望用盡可能少的二進制位表示盡可能多的信息,所以設置了 lock標記。該標記的值不同,整個 Mark Word表示的含義不同。biased_lock lock一起,表達的鎖狀態含義如上圖所示。
【2】biased_lock:對象是否啟用偏向鎖標記,只占1個二進制位。為1時表示對象啟用偏向鎖,為0時表示對象沒有偏向鎖。lock biased_lock共同表示對象處於什么鎖狀態
【3】age:4位的 Java對象年齡。在GC中,如果對象在 Survivor區復制一次,年齡增加1。當對象達到設定的閾值時,將會晉升到老年代。默認情況下,並行 GC的年齡閾值為15,並發GC的年齡閾值為6。由於age只有4位,所以最大值為15,這就是-XX:MaxTenuringThreshold 選項最大值為15的原因。
【4】identity_hashcode:31位的對象標識hashCode,采用延遲加載技術。調用方法 System.identityHashCode()計算,並會將結果寫到該對象頭中。當對象加鎖后(偏向、輕量級、重量級),MarkWord的字節沒有足夠的空間保存hashCode,因此該值會移動到線程 Monitor中。
【5】thread:持有偏向鎖的線程ID。
【6】epoch:偏向鎖的時間戳。
【7】ptr_to_lock_record:輕量級鎖狀態下,指向棧中鎖記錄的指針。
【8】ptr_to_heavyweight_monitor:重量級鎖狀態下,指向對象監視器 Monitor的指針。

我們通常說的通過 synchronized鏈接】實現的同步鎖,真實名稱叫做重量級鎖。但是重量級鎖會造成線程排隊(串行執行),且會使 CPU在用戶態和核心態之間頻繁切換,所以代價高、效率低。為了提高效率,不會一開始就使用重量級鎖,JVM在內部會根據需要,按如下步驟進行鎖的升級:
【1】初期鎖對象剛創建時,還沒有任何線程來競爭,對象的 Mark Word是下圖的第一種情形,這偏向鎖標識位是0鎖狀態01,說明該對象處於無鎖狀態(無線程競爭它)。
【2】當有一個線程來競爭鎖時,先用偏向鎖,表示鎖對象偏愛這個線程,這個線程要執行這個鎖關聯的任何代碼,不需要再做任何檢查和切換,這種競爭不激烈的情況下,效率非常高。這時 Mark Word會記錄自己偏愛的線程的ID,把該線程當做自己的熟人。如下圖第二種情形。
【3】當有兩個線程開始競爭這個鎖對象,情況發生變化了,不再是偏向(獨占)鎖了,鎖會升級為輕量級鎖,兩個線程公平競爭,哪個線程先占有鎖對象並執行代碼,鎖對象的 Mark Word就執行哪個線程的棧幀中的鎖記錄。如下圖第三種情形。
【4】如果競爭的這個鎖對象的線程超過兩個線程,導致了更多的切換和等待,JVM會把該鎖對象的鎖升級為重量級鎖,這個就叫做同步鎖,這個鎖對象 Mark Word再次發生變化,會指向一個監視器對象,這個監視器對象用集合的形式,來登記和管理排隊的線程。如下圖第四種情形。

Klass Word(類指針)

這一部分用於存儲對象的類型指針,該指針指向它的元數據,JVM通過這個指針確定對象是哪個類的實例。該指針的位長度為JVM的一個字大小,即 32位的 JVM為 32位,64位的 JVM為 64位。

如果應用的對象過多,使用 64位的指針將浪費大量內存,統計而言,64位的 JVM將會比 32位的 JVM多耗費 50%的內存。為了節約內存可以使用選項 +UseCompressedOops開啟指針壓縮,其中,oop即 ordinary object pointer普通對象指針。開啟該選項后,下列指針將壓縮至32位:
【1】每個 Class的屬性指針(即靜態變量);
【2】每個對象的屬性指針(即對象變量);
【3】普通對象數組的每個元素指針;
當然,也不是所有的指針都會壓縮,一些特殊類型的指針 JVM不會優化,比如指向 PermGen的 Class對象指針(JDK8中指向元空間的 Class對象指針)、本地變量、堆棧元素、入參、返回值和NULL指針等。

數組長度

如果對象是一個數組,那么對象頭還需要有額外的空間用於存儲數組的長度,這部分數據的長度也隨着 JVM架構的不同而不同:32位的JVM上,長度為32位;64位JVM則為64位。64位 JVM如果開啟 +UseCompressedOops選項,該區域長度也將由64位壓縮至32位。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM