概覽
-
物件頭
存放:關於堆物件的佈局、型別、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
};
如圖:
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 進行回答即可。