1. 對象結構
1.1 對象結構概覽

1. 對象頭:Instance Header
Java對象最復雜的一部分,采用C++定義了頭的協議格式,存儲了Java對象hash、GC年齡、鎖標記、class指針、數組長度等信息,稍后做出詳細解說。
2. 實例數據:Instance Data
這部分數據才是真正具有業務意義的數據,實際上就是當前對象中的實例字段。
在VM中,對象的字段是由基本數據類型和引用類型組成的。
對象的字段們有如下規則:
1:除了對象整體需要按8字節對齊外,每個成員變量都盡量使本身的大小在內存中盡量對齊。比如 int 按 4 位對齊,long 按 8 位對齊。
2:類屬性按照如下優先級進行排列:長整型long和雙精度類型double;整型int和浮點型float;字符char和短整型short;字節類型byte和布爾類型boolean;最后是引用類型。這些屬性都按照各自的單位對齊。
3:優先按照規則一和二處理父類中的成員,接著才是子類的成員。
4:當父類中最后一個成員和子類第一個成員的間隔如果不夠4個字節的話,就必須擴展到4個字節的基本單位。
5:如果子類第一個成員是一個雙精度或者長整型,并且父類并沒有用完8個字節,JVM會破壞規則2,按照整形(int),短整型(short),字節型(byte),引用類型(reference)的順序,向未填滿的空間填充。
3. 對齊填充(可能存在):
在hotSpot虛擬機中,默認的對齊位數是8,與CPU架構無關。
1.2 對象的占用空間
1.2.1 理論分析
對象占用空間 =
對象頭(
Mark Word (32位JVM,4字節)(64位JVM,8字節)
+ Klass指針 (32位JVM,4字節)(64位JVM,8字節)(64位JVM && 默認開啟壓縮-XX:+UseCompressedOops,4字節)
+ Array length (固定為4字節,因為數組最大長度是int最大值)
)
(Klass指針的大小根據是否開啟壓縮而定-XX:+UseCompressedOops)
+ 實例數據(
+ 對象內部的基本類型屬性:根據其大小而定比如 int=4字節 。byte 1;boolean 1;char 2;short 2;int 4;float 4;long 8;double 8
+ 對象內部的引用類型屬性:只需要占用其 Klass指針部分即可( 即4字節或者8字節)
)
+ 對齊填充(
補齊為8字節的倍數
)
一個含有兩個元素的int數組的占用空間為:
8(Mark Word)+
4(Klass指針且開啟壓縮)+
4(Array length )+
4*2(兩個int)
=24字節
一個字符串對象占用內存空間為:
Mark Word (32位JVM,4字節)(64位JVM,8字節)+
Klass指針 (32位JVM,4字節)(64位JVM,8字節)(64位JVM && 默認開啟壓縮-XX:+UseCompressedOops,4字節)+
1個 int 屬性(4字節)+
1個 long 屬性(8字節)+
char 數組屬性(Mark Word 8字節 + Klass指針4字節 + Array length 固定為4字節 + n個char占用2n字節)+
對齊填充
= 40字節+2n字節+對齊填充。
注意,說明一個問題,數組在作為屬性時,占用的空間并不只是Klass指針4字節,而是數組的占用的全部內存?
1.2.2 打印顯示對象占用空間方法1:
使用Unsafe:
java中的sun.misc.Unsafe類,有一個objectFieldOffset(Field f)方法,表示獲取指定字段在所在實例中的起始地址偏移量,如此可以計算出指定的對象中每個字段的偏移量,
值為最大的那個就是最后一個字段的首地址,加上該字段的實際大小,就能知道該對象整體的大小。
1 class test{ 2 boolean age; 3 int name; 4 boolean married; 5 }
1 static sun.misc.Unsafe U = null; 2 3 static Field theUnsafeInstance = null; 4 5 static { 6 try { 7 theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe"); 8 theUnsafeInstance.setAccessible(true); 9 U = (Unsafe) theUnsafeInstance.get(Unsafe.class); 10 } catch (NoSuchFieldException e) { 11 e.printStackTrace(); 12 } catch (IllegalAccessException e) { 13 e.printStackTrace(); 14 } 15 }
1 public static void main(String[] args) { 2 try { 3 Field field1 = test.class.getDeclaredField("age"); 4 System.out.println(U.objectFieldOffset(field1)); 5 Field field2 = test.class.getDeclaredField("name"); 6 System.out.println(U.objectFieldOffset(field2)); 7 Field field3 = test.class.getDeclaredField("married"); 8 System.out.println(U.objectFieldOffset(field3)); 9 Field field4 = test.class.getDeclaredField("hh"); 10 System.out.println(U.objectFieldOffset(field4)); 11 12 } catch (NoSuchFieldException e) { 13 e.printStackTrace(); 14 } 15 16 }
1 16 2 12 3 17 4 java.lang.NoSuchFieldException: hh 5 at java.lang.Class.getDeclaredField(Class.java:2070) 6 at test.main(test.java:46)
可以看到,通過Unsafe.objectFieldOffset(fieldName)方法計算看到:
test類的age/name/married三個字段的起始偏移量分別是16、12、17
而又因為married是boolean類型(見1.3.0),這個boolean字段排在在實例數據的最后,所以married字段的結尾偏移量為17+1。
所以可以算出每個test類對象占用空間大小 = 18 + 6= 24字節.(6字節是padding)
1.2.3 打印對象占用空間方法2:
見下文《1.4 打印各種鎖對象》。
1.3 對象、對象頭詳解
1.3.0 OOP-Klass模型
JVM中把我們上層可見的Java對象在底層實際上表示為兩部分:oop部分 + Klass部分,
Oop保存在堆上,專注于表示對象的信息,就是通常意義上的堆中對象。
(對象頭、實例數據、對其填充)
Klass保存在方法區,專注于表示類的信息。(所以保存在方法區)
猜測方法區的內部結構以類為單位的一坨一坨的塊。這么看應該就是Klass了。所以Klass保存的信息 = 方法區保存的信息。
(類型信息、運行時常量池、字段信息、方法信息等)

1.3.1 對象之OOP
當我們基于某個Java類用new創建一個新對象時,JVM會為其在堆上創建一個相應的oopDesc類的子類(instanceOopDesc)實例,用于存放該對象的(對象頭、實例數據、對其填充)。
注意和Klass的定義區分,Klass是類加載時生成、用于存放該類的東西即metadata。
而在堆上創建的這個instanceOopDesc所對應的地址會被用來創建一個引用,賦給當前線程運行時棧上的一個變量。
1.3.1.1 oopDesc類:
所在位置:oop.hpp文件。
是所有Oop的共同基類,
這個類的內部結構就是通常意義上的堆中對象的結構:對象頭、 實例數據、對齊填充。
1.3.1.2 instanceOopDesc類:
所在位置:instanceOop.hpp文件。
繼承于oopDesc類。
理解為Java對象(堆中部分)的C++實現。
這個類的內部結構,比其父類多了header_size等三個看不懂干嘛的屬性。
1.3.1.3 舉個栗子:arrayOopDesc類
所在位置:arrayOop.hpp文件。
繼承于oopDesc類,
理解為數組對象(堆中部分)的C++實現的基類,即所有數組對象的oop部分都繼承于他。
1.3.2 對象之Klass
一個Java類在被JVM加載時,JVM會為其在方法區開辟一個相應的Klass類的子類(instanceKlass)空間,用于存放該類的(類型信息、運行時常量池、字段信息、方法信息等),
(猜測)這些信息就叫類的元信息metadata。
注意和OOP的定義區分,OOP是創建對象時生成、用于存放該對象的東西。
1.3.2.1 Klass類:
所在位置:Klass.hpp文件。
是所有Klass的共同基類,
這個類的內部結構沒看懂。
1.3.2.2 InstanceKlass類:
所在位置:instanceKlass.hpp文件。
繼承于Klass類。
理解為Java對象(方法區部分)的C++實現。
這個類的內部結構,比其父類(Klass類)多了KlassFieldClosure、OopMapBlock等。
1.3.2.3 舉個栗子:arrayKlass類
所在位置:arrayKlass.hpp文件。
繼承于Klass類。
理解為數組對象(方法區部分)的C++實現的基類,即所有數組對象的Klass部分都繼承于他。
1.3.3 對象之Oop的代碼實現(即oopDesc類的C++代碼結構,即oop.hpp文件)
看一下oopDesc類的C++代碼結構:(原則上應該看instanceOopDesc類源碼,但是他絕大多數都是繼承于oopDesc,自己只有三個看不懂干嘛的屬性)
可以看到它含有以下屬性:
1 //hotspot/src/share/vm/oops/oop.hpp 2 class oopDesc { 3 friend class VMStructs; 4 private: 5 volatile markOop _mark; // 屬性之:對象頭的markword 6 union _metadata { // 屬性之:對象頭的klass 7 Klass* _klass; 8 narrowKlass _compressed_klass; 9 } _metadata; 10 public: 11 jbyte* byte_field_addr(int offset) const; // 屬性之:實例數據之:該對象可能包含的八種基本類型指向的地址、引用類型指向的地址 12 jchar* char_field_addr(int offset) const; 13 jboolean* bool_field_addr(int offset) const; 14 jint* int_field_addr(int offset) const; 15 jshort* short_field_addr(int offset) const; 16 jlong* long_field_addr(int offset) const; 17 jfloat* float_field_addr(int offset) const; 18 jdouble* double_field_addr(int offset) const; 19 Metadata** metadata_field_addr(int offset) const; 20 public: 21 void* field_base(int offset) const; //猜測是對齊填充 22 }
1.3.3.1. 對象之Oop的對象頭的markword的代碼實現:
一個markOopDesc類型的屬性,屬性名字叫做_mark。
1.3.3.2. 對象之Oop的對象頭的klass的代碼實現:
一個指向方法區Klass實例(即指向該類的metadata)的指針 或者 一個narrowKlass類型的屬性(即指向該類的metadata)
,只能是兩者之一,屬性名字叫做_metadata 。
(當JVM開啟了 -XX:UseCompressedOops選項時,就表示啟用指針壓縮,則使用narrowKlass類型封裝“指向Klass實例的指針”,否則直接使用“指向Klass實例的指針”)
1.3.3.3. 對象之Oop的對象頭的arraylength的代碼實現:
代碼中沒找到。
1.3.3.4. 對象之Oop的實例數據的代碼實現:
猜測保存的是各種類型實例的地址。代碼中沒找到。
1.3.3.5. 對象之Oop的對齊填充的代碼實現:
代碼中沒找到。
1.3.4 對象之Klass的代碼實現(即InstanceKlass類的C++代碼結構,即InstanceKlass.hpp文件)
這個類的內部結構,比其父類(Klass類,Klass.hpp)多了KlassFieldClosure字段、OopMapBlock字段等。
1.3.4.1 對象之Klass的KlassFieldClosure字段
不知道干嘛的
1.3.4.2 對象之Klass的OopMapBlock字段
1 class OopMapBlock VALUE_OBJ_CLASS_SPEC { 2 private: 3 int _offset; 4 uint _count; 5 };
這個屬性很重要。
由于GC收集時要掃描存活的對象(不考慮對象的非指針域、只掃描對象的指針域),那么這個時候就會用到每個類(每個InstanceKlass)的OopMapBlock結構(字段)。
每個InstanceKlass可能有多個OopMapBlock,子類一個、父類一個。
OopMapBlock里面記錄著這個實例中的子對象數量、子對象們的地址。具體如何數據的呢:
OopMapBlock有兩個屬性:_offset、_count。
offset描述第一個所引用的oop相對于當前oop地址的偏移量,
count表示包含的oop的個數,所以尋找oop時從offset向后尋找count個oop即可。
前面1.1.2小節說過:類屬性按照如下優先級進行排列:長整型long和雙精度類型double;整型int和浮點型float;字符char和短整型short;字節類型byte和布爾類型boolean;最后是引用類型。這些屬性都按照各自的單位對齊。
說明堆中對象的引用類型屬性地址都是挨著的。
關于OopMapBlock我以前有個疑問:
我的想法是:因為InstanceKlass存在于方法區中,而OopMapBlock在InstanceKlass中,所以OopMapBlock也存在于方法區。
那么OopMapBlock里面存的就應該是:這個Klass所引用的別的Klass的信息。那為什么GC時遍歷OopMapBlock就可以標記存活對象了呢?因為按道理說OopMapBlock存的是Klass信息,而不是對象信息。
回答:
Class信息只是提供了對象創建的模板, 定義了內存的布局. 簡單來說, 就是給定一個特定class類型的對象, 我們可以知道x偏移處, 這個對象的類型是什么.
而對于用這個模板創建的具體對象來說, 這個偏移處的引用可能為null, 也可能是某個具體的對象. GC在掃描這個對象時, 需要根據這個對象的內存布局, 找到里面對象引用成員的具體值.
這個信息在Class的模板中是可以找到的, 但是Class模板中的信息太多了, 比如某個Class有100個字段, 但只有一個是對象引用, 其他全部是原始的int這種類型, 根據Class信息來掃描就會很效率很低.
這個時候, OopMapBlock只要告訴GC, 這個對象內存最后的8個字節是對象引用, 那么GC只要掃描最后這個內存去找對象指針就行了.
1.3.5 對象頭之_klass
即oopDesc類的_metadata 屬性,是一個指向方法區Klass實例的指針。
作用是,指向方法區中本對象的instanceKlass。
這個概念很重要,它連接了同一個對象的堆中部分(oop)和方法區部分(Klass)。
(32位JVM,對象頭之_klass所占4字節)
(64位JVM,不開啟指針壓縮,對象頭之_klass所占8字節)
(64位JVM ,默認開啟壓縮-XX:+UseCompressedOops,對象頭之_klass所占4字節)
1.3.6 對象頭之markword
即oopDesc類的_mark屬性。類型為markOop類。
作用是,保存一些GC分代年齡、鎖狀態標記、哈希碼、epoch、是否可偏向等信息。
(32位JVM,對象頭之markword所占4字節)
(64位JVM,對象頭之markword所占8字節)
1.3.7 對象頭之各個子部分實現
看一下markOop.hhp文件源碼,目的是想要先了解一下markword內部,從而了解整個對象頭:
下面的代碼為markOop.hhp的注釋部分,表示的是markword各部分占用的空間。
1 // Bit-format of an object header (most significant first, big endian layout below): 2 // 3 // 32 bits: 4 // -------- 5 // hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object) 6 // JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object) 7 // size:32 ------------------------------------------>| (CMS free block) 8 // PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object) 9 // 10 // 64 bits: 11 // -------- 12 // unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object) 13 // JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object) 14 // PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object) 15 // size:64 ----------------------------------------------------->| (CMS free block) 16 // 17 // unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object) 18 // JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object) 19 // narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object) 20 // unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
1.3.7.1 對象頭之各個子部分實現(64位環境 + 未開啟指針壓縮)
雖然上面的源碼沒看懂,但是有人總結了對象頭的各部分結構占用的空間:
64位環境未開啟指針壓縮的場景:

對象無鎖狀態時:
1. markword = 64位 = 未使用空間25位 + 對象identity_hashcode31位 + 未使用空間1位 + 分代年齡4位 + 是否偏向鎖1位 (0)+ 鎖標志位2位 (01)
2. _klass = 64位 = 指向該對象所屬類的存放法在方法區中的元信息metadata。
3. Array length = 32位 = 用于儲存數組長度(所以數組最大長度為int最大值)
4. 合計空間 = 128位(16字節) 或者 160位(20字節)
對象為偏向鎖時:
1. markword = 64位 = thread54位 + epoch2位 + 未使用空間1位 + 分代年齡4位 + 是否偏向鎖1位 (1)+ 鎖標志位2位 (01)
2. _klass = 64位 = 指向該對象所屬類的存放法在方法區中的元信息metadata。
3. Array length = 32位 = 用于儲存數組長度(所以數組最大長度為int最大值)
4. 合計空間 = 128位(16字節) 或者 160位(20字節)
對象為輕量鎖時:
1. markword = 64位 = 指向棧中鎖記錄LockRecord的指針62位 + 鎖標志位2位 (00)
2. _klass = 64位 = 指向該對象所屬類的存放法在方法區中的元信息metadata。
3. Array length = 32位 = 用于儲存數組長度(所以數組最大長度為int最大值)
4. 合計空間 = 128位(16字節) 或者 160位(20字節)
對象為重量鎖時:
1. markword = 64位 = 指向指向管程Monitor的指針62位 + 鎖標志位2位 (10)
2. _klass = 64位 = 指向該對象所屬類的存放法在方法區中的元信息metadata。
3. Array length = 32位 = 用于儲存數組長度(所以數組最大長度為int最大值)
4. 合計空間 = 128位(16字節) 或者 160位(20字節)
對象在GC執行標記算法時被插入到空閑鏈表時:
1. 空位62位 + 鎖標志位2位 (11)
2. _klass = 64位 = 指向該對象所屬類的存放法在方法區中的元信息metadata。
3. Array length = 32位 = 用于儲存數組長度(所以數組最大長度為int最大值)
4. 合計空間 = 128位(16字節) 或者 160位(20字節)
幾點說明:
identity_hashcode:對象的hash碼,hash代表的并不一定是對象的(虛擬)內存地址,但依賴于內存地址,具體取決于運行時庫和JVM的具體實現,底層由C++實現,實現細節參考OpenJDK源碼。但可以簡單的理解為對象的內存地址的整型值。
thread:持有偏向鎖的線程ID和其他信息。這個線程ID并不是JVM分配的線程ID號,和Java Thread中的ID是兩個概念。
epoch:偏向時間戳。
1.3.7.2 對象頭之各個子部分實現(64位環境 + 開啟指針壓縮)
應該就是把_klass改為32位即可。
不想研究了。
1.3.7.3 對象頭之各個子部分實現(32位環境)
略。
1.4 打印各種鎖對象
1.4.1 前置條件:
openjdk.jol包的Gradle依賴:compile 'org.openjdk.jol:jol-core:0.9'
1 public class A { 2 boolean flag = false; 3 String hy = "hhh"; 4 }
1.4.2 打印:無鎖對象:
1 public class PrintObject { 2 public static void main(String[] args) { 3 A a = new A(); 4 System.out.println(ClassLayout.parseInstance(a).toPrintable()); 5 } 6 } 7 8 BiasedLock.A object internals: 9 OFFSET SIZE TYPE DESCRIPTION VALUE 10 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 11 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 12 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 13 12 1 boolean A.flag false 14 13 3 (alignment/padding gap) 15 16 4 java.lang.String A.hy (object) 16 20 4 (loss due to the next object alignment) 17 Instance size: 24 bytes 18 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
第一行+第二行:對象頭的markword(一共64位,8字節)
無鎖對象的對象頭的markword特征是:第一個字節以001結尾(因為鎖標志位是01)(原則上應該還有hashcode,但是不知道為啥沒有)
第三行:對象a的對象頭的_klass(壓縮,所以是32位,4字節)
第四行:對象a的boolean類型字段(1字節)
第五行:不知道為啥補了3字節,猜測是為了補齊boolean1字節——>4字節(3字節)
第六行:對象a的String類型字段(即String對象的_klass,4字節)
第七行:為了湊齊8字節的倍數,所以補了4字節(4字節)
合計:24字節
1.4.3 打印:匿名偏向鎖對象:
(需要上來就把線程sleep一會兒,sleep時長為“偏向鎖延遲加載時間”,目的是啟動偏向鎖,即當前程序所有對象的初始狀態都是匿名偏向鎖對象)
1 public class PrintObject { 2 public static void main(String[] args) throws InterruptedException{ 3 Thread.sleep(5000); 4 A a = new A(); 5 System.out.println(ClassLayout.parseInstance(a).toPrintable()); 6 } 7 } 8 9 BiasedLock.A object internals: 10 OFFSET SIZE TYPE DESCRIPTION VALUE 11 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 12 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 13 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 14 12 1 boolean A.flag false 15 13 3 (alignment/padding gap) 16 16 4 java.lang.String A.hy (object) 17 20 4 (loss due to the next object alignment) 18 Instance size: 24 bytes 19 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
第一行+第二行:對象頭的markword(一共64位,8字節)
偏向鎖對象的對象頭的markword特征是:第一個字節以101結尾(因為鎖標志位是01 + 是否為偏向鎖為1)
不偏向任何線程的偏向鎖對象的對象頭的markword特征是:第一個字節以101結尾 + 線程ID為0
第三行:對象a的對象頭的_klass(壓縮,所以是32位,4字節)
第四行:對象a的boolean類型字段(1字節)
第五行:不知道為啥補了3字節,猜測是為了補齊boolean1字節——>4字節(3字節)
第六行:對象a的String類型字段(即String對象的_klass,4字節)
第七行:為了湊齊8字節的倍數,所以補了4字節(4字節)
合計:24字節
問題:
默認“偏向鎖延遲加載時間”是多少,以及如何修改?
回答:
默認時間為5s,猜測這個5s的意思是,程序啟動5s之后創建的所有對象頭的初始狀態都是不偏向任何線程的偏向鎖狀態。
可以手動修改時間:-XX:BiasedLockingStartupDelay=0,表示修改為0s,即程序啟動時就啟動偏向鎖。
也可以手動禁止偏向鎖:-XX:-UseBiasedLocking=false 關閉偏向鎖,默認進入輕量級鎖
1.4.4 打印:偏向線程的偏向鎖對象:
(在匿名偏向鎖的基礎上,再加個synchronized )
1 public class PrintObject { 2 public static void main(String[] args) throws InterruptedException{ 3 Thread.sleep(5000); 4 A a = new A(); 5 synchronized (a) { 6 System.out.println(ClassLayout.parseInstance(a).toPrintable()); 7 } 8 System.out.println(ClassLayout.parseInstance(a).toPrintable()); 9 } 10 } 11 =================================================================================================================================================================== 12 BiasedLock.A object internals: 13 OFFSET SIZE TYPE DESCRIPTION VALUE 14 0 4 (object header) 05 28 27 03 (00000101 00101000 00100111 00000011) (52897797) 15 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 16 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 17 12 1 boolean A.flag false 18 13 3 (alignment/padding gap) 19 16 4 java.lang.String A.hy (object) 20 20 4 (loss due to the next object alignment) 21 Instance size: 24 bytes 22 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total 23 24 BiasedLock.A object internals: 25 OFFSET SIZE TYPE DESCRIPTION VALUE 26 0 4 (object header) 05 28 27 03 (00000101 00101000 00100111 00000011) (52897797) 27 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 28 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 29 12 1 boolean A.flag false 30 13 3 (alignment/padding gap) 31 16 4 java.lang.String A.hy (object) 32 20 4 (loss due to the next object alignment) 33 Instance size: 24 bytes 34 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
(1)第一坨結果:
第一行+第二行:對象頭的markword,(一共64位,8字節)
偏向鎖對象的對象頭的markword特征是:第一個字節以101結尾(因為鎖標志位是01 + 是否為偏向鎖為1)
偏向了線程的偏向鎖對象的對象頭的markword特征是:第一個字節以101結尾 + 有線程ID
第三行:對象a的對象頭的_klass(壓縮,所以是32位,4字節)
第四行:對象a的boolean類型字段(1字節)
第五行:不知道為啥補了3字節,猜測是為了補齊boolean1字節——>4字節(3字節)
第六行:對象a的String類型字段(即String對象的_klass,4字節)
第七行:為了湊齊8字節的倍數,所以補了4字節(4字節)
合計:24字節
(2)第二坨結果:
因為偏向鎖退出同步塊以后,線程ID仍然不改變。
所以第二坨和第一坨相同。
1.4.5 打印:從無鎖直接升級為輕量鎖對象:
(跳過匿名偏向鎖,趁著宏程序還未開啟偏向鎖時,就加上synchronized )
1 public class PrintObject { 2 public static void main(String[] args) throws InterruptedException{ 3 // Thread.sleep(5000); 4 A a = new A(); 5 synchronized (a) { 6 System.out.println(ClassLayout.parseInstance(a).toPrintable()); 7 } 8 System.out.println(ClassLayout.parseInstance(a).toPrintable()); 9 } 10 } 11 ==================================================================================================================================================================== 12 BiasedLock.A object internals: 13 OFFSET SIZE TYPE DESCRIPTION VALUE 14 0 4 (object header) 88 f2 f1 02 (10001000 11110010 11110001 00000010) (49410696) 15 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 16 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 17 12 1 boolean A.flag false 18 13 3 (alignment/padding gap) 19 16 4 java.lang.String A.hy (object) 20 20 4 (loss due to the next object alignment) 21 Instance size: 24 bytes 22 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total 23 24 BiasedLock.A object internals: 25 OFFSET SIZE TYPE DESCRIPTION VALUE 26 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 27 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 28 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 29 12 1 boolean A.flag false 30 13 3 (alignment/padding gap) 31 16 4 java.lang.String A.hy (object) 32 20 4 (loss due to the next object alignment) 33 Instance size: 24 bytes 34 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
(1)第一坨結果:
第一行+第二行:對象頭的markword,(一共64位,8字節)
輕量鎖對象的對象頭的markword特征是:第一個字節以00結尾 + 指向棧中鎖記錄LockRecord
第三行:對象a的對象頭的_klass(壓縮,所以是32位,4字節)
第四行:對象a的boolean類型字段(1字節)
第五行:不知道為啥補了3字節,猜測是為了補齊boolean1字節——>4字節(3字節)
第六行:對象a的String類型字段(即String對象的_klass,4字節)
第七行:為了湊齊8字節的倍數,所以補了4字節(4字節)
合計:24字節
(2)第二坨結果:
因為輕量鎖退出同步塊以后,鎖退化為無鎖狀態。
所以第二坨和無鎖對象相同。
1.4.6 打印:從偏向鎖升級為輕量鎖對象:
(匿名偏向鎖 + 兩個以上線程競爭 + 利用join實現不同時競爭一個對象)
1 public class PrintObject { 2 3 public static void main(String[] args) throws InterruptedException{ 4 Thread.sleep(5000); 5 A a = new A(); 6 7 Thread t1 = new Thread(() -> { 8 synchronized (a){ 9 System.out.println("thread1 locking==========================="); 10 System.out.println(ClassLayout.parseInstance(a).toPrintable()); //偏向鎖 11 } 12 13 }); 14 t1.start(); 15 t1.join(); 16 17 synchronized (a){ 18 System.out.println("main locking==========================="); 19 System.out.println(ClassLayout.parseInstance(a).toPrintable());//輕量鎖 20 } 21 } 22 } 23 24 25 thread1 locking=========================== 26 BiasedLock.A object internals: 27 OFFSET SIZE TYPE DESCRIPTION VALUE 28 0 4 (object header) 05 f8 27 1f (00000101 11111000 00100111 00011111) (522713093) 29 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 30 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 31 12 1 boolean A.flag false 32 13 3 (alignment/padding gap) 33 16 4 java.lang.String A.hy (object) 34 20 4 (loss due to the next object alignment) 35 Instance size: 24 bytes 36 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total 37 38 main locking=========================== 39 BiasedLock.A object internals: 40 OFFSET SIZE TYPE DESCRIPTION VALUE 41 0 4 (object header) 00 f4 d9 02 (00000000 11110100 11011001 00000010) (47838208) 42 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 43 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 44 12 1 boolean A.flag false 45 13 3 (alignment/padding gap) 46 16 4 java.lang.String A.hy (object) 47 20 4 (loss due to the next object alignment) 48 Instance size: 24 bytes 49 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
(1)第一坨(偏向線程t1的偏向鎖)結果:
第一行+第二行:對象頭的markword,(一共64位,8字節)
偏向鎖對象的對象頭的markword特征是:第一個字節以101結尾
偏向了線程的偏向鎖對象的對象頭的markword特征是:第一個字節以101結尾 + 有線程ID
第三行:對象a的對象頭的_klass(壓縮,所以是32位,4字節)
第四行:對象a的boolean類型字段(1字節)
第五行:不知道為啥補了3字節,猜測是為了補齊boolean1字節——>4字節(3字節)
第六行:對象a的String類型字段(即String對象的_klass,4字節)
第七行:為了湊齊8字節的倍數,所以補了4字節(4字節)
合計:24字節
(2)第二坨(輕量鎖)結果:
第一行+第二行:對象頭的markword,(一共64位,8字節)
輕量鎖對象的對象頭的markword特征是:第一個字節以00結尾 + 指向棧中鎖記錄LockRecord
第三行:對象a的對象頭的_klass(壓縮,所以是32位,4字節)
第四行:對象a的boolean類型字段(1字節)
第五行:不知道為啥補了3字節,猜測是為了補齊boolean1字節——>4字節(3字節)
第六行:對象a的String類型字段(即String對象的_klass,4字節)
第七行:為了湊齊8字節的倍數,所以補了4字節(4字節)
合計:24字節
1.4.7 打印:重量鎖對象:
(匿名偏向鎖 + 兩個以上線程競爭 + 同時競爭一個對象)
1 public class PrintObject { 2 3 public static void main(String[] args) throws InterruptedException{ 4 Thread.sleep(5000); 5 A a = new A(); 6 7 Thread t1 = new Thread(() -> { 8 synchronized (a){ 9 System.out.println("thread1 locking==========================="); 10 System.out.println(ClassLayout.parseInstance(a).toPrintable()); //偏向鎖 11 } 12 13 }); 14 t1.start(); 15 16 synchronized (a){ 17 System.out.println("main locking==========================="); 18 System.out.println(ClassLayout.parseInstance(a).toPrintable());//輕量鎖 19 } 20 } 21 } 22 23 main locking=========================== 24 BiasedLock.A object internals: 25 OFFSET SIZE TYPE DESCRIPTION VALUE 26 0 4 (object header) 9a 66 5a 1c (10011010 01100110 01011010 00011100) (475686554) 27 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 28 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 29 12 1 boolean A.flag false 30 13 3 (alignment/padding gap) 31 16 4 java.lang.String A.hy (object) 32 20 4 (loss due to the next object alignment) 33 Instance size: 24 bytes 34 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total 35 36 thread1 locking=========================== 37 BiasedLock.A object internals: 38 OFFSET SIZE TYPE DESCRIPTION VALUE 39 0 4 (object header) 9a 66 5a 1c (10011010 01100110 01011010 00011100) (475686554) 40 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 41 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 42 12 1 boolean A.flag false 43 13 3 (alignment/padding gap) 44 16 4 java.lang.String A.hy (object) 45 20 4 (loss due to the next object alignment) 46 Instance size: 24 bytes 47 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
(1)第一坨(可能因為main線程優先級較高,所以main和t1競爭時總是main先執行,與第二坨指向同一個Monitor)
第一行+第二行:對象頭的markword,(一共64位,8字節)
重量鎖對象的對象頭的markword特征是:第一個字節以10結尾 + 指向指向管程Monitor的指針
第三行:對象a的對象頭的_klass(壓縮,所以是32位,4字節)
第四行:對象a的boolean類型字段(1字節)
第五行:不知道為啥補了3字節,猜測是為了補齊boolean1字節——>4字節(3字節)
第六行:對象a的String類型字段(即String對象的_klass,4字節)
第七行:為了湊齊8字節的倍數,所以補了4字節(4字節)
合計:24字節
(2)第二坨(可能因為main線程優先級較高,所以main和t1競爭時總是t1后執行,與第一坨指向同一個Monitor)
第一行+第二行:對象頭的markword,(一共64位,8字節)
重量鎖對象的對象頭的markword特征是:第一個字節以10結尾 + 指向指向管程Monitor的指針
第三行:對象a的對象頭的_klass(壓縮,所以是32位,4字節)
第四行:對象a的boolean類型字段(1字節)
第五行:不知道為啥補了3字節,猜測是為了補齊boolean1字節——>4字節(3字節)
第六行:對象a的String類型字段(即String對象的_klass,4字節)
第七行:為了湊齊8字節的倍數,所以補了4字節(4字節)
合計:24字節
1.4.8 總結一下各種鎖的創建場景:
無鎖:
程序啟動5s以內創建一個對象a即可。
無鎖的特點:markWord的第一個字節是001。
匿名偏向鎖:
程序啟動5s以后創建一個對象a即可。(可以先讓程序sleep五秒)
匿名偏向鎖的特點:markWord的第一個字節是101 + 無線程ID
偏向于線程的偏向鎖:
匿名偏向鎖的基礎上對對象a加一個Synchronized。
匿名偏向鎖的特點:markWord的第一個字節是101 + 有線程ID
輕量級鎖:
方式1:main線程里被Thread1,join();Thread1里面是對象a偏向于線程Thread1的偏向鎖。等Thread1執行完,markWord中依然存的是Thread1的ID,
此時Main線程對于對象a加一個synchronized,則對象a的鎖升級為由Main線程持有的輕量級鎖。
方式2:無鎖狀態時對對象a加一個synchronized即可。
輕量級鎖的特點:markWord的第一個字節是00 + 指向棧中LockRecord記錄
重量級鎖:
在輕量級鎖的基礎上,開啟另一個線程對對象a加上synchronized。此種場景為兩個線程同時synchronized對象a,
而輕量級鎖的方式1營造的場景是Thread1偏向鎖執行完成后Main線程再加一個synchronized。
重量級鎖的特點:markWord的第一個字節是10 + 指向被鎖對象的monitor

浙公網安備 33010602011771號