JVM - 寫了這麼多年代碼,你還不知道new對象背後的邏輯?

在這裏插入圖片描述

對象創建流程

在這裏插入圖片描述

我們知道JVM三大組成部分: 類加載子系統、運行時數據區 、字節碼執行引擎。

要想new 一個對象,肯定是要繞不開JVM的機制。

【類加載檢查】

JVM啓動的時候並不是將所有的類都初始化,所以當碰到一個new指令時,JVM首先會去檢查這個類有沒有被加載,具體就是去常量池中看是否有這個類的符號引用,並檢查這個符號引用代表的類是否已經被加載、解析和初始化過 。 若沒有這必須經歷【類加載子系統】的歷練 (加載–校驗–準備–解析–初始化)

JVM-白話聊一聊JVM類加載和雙親委派機制源碼解析


【分配內存】

類加載校驗通過後 ,是不是該分配內存了呢?

是的, 接下來JVM將會爲這個新生的對象分給內存,因爲這個新生對象所需要內存大小在類加載完之後便可以完全確定,對象放哪裏呢? 通常都是放在堆中,所以所謂的分配內存實際上就是從Java堆中劃分出一塊固定大小的內存給這個新生對象。

雖然很簡單的一件事情,但是要考慮的地方可不少

  1. 採取何種方式分配內存
  2. 併發問題

內存劃分的兩種方式

JVM提供了2中劃分內存的方法

  • 指針碰撞(Bump the Pointer) 【默認方式

如果堆中的內存是絕對規整的,大家都按順序排放,分配過內存的對象那個在一邊,未使用的內存在另外一邊 ,分界線使用指針來維護。因爲新生對象所需要內存大小在類加載完之後便可以完全確定,所以僅需要將指針移動對象大小的位置即可。

當然了這是一種理想的情況,JVM裏還有GC,會標記清除等等

  • 空閒列表(Free List)

    如果堆內存中的內存並不是規整的,分配的內存和未分配的內存糅雜在一起, 如果還用上面的指針碰撞的方式, 如果移動的可用內存無法容納這個對象,放不下啊? 咋弄? 繼續碰麼?

顯然效率很低。 所以JVM採用了另外一種方式,JVM維護了一個列表,記錄了堆中的可用內存,那麼分配內存的時候就從JVM維護的列表中找一個足夠容納這個對象的內存區域給它,並更新列表記錄。


解決分配內存併發問題的兩種方式

第二個問題 併發問題如何解決呢?

在併發的情況下,可能出現JVM正在給對象A分配內存,但是指針還沒來得及修改,對象B又使用了A的內存空間的情況。

爲了解決這個問題,JVM採取了

  • CAS (compare and swap)

簡而言之就是JVM採用【 CAS+失敗重試 】保證更新操作的原子性 。

  • 本地線程分配緩衝 (Thread Local Allocation Buffer , TLAB)

把內存分配的動作按照線程劃分在不同的空間之中進行,即每個線程在Java堆中預先分配一小塊內存

通過­XX:+/­ UseTLAB參數來設定虛擬機是否使用TLAB。

JDK8中默認開啓XX:+UseTLAB ,默認值eden區域的1%,當然了也可以通過-XX:TLABSize 指定TLAB大小 。 一般不建議修改。

如果TLAB還放不下,那就走CAS了…

不管怎麼分配,目的只是爲了更好的回收內存或者更快的分配對象


【初始化】

內存分配完成後,虛擬機需要將分配到的內存空間都初始化爲零值(不包括對象頭).

如果使用TLAB,這一工作過程也可以提前至TLAB分配時進行。

這一步操作保證了對象的實例字段在Java代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數據類型所對應的默認值 (比如 int 默認0 , String 默認null , boolean 默認false等等)


【設置對象頭】

初始化默認值以後,JVM要對對象進行必要的設置,例如這個對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息。這些信息存放在對象的對象頭Object Header之中。

這部分數據的長度在32位和64位的虛擬機中分別爲32個和64個bits,官方稱它爲“Mark Word”。


對象的組成

在HotSpot虛擬機中,對象在內存中存儲的佈局可以分爲3塊區域:對象頭(Header)、 實例數據(Instance Data)和和對齊填充(Padding) 。

在這裏插入圖片描述


對象頭的兩部分組成

HotSpot虛擬機的對象頭包括兩部分信息

  • 第一部分用於存儲對象自身的運行時數據, 如哈希碼(HashCode)、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時 間戳等。

32位操作系統爲例

在這裏插入圖片描述

  • 對象頭的另外一部分是類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。

如下所示
在這裏插入圖片描述


【執行init方法】

執行方法,即對象按照程序員的意願進行初始化。對應到語言層面上講,就是爲屬性賦值(注意,這與上面的賦零值不同,這是由程序員賦的值) 和執行構造方法。

IDEA安裝jclasslib插件可以查看
在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

這裏的init實際上是C++調用的,相對於面向開發人員 就是 new Artisan() ,並執行Artisan默認的構造函數。



總結一下

在這裏插入圖片描述


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章