天天看點

【詳細圖解】一個 Java 對象是如何被建立的?

程式員日常最喜歡的自我調侃就是,沒有對象很簡單啊,自己寫代碼 new 一個不就好了,說到這裡,我們的驕傲就已經盡數展現了。

【詳細圖解】一個 Java 對象是如何被建立的?

不過話說回來,一個簡簡單單的 new 背後,JVM 到底為我們做了些什麼呢, Java 的對象具體是怎麼被建立的呢,一起來看看吧。

比如我們想要建立一個 Person 對象:

Person person = new Person();
           

在 JVM 中的加載流程如下圖所示:

【詳細圖解】一個 Java 對象是如何被建立的?

Step1:類加載檢查

JVM 虛拟機遇到⼀條 new 指令時,⾸先會檢查在常量池中是否可以定位到這個類的符号引⽤(比如 org.simple.Person),并且檢查這個符号引⽤代表的類是否已被加載、解析和初始化過。

如果沒有,那必須先執⾏相應的類加載過程。

Step2:配置設定記憶體

當類加載完成後,就可以确定對象所需的記憶體大小了。

為對象配置設定空間的任務實際上就等同于把⼀塊确定大小的記憶體從 Java 堆中劃分出來。

Step3:初始化零值

記憶體配置設定完成後,虛拟機需要将配置設定到的記憶體空間都初始化為零值(不包括對象頭)。

這⼀步操作保證了對象的執行個體字段在 Java 代碼中可以不賦初始值就直接使⽤,也就是說,程式能通路到這些字段的資料類型所對應的零值。

Step4:設定對象頭

初始化零值完成之後,虛拟機要對對象進⾏必要的設定,例如這個對象是哪個類的執行個體、如何才能找到類的中繼資料資訊、對象的哈希碼、對象的 GC 分代年齡等資訊,這些資訊都存放在對象頭中。

另外,根據虛拟機目前運⾏狀态的不同,如是否啟⽤偏向鎖等,對象頭會有不同的設定⽅式。

Step5:執行 init 方法

此時從虛拟機的視⻆來看,⼀個新的對象已經産⽣了,但從 Java 程式的視⻆來看,對象建立才剛開始, ⽅法還沒有執⾏,所有的字段都還為零。

是以⼀般來說,執⾏ new 指令之後會接着執⾏初始化方法,把對象按照程式員的意願進⾏初始化,這樣⼀個真正可⽤的對象才算完全建立成功。

以上就是對象的建立過程了,其實還有很多細節是我們需要進一步了解的,比如,Java 是如何為一個新的對象配置設定記憶體的呢?

記憶體配置設定⽅式有 “指針碰撞” 和 “空閑清單” 兩種,選擇哪⼀種方式進行配置設定,主要取決于 Java 堆記憶體是否規整,比如 "标記-整理" 算法、複制算法就屬于規整類算法,而 "标記-清除" 則不屬于。

1. 指針碰撞法

假設 Java 堆中記憶體是規整的,已配置設定的記憶體和空閑記憶體分别在不同的一側,通過一個指針作為分界點,需要配置設定記憶體時,僅僅需要把指針往空閑的一端移動與對象大小相等的距離。

2. 空閑清單法

如果 Java 堆的記憶體不是規整的,已配置設定的記憶體和空閑記憶體互相交錯。那麼 JVM 通過維護一個清單來記錄可用的記憶體塊資訊。

當配置設定操作發生時,從清單中找到一個足夠大的記憶體塊配置設定給對象執行個體,并更新清單上的記錄。

此外,在建立對象的時候還有⼀個很重要的問題,就是線程安全。

在實際開發過程中,建立對象是很頻繁的事情,是以作為虛拟機必須要保證線程安全。

通常采⽤兩種⽅式來保證線程安全:

CAS + 失敗重試: CAS 是樂觀鎖的⼀種實作⽅式,虛拟機采⽤ CAS + 失敗重試的方式保證更新操作的原⼦性。

TLAB(線程本地配置設定緩存區): 預先為每⼀個線程在 Eden 區配置設定⼀塊記憶體,JVM 在給線程中的對象配置設定記憶體時,⾸先在 TLAB 配置設定,當對象⼤于 TLAB 中的剩餘記憶體或 TLAB 的記憶體已⽤盡時,再采⽤上述的 CAS 進⾏記憶體配置設定。

【詳細圖解】一個 Java 對象是如何被建立的?

本篇部落格參考了 JavaGuide 面試題中的内容,感謝原作者,送上花花~

原始連結:https://github.com/Snailclimb/JavaGuide