您好,登錄后才能下訂單哦!
這期內容當中小編將會給大家帶來有關java類對象底層是如何創建的,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
0、前言
Java程序中 User user = new User();的代碼在執行過程中,JVM究竟做了哪些工作?
1、Java類對象的創建過程
Java對象保存在內存中時,主要由三部分組成:對象頭、實例數據、對齊填充字段,所以Java對象創建的過程實際上是對這三部分進行配置、補充和初始化的過程。
注:對齊填充字段:在JVM中,要求對象占用內存的大小應該是8bit的倍數,這個信息是用來補齊8bit的,無其他作用
1.1、類加載檢查階段
虛擬機遇到一條new指令時,首先將去檢查這個指令的參數是否能在常量池中定位到這個類的符號引用,并且檢查這個符號引用所代表的類是否已被加載過、連接過和初始化過。如果沒有則必須先執行相應的類加載過程。
1.2、分配內存
類加載檢查通過后,JVM將為新創建對象分配內存。對象所需的內存大小在類加載完成后便可以確定。所以,為對象分配內存空間相當于把確定大小的內存從Java堆中劃分出來。分配方式有"指針碰撞"和"空閑列表"兩種,選擇哪種分配方式由Java堆是否規整決定,而Java堆是否規整又由所采用的垃圾收集器是否具有壓縮整理功能所決定(標記-清楚不規整,標記-整理和復制都是規整的)。
1. 指針碰撞
適用場合:堆內存規整(沒有內存碎片)
原理:用過的內存放一邊,沒用過的內存放一邊,中間有一個分界值指針,分配內存時只需要向著沒用過的內存方向將該指針移動對象確定內存大小位置即可
GC收集器:Serial、ParNew
2. 空閑列表
適用場合:堆內存不規整
原理:JVM會維護一個列表,該列表會記錄哪些內存塊是可用的,分配內存時找一塊足夠大的內存區域劃分給對象實例,最后更新內存列表
GC收集器:CMS
內存分配的并發問題
堆內存是線程共享的,所以在創建對象分配內存的時候一個重要的問題就是線程安全問題。JVM采用以下兩種方式保證線程安全。
1. CAS+失敗重試:CAS是樂觀鎖的一種實現方式。樂觀鎖,就是每次假設沒有沖突,不加鎖地去執行操作。JVM采用CAS+失敗重試的方式保證更新操作的原子性。
2. 線程本地分配緩存(TLAB):JVM為每個線程預先在Eden區分配一小塊區域-線程本地分配緩存(TLAB),JVM在給線程中的對象分配內存時,首先在 TLAB中劃分內存。當對象大于TLAB剩余內存或者TLAB用盡時,JVM會再采用CAS+失敗重試當方式分配內存。
1.3、初始化零值
內存分配完成后,虛擬機需要將分配到的內存空間都初始化零值(不包括頭對象)。這一過程保證了Java的實例對象在JVM中可以不賦初始值就直接使用,程序能訪問這些字段的數據類型所對應的零值。
1.4、設置頭對象
初始化零值后,JVM要對對象信息進行必要的設置(與類的關聯關系、關聯類的元數據信息、對象的哈希碼、對象的GC分代年齡),這些信息存在對象的對象頭中。
1.4.1、頭對象的形式
JVM中對象頭的方式有以下兩種(以32位JVM為例)
1.4.1.1、普通對象
|--------------------------------------------------------------|
| Object Header (64 bits) |
|------------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------------|-------------------------|
1.4.1.2、數組對象
|------------------------------------------------------------------------------|
| Object Header (96 bits) |
|--------------------------------|-----------------------|----------------------|
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
|--------------------------------|-----------------------|----------------------|
1.4.2、頭對象組成
1. Mark Word
2. 指向類的指針
3. 數組長度(只有數組對象才有)
1.4.2.1、Mark Word
Mark Word記錄了對象和鎖有關的信息,當這個對象被synchronized關鍵字當成同步鎖時,圍繞這個鎖的一系列操作都和Mark Word有關。
Mark Word在32位JVM中的長度是32bit,在64位JVM中長度是64bit。
JVM一般是這樣使用鎖和Mark Word的:
1,當沒有被當成鎖時,這就是一個普通的對象,Mark Word記錄對象的HashCode,鎖標志位是01,是否偏向鎖那一位是0。
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中,Mark Word存儲示意:
其中無鎖和偏向鎖的鎖標志位都是01,只是在前面的1bit區分了這是無鎖狀態還是偏向鎖狀態。
JDK1.6以后的版本在處理同步鎖時存在鎖升級的概念,JVM對于同步鎖的處理是從偏向鎖開始的,隨著競爭越來越激烈,處理方式從偏向鎖升級到輕量級鎖,最終升級到重量級鎖。
1.4.2.2、指向類的指針
該指針在32位JVM中的長度是32bit,在64位JVM中長度是64bit。
Java對象的類數據保存在方法區。
1.4.2.3、數組長度
只有數組對象保存了這部分數據。
該數據在32位和64位JVM中長度都是32bit。
1.5、執行init方法
上述過程執行完,對象實例便已經創建出來了,但是所有的成員變量(屬性字段)還都是零值。所以在new命令執行完之后,還需要執行<init>方法對類的成員變量進行初始化,至此一個類的實例對象就創建完成了。
上述就是小編為大家分享的java類對象底層是如何創建的了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。