Java 對象頭那點事

概覽

image


  • 對象頭
    存放:關於堆對象的佈局、類型、GC狀態、同步狀態和標識哈希碼的基本信息。Java對象和vm內部對象都有一個共同的對象頭格式。
    (後面做詳細介紹)

  • 實例數據
    存放:類的數據信息,父類的信息,對象字段屬性信息。
    如果對象有屬性字段,則這裏會有數據信息。如果對象無屬性字段,則這裏就不會有數據。
    根據字段類型的不同佔不同的字節,例如boolean類型佔1個字節,int類型佔4個字節等等;

  • 對齊填充
    存放:爲了字節對齊,填充的數據,不是必須的。
    默認情況下,Java虛擬機堆中對象的起始地址需要對齊至8的倍數。
    假如對象頭大小爲12,實例數據大小爲5,最近且大於12+5的8的倍數值是24,則對齊補充大小爲:24-12-5=7。


爲什麼需要對象填充?

字段內存對齊的其中一個原因,是讓字段只出現在同一CPU的緩存行中。如果字段不是對齊的,那麼就有可能出現跨緩存行的字段。也就是說,該字段的讀取可能需要替換兩個緩存行,而該字段的存儲也會同時污染兩個緩存行。這兩種情況對程序的執行效率而言都是不利的。其實對其填充的最終目的是爲了計算機高效尋址。

對象頭

mark word

OpenJDK(JDK8)地址:https://github.com/openjdk/jdk
根據OpenJDK 官方源碼中MarkOop.hpp文件中給的註釋介紹,可以大概看出mark word的組成。

MarkOop.hpp中的註釋1:

32 bits:
--------
           hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
           JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
           size:32 ------------------------------------------>| (CMS free block)
           PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)

64 bits:
--------
           unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
           JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
           PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
           size:64 ----------------------------------------------------->| (CMS free block)

MarkOop.hpp中的註釋2:

    [JavaThread* | epoch | age | 1 | 01]       lock is biased toward given thread
    [0           | epoch | age | 1 | 01]       lock is anonymously biased

  - the two lock bits are used to describe three states: locked/unlocked and monitor.

    [ptr             | 00]  locked             ptr points to real header on stack
    [header      | 0 | 01]  unlocked           regular object header
    [ptr             | 10]  monitor            inflated lock (header is wapped out)
    [ptr             | 11]  marked             used by markSweep to mark an object
                                               not valid at any other time

MarkOop.hpp中的源碼1:

  enum { age_bits                 = 4,
         lock_bits                = 2,
         biased_lock_bits         = 1,
         max_hash_bits            = BitsPerWord - age_bits - lock_bits - biased_lock_bits,
         hash_bits                = max_hash_bits > 31 ? 31 : max_hash_bits,
         cms_bits                 = LP64_ONLY(1) NOT_LP64(0),
         epoch_bits               = 2
  };

如圖:
image


MarkOop.hpp中的源碼2:

  enum { locked_value             = 0,
         unlocked_value           = 1,
         monitor_value            = 2,
         marked_value             = 3,
         biased_lock_pattern      = 5
  };
  • locked_value
    輕量級鎖狀態值,mark word 最後2位爲00,轉爲10進製爲0。
  • unlocked_value
    無鎖狀態值,mark word 最後3位爲001,轉爲10進製爲1。
  • monitor_value
    重量級鎖狀態值,mark word 最後2位爲10,轉爲10進製爲2。
  • marked_value
    mark word 最後2位爲11,轉爲10進製爲3。
    作用比較複雜,
    1:當鎖升級爲重量級鎖的過程中,會將markword設置爲這個值。
    2:當對象GC時也要使用這個值。

markOop.hpp部分源碼如下:

  // 僅用於存儲到Lock Record中,用來表示鎖正在使用重量級監視器(輕量級鎖膨脹爲重量級鎖之前會這麼做)
  static markOop unused_mark() {
    return (markOop) marked_value;
  }

  // age operations
  markOop set_marked()   { return markOop((value() & ~lock_mask_in_place) | marked_value); }
  markOop set_unmarked() { return markOop((value() & ~lock_mask_in_place) | unlocked_value); }
  • biased_lock_pattern
    偏向鎖狀態值,mark word 最後3位爲101,轉爲10進製爲5。

markOop.cpp中還有以下代碼,用以判斷當前markword處於哪種鎖狀態:

  // 輕量級鎖
  bool is_locked()   const {
    return (mask_bits(value(), lock_mask_in_place) != unlocked_value);
  }
  // 偏向鎖
  bool is_unlocked() const {
    return (mask_bits(value(), biased_lock_mask_in_place) == unlocked_value);
  }
  // marked
  bool is_marked()   const {
    return (mask_bits(value(), lock_mask_in_place) == marked_value);
  }
  // 無鎖
  bool is_neutral()  const { return (mask_bits(value(), biased_lock_mask_in_place) == unlocked_value); }
  // 膨脹時 markOop 的特殊臨時狀態。 在鎖外查看標記的代碼需要考慮到這一點。
  bool is_being_inflated() const { return (value() == 0); }
  // 鎖對象處於升級爲重量級鎖的過程中
  static markOop INFLATING() { return (markOop) 0; }

爲什麼對象頭分代佔4bit
因爲對象經過15次GC就會被放入老年代,而15轉化爲二進制就是1111,幹好佔4bit.


epoch的作用
抄自於:http://www.itqiankun.com/article/bias-lock-epoch-effect

其本質是一個時間戳,代表了偏向鎖的有效性,epoch存儲在可偏向對象的MarkWord中。

①:除了對象中的epoch,對象所屬的類class信息中,也會保存一個epoch值。

②:每當遇到一個全局安全點時(這裏的意思是說批量重偏向沒有完全替代了全局安全點,全局安全點是一直存在的),比如要對class C 進行批量再偏向,則首先對 class C中保存的epoch進行增加操作,得到一個新的epoch_new。

③:然後掃描所有持有 class C 實例的線程棧,根據線程棧的信息判斷出該線程是否鎖定了該對象,僅將epoch_new的值賦給被鎖定的對象中,也就是現在偏向鎖還在被使用的對象纔會被賦值epoch_new。

④:退出安全點後,當有線程需要嘗試獲取偏向鎖時,直接檢查 class C 中存儲的 epoch 值是否與目標對象中存儲的 epoch 值相等, 如果不相等,則說明該對象的偏向鎖已經無效了(因爲(3)步驟裏面已經說了只有偏向鎖還在被使用的對象纔會有epoch_new,這裏不相等的原因是class C裏面的epoch值是epoch_new,而當前對象的epoch裏面的值還是epoch),此時競爭線程可以嘗試對此對象重新進行偏向操作。

klass point

元數據指針class pointer,即指向方法區的instanceKlass實例(虛擬機通過這個指針來羣定這個對象是哪個類的實例)。

oop.hpp中的源碼:

class oopDesc {
  friend class VMStructs;
 private:
  volatile markOop  _mark;
  union _metadata {
    Klass*      _klass;
    narrowKlass _compressed_klass;
  } _metadata;

length field

該屬性只有數組對象纔有,用以表示數組的長度。

arrayOop.hpp中有這麼一段註釋:

// The layout of array Oops is:
//
//  markOop
//  Klass*    // 32 bits if compressed but declared 64 in LP64.
//  length    // shares klass memory or allocated after declared fields.

總結

可能面試的時候會被問到這個問題:爲什麼一個對象可以當成一把鎖?
這方面可以與上文中提到的對象頭、markword 進行回答即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章