Synchronized(2):JVM物件結構
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.2 物件之Klass
1.3.3 物件之Oop的程式碼實現(即oopDesc類的C++程式碼結構,即oop.hpp檔案)
1.3.4 物件之Klass的程式碼實現(即InstanceKlass類的C++程式碼結構,即InstanceKlass.hpp檔案)
1.3.5 物件頭之_klass
1.3.6 物件頭之markword
1.3.7 物件頭之各個子部分實現
1.3.7.1 物件頭之各個子部分實現(64位環境 + 未開啟指標壓縮)
1.3.7.2 物件頭之各個子部分實現(64位環境 + 開啟指標壓縮)
1.3.7.3 物件頭之各個子部分實現(32位環境)
1.4 列印各種鎖物件
1.4.1 前置條件:
1.4.2 列印:無鎖物件:
1.4.3 列印:匿名偏向鎖物件:
(需要上來就把執行緒sleep一會兒,sleep時長為“偏向鎖延遲載入時間”,目的是啟動偏向鎖,即當前程式所有物件的初始狀態都是匿名偏向鎖物件)
1.4.4 列印:偏向執行緒的偏向鎖物件:
(在匿名偏向鎖的基礎上,再加個synchronized )
1.4.5 列印:從無鎖直接升級為輕量鎖物件:
(跳過匿名偏向鎖,趁著巨集程式還未開啟偏向鎖時,就加上synchronized )
1.4.6 列印:從偏向鎖升級為輕量鎖物件:
(匿名偏向鎖 + 兩個以上執行緒競爭 + 利用join實現不同時競爭一個物件)
1.4.7 列印:重量鎖物件:
(匿名偏向鎖 + 兩個以上執行緒競爭 + 同時競爭一個物件)
1.4.8 總結一下各種鎖的建立場景: