在HotSpot虛擬機裏,對象在堆內存中的存儲佈局能夠劃分爲三個部分:數組
對象頭(Header)oop
實例數據(Instance Data)佈局
對齊填充(Padding)。線程
HotSpot虛擬機(後面沒有說明的話默認是這個虛擬機)對象頭包括三部分:設計
一、Mark Word指針
二、指向類的指針code
三、數組長度(只有數組對象纔有)對象
Mark Word記錄了對象和鎖有關的信息,當這個對象被synchronized關鍵字當成同步鎖時,圍繞這個鎖的一系列操做都和Mark Word有關。blog
Mark Word在32位JVM中的長度是32bit,在64位JVM中長度是64bit。繼承
Mark Word在不一樣的鎖狀態下存儲的內容不一樣,在32位JVM中是這麼存的:
一共32位,兩位用來記錄鎖的信息,1位用來記錄是不是偏向鎖,若是偏向鎖是1的話,那麼會分配23位來記錄偏向的線程id,當計算過Hash後,意味着會分配25bit來記錄HashCode,那麼久沒有空間用來記錄偏向鎖的線程ID了,因此計算過HashCode後就無法再進入偏向鎖。若是進入輕量級鎖或者重量級鎖,意味着會用30bit指向指針,那麼此時對象頭中就只有兩種信息,鎖標誌、指向鎖的指針。
其中無鎖和偏向鎖的鎖標誌位都是01,只是在前面的1bit區分了這是無鎖狀態仍是偏向鎖狀態。
JDK1.6之後的版本在處理同步鎖時存在鎖升級的概念,JVM對於同步鎖的處理是從偏向鎖開始的,隨着競爭愈來愈激烈,處理方式從偏向鎖升級到輕量級鎖,最終升級到重量級鎖。
結合Mark Word分析鎖升級的流程:
1,當沒有被當成鎖時,這就是一個普通的對象,Mark Word記錄對象的HashCode,鎖標誌位是01,是否偏向鎖那一位是0(0則false , 1 則true)。
2,當對象被當作同步鎖並有一個線程A搶到了鎖時,鎖標誌位仍是01,可是否偏向鎖那一位改爲1,前23bit記錄搶到鎖的線程id,表示進入偏向鎖狀態。
3,當線程A再次試圖來得到鎖時,JVM發現同步鎖對象的標誌位是01,是否偏向鎖是1,也就是偏向狀態,Mark Word中記錄的線程id就是線程A本身的id,表示線程A已經得到了這個偏向鎖,能夠執行同步鎖的代碼。
4,當線程B試圖得到這個鎖時,JVM發現同步鎖處於偏向狀態,可是Mark Word中的線程id記錄的不是B,那麼線程B會先用CAS操做試圖得到鎖,這裏的得到鎖操做是有可能成功的,由於線程A通常不會自動釋放偏向鎖。若是搶鎖成功,就把Mark Word裏的線程id改成線程B的id,表明線程B得到了這個偏向鎖,能夠執行同步鎖代碼。若是搶鎖失敗,則繼續執行步驟5。
5,偏向鎖狀態搶鎖失敗,表明當前鎖有必定的競爭,偏向鎖將升級爲輕量級鎖。JVM會在當前線程的線程棧中開闢一塊單獨的空間,裏面保存指向對象鎖Mark Word的副本,同時在對象鎖Mark Word中保存指向這片空間的指針。上述兩個保存操做都是CAS操做,若是保存成功,表明線程搶到了同步鎖,就把Mark Word中的鎖標誌位改爲00,能夠執行同步鎖代碼。若是保存失敗,表示搶鎖失敗,競爭太激烈,繼續執行步驟6。
6,輕量級鎖搶鎖失敗,JVM會使用自旋鎖,自旋鎖不是一個鎖狀態,只是表明不斷的重試,嘗試搶鎖。從JDK1.7開始,自旋鎖默認啓用,自旋次數由JVM決定。若是搶鎖成功則執行同步鎖代碼,若是失敗則繼續執行步驟7。
7,自旋鎖重試以後若是搶鎖依然失敗,同步鎖會升級至重量級鎖,鎖標誌位改成10。在這個狀態下,未搶到鎖的線程都會被阻塞。
該指針在32位JVM中的長度是32bit,在64位JVM中長度是64bit。
Java對象的類數據保存在方法區。 並非全部的虛擬機實現都必須在對象數據上保留類型指針,換句話說,查找對象的元數據信息並不必定要通過對象自己。
若是對象是一個Java數組,那在對象頭中還必須有一塊用於記錄數組長度的數據,由於虛擬機能夠經過普通Java對象的元數據信息肯定Java對象的大小,可是若是數組的長度是不肯定的,將沒法經過元數據中的信息推斷出數組的大小。
只有數組對象保存了這部分數據, 該數據在32位和64位JVM中長度都是32bit。
實例數據部分是對象真正存儲的有效信息,即咱們在程序代碼裏面所定義的各類類型的字段內容,不管是從父類繼承下來的,仍是在子類中定義的字段都必須記錄起來。這部分的存儲順序會受到虛擬機分配策略參數(-XX:FieldsAllocationStyle
參數)和字段在Java源碼中定義順序的影響。HotSpot虛擬機默認的分配順序爲longs/doubles、ints、shorts/chars、bytes/booleans、oops(OrdinaryObject Pointers,OOPs)
,從以上默認的分配策略中能夠看到,相同寬度的字段老是被分配到一塊兒存放,在知足這個前提條件的狀況下,在父類中定義的變量會出如今子類以前。若是HotSpot虛擬機的+XX:CompactFields
參數值爲true(默認就爲true),那子類之中較窄的變量也容許插入父類變量的空隙之中,以節省出一點點空間。
這並非必然存在的,也沒有特別的含義,它僅僅起着佔位符的做用。因爲HotSpot虛擬機的自動內存管理系統要求對象起始地址必須是8字節的整數倍,換句話說就是任何對象的大小都必須是8字節的整數倍。對象頭部分已經被精心設計成正好是8字節的倍數(1倍或者2倍),所以,若是對象實例數據部分沒有對齊的話,就須要經過對齊填充來補全。
1.<<深刻理解Java虛擬機:JVM高級特性與最佳實踐(第3版) (華章原創精品)>>,周志明