- 面試官:創(chuàng)建java對象有哪幾種方式华临?
- 小白:new、clone端考、反射雅潭、反序列化。
- 面試官:那你知道 new 一個對象的時候却特,JVM 做了哪些事嗎扶供?說說具體的過程。
- 小白:就是 new 一下核偿,然后在堆分配內(nèi)存 ……
- 面試官:你先回家等通知吧诚欠。
所以你知道 new 一個對象的時候做了哪些事顽染,具體過程是怎樣的嗎漾岳?其實主要經(jīng)歷了如下過程:
- 檢查類是否加載過;
- 分配內(nèi)存粉寞;
1. 檢查類是否加載過:
在之前 JVM 系列文章中說過尼荆,類通過 ClassLoader 生成一個模板,這個模板放在方法區(qū)(1.7的實現(xiàn)叫永久代唧垦,1.8的實現(xiàn)叫元空間)捅儒,這個模板就包含了類的結(jié)構(gòu)信息,包括方法、屬性巧还、常量池等鞭莽。 new 一個對象的時候,首先會檢查是否已經(jīng)生成了類的模板麸祷。如果有澎怒,就直接拿來用;如果沒有阶牍,就先加載類生成類的模板喷面。
2. 分配內(nèi)存:
經(jīng)過了第一步之后,就要為對象分配內(nèi)存走孽,這個過程在堆中進行惧辈。分配內(nèi)存有兩種方式,一個叫指針碰撞磕瓷,一個叫空閑列表盒齿。至于具體用哪種方式,取決于堆內(nèi)存是否連續(xù)困食。之前的 JVM 垃圾回收文章中說到過县昂,如果采用標記清除算法進行垃圾回收,就會產(chǎn)生內(nèi)存碎片陷舅,如果是用標記整理倒彰,就不會有內(nèi)存碎片。如果沒有內(nèi)存碎片莱睁,就用指針碰撞待讳,否則就用空閑列表。
指針碰撞:用過的內(nèi)存放一邊仰剿,沒用過的放另一邊创淡,中間有個指針作為分界線,采用該方式為對象分配內(nèi)存時南吮,只需要將指針向未用過的內(nèi)存方向移動對象所需內(nèi)存大小即可琳彩。
空閑列表:有內(nèi)存碎片的時候,虛擬機會維護一個列表部凑,列表記錄了哪些位置的內(nèi)存是可用的露乏,給對象分配內(nèi)存時就會找一塊夠大的內(nèi)存去分配,然后更新列表記錄涂邀。
3. 初始化零值:
什么叫初始化零值瘟仿?你有沒有發(fā)現(xiàn),我們在類中定義的成員變量比勉,是不需要賦初始值也可以使用的劳较,而局部變量驹止,沒進行初始化去使用就會報錯。這是為什么呢观蜗?就是因為在對象的創(chuàng)建過程中有“初始化零值”這一步臊恋。比如定義了一個 int 類型的成員變量,拿來用的時候墓捻,默認值是0捞镰,而不是null,這就是初始化零值毙替。
4. 設(shè)置對象頭:
什么是對象頭岸售?JVM 在存儲對象時,增加的一些標記字段厂画,用于增強對象的功能凸丸,這就是對象頭。java 對象頭包括:
Mark word:存儲對象自身的一些數(shù)據(jù)袱院,比如 hashCode屎慢,gc 分代年齡等;
Klass pointer:存儲指針忽洛,JVM 通過這個指針來確定該對象是哪個類的實例腻惠;
array length:如果對象是數(shù)組,那么還會存儲數(shù)組的長度欲虚。
5. 執(zhí)行init方法:
經(jīng)過上面四個步驟集灌,一個新的 java 對象就已經(jīng)產(chǎn)生了,最后就是執(zhí)行 init 方法复哆,讓對象按照程序猿的意愿欣喧,進行初始化。什么叫按照程序猿的意愿初始化梯找?就是你 new 對象的時候傳了哪些參數(shù)唆阿,屬性值是什么。
內(nèi)存分配的過程中锈锤,如何保證線程安全呢驯鳖?JVM 采用 TLAB + CAS 的方式保證線程安全。
TLAB 就是為每個線程預先在伊甸園區(qū)分配一塊內(nèi)存久免,JVM 要給對象分配內(nèi)存的時候浅辙,首先會用 TLAB,即預先分配的這塊內(nèi)存妄壶,如果不夠摔握,就用 CAS 一直重試寄狼。