天天看點

Java對象結構與鎖實作原理及MarkWord詳解

閱讀本部落格前,需要了解基本的同步概念,傳送門:http://note.youdao.com/noteshare?id=7f10475c6bb01658b955eaca531c0be6&sub=590214A72B3C455FAB266C4FB7A32698

我們都知道,Java對象存儲在堆(Heap)記憶體。那麼一個Java對象到底包含什麼呢?概括起來分為對象頭、對象體和對齊位元組。如下圖所示:

Java對象結構與鎖實作原理及MarkWord詳解

對象的幾個部分的作用:

1.對象頭中的Mark Word(标記字)主要用來表示對象的線程鎖狀态,另外還可以用來配合GC、存放該對象的hashCode;

2.Klass Word是一個指向方法區中Class資訊的指針,意味着該對象可随時知道自己是哪個Class的執行個體;

3.數組長度也是占用64位(8位元組)的空間,這是可選的,隻有當本對象是一個數組對象時才會有這個部分;

4.對象體是用于儲存對象屬性和值的主體部分,占用記憶體空間取決于對象的屬性數量和類型;

5.對齊字是為了減少堆記憶體的碎片空間(不一定準确)。

了解了對象的總體結構,接下來深入地了解對象頭的三個部分。

一、Mark Word(标記字)

Java對象的狀态主要靠Mark Word來标記,主要有5種,大部分與線程有關。這裡以64位JVM為例:

Java對象結構與鎖實作原理及MarkWord詳解

以上是Java對象處于5種不同狀态時,Mark Word中64個位的表現形式,上面每一行代表對象處于某種狀态時的樣子。其中各部分的含義如下:

lock:2位的鎖狀态标記位,由于希望用盡可能少的二進制位表示盡可能多的資訊,是以設定了lock标記。該标記的值不同,整個Mark Word表示的含義不同。biased_lock和lock一起,表達的鎖狀态含義如下:

biased_lock       lock            狀态

0                        01              無鎖

1                        01              偏向鎖

                          00              輕量級鎖

                          10              重量級鎖

                          11              GC标記

biased_lock:對象是否啟用偏向鎖标記,隻占1個二進制位。為1時表示對象啟用偏向鎖,為0時表示對象沒有偏向鎖。lock和biased_lock共同表示對象處于什麼鎖狀态。

age:4位的Java對象年齡。在GC中,如果對象在Survivor區複制一次,年齡增加1。當對象達到設定的門檻值時,将會晉升到老年代。預設情況下,并行GC的年齡門檻值為15,并發GC的年齡門檻值為6。由于age隻有4位,是以最大值為15,這就是-XX:MaxTenuringThreshold選項最大值為15的原因。

identity_hashcode:31位的對象辨別hashCode,采用延遲加載技術。調用方法System.identityHashCode()計算,并會将結果寫到該對象頭中。當對象加鎖後(偏向、輕量級、重量級),MarkWord的位元組沒有足夠的空間儲存hashCode,是以該值會移動到管程Monitor中。

thread:持有偏向鎖的線程ID。

epoch:偏向鎖的時間戳。

ptr_to_lock_record:輕量級鎖狀态下,指向棧中鎖記錄的指針。

ptr_to_heavyweight_monitor:重量級鎖狀态下,指向對象螢幕Monitor的指針。

        我們通常說的通過synchronized實作的同步鎖,真實名稱叫做重量級鎖。但是重量級鎖會造成線程排隊(串行執行),且會使CPU在使用者态和核心态之間頻繁切換,是以代價高、效率低。為了提高效率,不會一開始就使用重量級鎖,JVM在内部會根據需要,按如下步驟進行鎖的更新:

        1.初期鎖對象剛建立時,還沒有任何線程來競争,對象的Mark Word是下圖的第一種情形,這偏向鎖辨別位是0,鎖狀态01,說明該對象處于無鎖狀态(無線程競争它)。

        2.當有一個線程來競争鎖時,先用偏向鎖,表示鎖對象偏愛這個線程,這個線程要執行這個鎖關聯的任何代碼,不需要再做任何檢查和切換,這種競争不激烈的情況下,效率非常高。這時Mark Word會記錄自己偏愛的線程的ID,把該線程當做自己的熟人。如下圖第二種情形。

        3.當有兩個線程開始競争這個鎖對象,情況發生變化了,不再是偏向(獨占)鎖了,鎖會更新為輕量級鎖,兩個線程公平競争,哪個線程先占有鎖對象并執行代碼,鎖對象的Mark Word就執行哪個線程的棧幀中的鎖記錄。如下圖第三種情形。

        4.如果競争的這個鎖對象的線程更多,導緻了更多的切換和等待,JVM會把該鎖對象的鎖更新為重量級鎖,這個就叫做同步鎖,這個鎖對象Mark Word再次發生變化,會指向一個螢幕對象,這個螢幕對象用集合的形式,來登記和管理排隊的線程。如下圖第四種情形。

        如果線程競争鎖以圖的方式來單獨描述的話,鎖對象處于5種狀态下的Mark Word分别表現如下:

Java對象結構與鎖實作原理及MarkWord詳解

二、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位。

by@六噸代碼