JVM 類加載機制、對象的創建過程


 

類加載機制

類加載也叫類初始化,包括加載、連接、初始化三個步驟。
 

加載

jvm將類的.class文件讀到內存中,並創建對應的java.lang.Class對象。
 

加載由類加載器來完成,jvm提供了3種類加載器

  • Bootstrap ClassLoader :根類加載器,也叫做引導類加載器,負責加載jdk中的核心類。根類加載器是用C++寫的,不繼承java.lang.ClassLoader。
  • Extension ClassLoader :擴展類加載器,負責加載jdk中的擴展類(非核心類),擴展類加載器是用java寫的,繼承自ClassLoader。
  • System ClassLoader:系統類加載器,負責加載第三方jar包、我們自己寫的類。系統類加載器是用java寫的,繼承自ClassLoader。

可以繼承ClassLoader類來實現自定義的類加載器。

加載順序:根類加載器、擴展類加載器、系統類加載器、自定義的類加載器

 

jvm的類加載機制有3種,這3種機制共同作用,一起完成類的加載

  • 全盤負責:使用類加載器加載一個類時,這個類所依賴(引用)的類也由該類加載器加載
  • 父類委託:先使用父類的類加載器來加載,如果父類的類加載器加載不了,再使用類本身的類加載器來加載
  • 緩存機制:jvm會緩存加載過的類的class對象,要使用某個類時,先在緩存中搜索是否有對應的class對象,有就直接使用、不再加載,沒有才加載。運行程序後,如果修改了某個類,需要重啓jvm纔會生效,不然使用的是之前緩存的class對象。

 

類加載的大致過程
在這裏插入圖片描述
類加載是按需加載,使用該類時才加載,類加載生成的class對象用全類名唯一標識。

因爲存在緩存機制,一個類只加載一次,且一個類在內存中最多隻有一個class對象。
 

爲什麼要使用雙親委派機制去加載類?

避免重複加載同一個.class文件

 

連接

加載獲得的class對象是二進制數據,連接是把class對象放到jre中(把class對象連接到jre),jre即java運行時環境。
 

連接分爲3個階段

  • 校驗:檢驗被加載的類的內部結構是否正確、和其他類是否協調一致
  • 準備:爲類的成員變量分配內存,設置默認的初始值,比如int賦爲0,引用型賦爲null
  • 解析:將class對象中的符號引用替換爲直接引用

class對象是加載時生成的,加載時class對象中的成員變量還沒有分配內存,不知道該成員變量在內存中的地址,只能用符號引用暫時表示該成員變量在內存中的地址。連接的準備階段給成員變量分配內存,之後就可以用直接引用(內存地址)替換掉符號引用。

 

初始化

初始化是對類進行初始化,不是對類的對象進行初始化。類初始化會執行static代碼塊、初始化static靜態成員。

 

類加載|初始化的時機(jvm什麼時候加載|初始化一個類)

  • 創建類的實例。包括通過new來創建、通過反射來創建、通過反序列化來創建。
  • 通過類名調用類的靜態成員
  • 初始化這個類的子類之前
  • 通過java.exe運行主類時,JVM會先初始化這個主類,再執行這個主類

 

類加載方式

主要有兩種

  • 隱式加載:使用new創建對象,隱式調用類加載器,加載對應的類到 JVM 中,這是最常見的類加載方式
  • 顯式加載:使用 loadClass()、forName() 等方法顯式加載需要的類,獲取到 Class 對象後,調用 Class 對象的 newInstance() 方法來創建類的實例

 

兩種類加載方式的區別

  • 隱式加載能直接獲取類的實例,顯式加載需要調用 Class 對象的 newInstance() 方法來生成類的實例
  • 隱式加載能使用帶參的構造函數,而Class對象的 newInstance() 不能傳入參數,如果要使用帶參的構造函數,可以通過反射獲取到該類帶參的構造方法,通過反射調用帶參的構造方法來創建實例

 

loadClass() 、 forName() 的區別

loadClass() 只執行類加載的第一步:加載,後續操作均未進行;Class.forName() 執行了類加載的整個過程(3步)。

 

對象的創建過程

1、先在常量池中定位該類的符號引用,判斷是否已有該類的class對象,如果沒有則先加載該類。加載時會執行static代碼塊、初始化靜態成員
 

2、在堆中分配內存空間。有2種分配方式

  • 指針碰撞:適用於連續的內存空間,包括開闢一塊內存、移動指針兩個步驟
  • 空閒列表:適用於瑣碎的內存空間,包括開闢一塊內存、修改空閒列表兩個步驟

都是2個步驟,不具有原子性,可能出現併發問題,jvm採用CAS算法實現樂觀鎖,搭配失敗重試來保證內存分配的成功率。
 

3、初始化分配的內存空間。分配內存空間時是按成員變量的類型進行分配的,此時初始化成員變量爲默認值,比如int型初始化爲0,引用型初始化爲null
 

4、設置對象頭的相關數據,比如GC分代年齡、對象的hashCode、鎖狀態標識、元數據信息
 

5、執行普通初始塊、構造函數

 

對象的內存佈局

在這裏插入圖片描述
1、對象頭用於存儲對象的元數據信息

  • Mark Word 部分存儲對象自身的運行時數據,比如哈希值、gc分代年齡、鎖狀態標識
  • 類型指針指向對象所屬的類的元數據,標識對象所屬的類

2、實例數據存儲的是對象本身的數據,即各成員變量的值

3、對齊填充部分只是讓實例數據佔用的內存空間是8的倍數,無實際意義

 

對象的訪問方式

對象創建之後,在java虛擬機棧中進行訪問,有2種訪問方式

  • 直接指針訪問:虛擬機棧的局部變量表中存儲對象的引用(reference類型),通過引用直接訪問對象
  • 句柄訪問:用句柄存儲對象的引用,句柄放在句柄池中,虛擬機棧的局部變量表中存儲對象的句柄,相當於二級指針。句柄池是堆中的一塊內存。

直接指針訪問效率高,但gc回收對象時效率低;句柄訪問效率低,但gc回收對象時效率高。HotSpot虛擬機採用的是直接指針訪問。

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