JVM - 写了这么多年代码,你还不知道new对象背后的逻辑?
文章目錄
- 對象創(chuàng)建流程
- 【類加載檢查】
- 【分配內(nèi)存】
- 內(nèi)存劃分的兩種方式
- 解決分配內(nèi)存并發(fā)問題的兩種方式
- 【初始化】
- 【設(shè)置對象頭】
- 對象的組成
- 對象頭的兩部分組成
- 【執(zhí)行init方法】
- 總結(jié)一下
對象創(chuàng)建流程
我們知道JVM三大組成部分: 類加載子系統(tǒng)、運行時數(shù)據(jù)區(qū) 、字節(jié)碼執(zhí)行引擎。
要想new 一個對象,肯定是要繞不開JVM的機(jī)制。
【類加載檢查】
JVM啟動的時候并不是將所有的類都初始化,所以當(dāng)碰到一個new指令時,JVM首先會去檢查這個類有沒有被加載,具體就是去常量池中看是否有這個類的符號引用,并檢查這個符號引用代表的類是否已經(jīng)被加載、解析和初始化過 。 若沒有這必須經(jīng)歷【類加載子系統(tǒng)】的歷練 (加載–校驗–準(zhǔn)備–解析–初始化)
JVM-白話聊一聊JVM類加載和雙親委派機(jī)制源碼解析
【分配內(nèi)存】
類加載校驗通過后 ,是不是該分配內(nèi)存了呢?
是的, 接下來JVM將會為這個新生的對象分給內(nèi)存,因為這個新生對象所需要內(nèi)存大小在類加載完之后便可以完全確定,對象放哪里呢? 通常都是放在堆中,所以所謂的分配內(nèi)存實際上就是從Java堆中劃分出一塊固定大小的內(nèi)存給這個新生對象。
雖然很簡單的一件事情,但是要考慮的地方可不少
內(nèi)存劃分的兩種方式
JVM提供了2中劃分內(nèi)存的方法
- 指針碰撞(Bump the Pointer) 【默認(rèn)方式】
如果堆中的內(nèi)存是絕對規(guī)整的,大家都按順序排放,分配過內(nèi)存的對象那個在一邊,未使用的內(nèi)存在另外一邊 ,分界線使用指針來維護(hù)。因為新生對象所需要內(nèi)存大小在類加載完之后便可以完全確定,所以僅需要將指針移動對象大小的位置即可。
當(dāng)然了這是一種理想的情況,JVM里還有GC,會標(biāo)記清除等等
-
空閑列表(Free List)
如果堆內(nèi)存中的內(nèi)存并不是規(guī)整的,分配的內(nèi)存和未分配的內(nèi)存糅雜在一起, 如果還用上面的指針碰撞的方式, 如果移動的可用內(nèi)存無法容納這個對象,放不下啊? 咋弄? 繼續(xù)碰么?
顯然效率很低。 所以JVM采用了另外一種方式,JVM維護(hù)了一個列表,記錄了堆中的可用內(nèi)存,那么分配內(nèi)存的時候就從JVM維護(hù)的列表中找一個足夠容納這個對象的內(nèi)存區(qū)域給它,并更新列表記錄。
解決分配內(nèi)存并發(fā)問題的兩種方式
第二個問題 并發(fā)問題如何解決呢?
在并發(fā)的情況下,可能出現(xiàn)JVM正在給對象A分配內(nèi)存,但是指針還沒來得及修改,對象B又使用了A的內(nèi)存空間的情況。
為了解決這個問題,JVM采取了
- CAS (compare and swap)
簡而言之就是JVM采用【 CAS+失敗重試 】保證更新操作的原子性 。
- 本地線程分配緩沖 (Thread Local Allocation Buffer , TLAB)
把內(nèi)存分配的動作按照線程劃分在不同的空間之中進(jìn)行,即每個線程在Java堆中預(yù)先分配一小塊內(nèi)存
通過-XX:+/- UseTLAB參數(shù)來設(shè)定虛擬機(jī)是否使用TLAB。
JDK8中默認(rèn)開啟XX:+UseTLAB ,默認(rèn)值eden區(qū)域的1%,當(dāng)然了也可以通過-XX:TLABSize 指定TLAB大小 。 一般不建議修改。
如果TLAB還放不下,那就走CAS了…
不管怎么分配,目的只是為了更好的回收內(nèi)存或者更快的分配對象
【初始化】
內(nèi)存分配完成后,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對象頭).
如果使用TLAB,這一工作過程也可以提前至TLAB分配時進(jìn)行。
這一步操作保證了對象的實例字段在Java代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數(shù)據(jù)類型所對應(yīng)的默認(rèn)值 (比如 int 默認(rèn)0 , String 默認(rèn)null , boolean 默認(rèn)false等等)
【設(shè)置對象頭】
初始化默認(rèn)值以后,JVM要對對象進(jìn)行必要的設(shè)置,例如這個對象是哪個類的實例、如何才能找到類的元數(shù)據(jù)信息、對象的哈希碼、對象的GC分代年齡等信息。這些信息存放在對象的對象頭Object Header之中。
這部分?jǐn)?shù)據(jù)的長度在32位和64位的虛擬機(jī)中分別為32個和64個bits,官方稱它為“Mark Word”。
對象的組成
在HotSpot虛擬機(jī)中,對象在內(nèi)存中存儲的布局可以分為3塊區(qū)域:對象頭(Header)、 實例數(shù)據(jù)(Instance Data)和和對齊填充(Padding) 。
對象頭的兩部分組成
HotSpot虛擬機(jī)的對象頭包括兩部分信息
- 第一部分用于存儲對象自身的運行時數(shù)據(jù), 如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程ID、偏向時 間戳等。
32位操作系統(tǒng)為例
- 對象頭的另外一部分是類型指針,即對象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過這個指針來確定這個對象是哪個類的實例。
如下所示
【執(zhí)行init方法】
執(zhí)行方法,即對象按照程序員的意愿進(jìn)行初始化。對應(yīng)到語言層面上講,就是為屬性賦值(注意,這與上面的賦零值不同,這是由程序員賦的值) 和執(zhí)行構(gòu)造方法。
IDEA安裝jclasslib插件可以查看
這里的init實際上是C++調(diào)用的,相對于面向開發(fā)人員 就是 new Artisan() ,并執(zhí)行Artisan默認(rèn)的構(gòu)造函數(shù)。
總結(jié)一下
總結(jié)
以上是生活随笔為你收集整理的JVM - 写了这么多年代码,你还不知道new对象背后的逻辑?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM - 应用JVM核心参数推荐设置
- 下一篇: JVM - 剖析Java对象头Objec