天天看點

深入了解JVM之深入Hotspot了解類加載機制前言klass模型類加載過程關于類初始化先後問題總結

前言

小編最近涉獵比較廣泛,有點貪多嚼不爛的感覺,但是每天有學不完的知識(有的是溫故而知新),很充實也很滿足,重拾技術的熱情。今天為大家帶來JVM的類加載機制,首先,是否和小編曾經一樣,不知道學習JVM有和作用,好像除了面試的時候通通背一遍做幾題關于JVM類加載的面試題,之後進入公司後都是寫一些業務代碼。這是大家深有感觸的。那小編為什麼要學習JVM,這邊羅列了兩點;第一:為了搞清楚Java代碼運作的本質。第二:真正遇到JVM相關問題時能夠解決,并有相應的能力進行調優。

JVM有幾大子產品組成:

  1. 類加載的子系統
  2. 記憶體模型
  3. 執行引擎
  4. 垃圾收集器(GC)
  5. JIT(熱點代碼緩存)

這篇部落格跟其他大部分部落格可能不太一樣,希望大家有耐心可以看完。接下來咱們講一下最開始的,就是類的加載機制,進入正題。

klass模型

何為klass模型,在說明這個問題之前,大家有沒有想過我們編寫的java類在JVM中是以何種形式存在的,其實他就是以klass模型存在的,klass模型即java類在jvm中存在的形式。下圖為klass模型類的繼承結構:

深入了解JVM之深入Hotspot了解類加載機制前言klass模型類加載過程關于類初始化先後問題總結

這邊小編說明一下

從繼承關系上也能看出來,類的元資訊是存儲在原空間的

普通的Java類在JVM中對應的是instanceKlass類的執行個體,再來說下它的三個字類

  1. InstanceMirrorKlass:用于表示java.lang.Class,Java代碼中擷取到的Class對象,實際上就是這個C++類的執行個體,存儲在堆區,學名鏡像類
  2. InstanceRefKlass:用于表示java/lang/ref/Reference類的子類
  3. InstanceClassLoaderKlass:用于周遊某個加載器加載的類
Java中的數組不是靜态資料類型,是動态資料類型,即是運作期生成的,Java數組的元資訊用ArrayKlass的子類來表示:
  1. TypeArrayKlass:用于表示基本類型的數組
  2. ObjArrayKlass:用于表示引用類型的數組

小編這邊把klazz的了解也分享一下:klazz分為兩類

第一種:數組類

instanceKlass:java普通類在jvm中對應的c++類 在方法區中(類的元資訊:如通路權限,類的屬性,類的方法)

InstanceMirrorKlass:對應的class對象 在堆中

第二種:非數組類

TypeArrayKlass(基本類型數組):boolean,byte,char,int,short,long,float,double

ObjArrayKlass:引用類型

使用HSDB來檢視java類對應的klass類(非數組)

HSDB的簡單使用:HSDB(Hotspot Debugger),JDK自帶的工具,用于檢視JVM運作時的狀态。

HSDB位于安裝目錄下與bin同一檔案夾的lib裡面,小編這裡是這樣的前面路徑是各位的安裝目錄\Java\jdk1.8.0_212\lib裡面,接下來啟動HSDB:

java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB

啟動後的頁面如下圖

深入了解JVM之深入Hotspot了解類加載機制前言klass模型類加載過程關于類初始化先後問題總結

這邊jdk安裝方式不同,可能啟動連接配接出現這個報錯。(至少小編是這樣的,如果沒有請自行跳過)

深入了解JVM之深入Hotspot了解類加載機制前言klass模型類加載過程關于類初始化先後問題總結

解決方法:去jdk裡面找了一下有沒有這個dll檔案,還真有,我就給copy到外部jre裡面對應的目錄裡面了,接着啟動HSDB就沒有問題啦。

簡單示例代碼:

public class LearnJVM {
    private int a =2;
    private static int b =2;
    private final int c =2;
    private static final int d =1;

    public static void main(String[] args) {
    LearnJVM learnJVM = new LearnJVM();
        int[] intArr = new int[1];
        LearnJVM[] learnJVMS = new LearnJVM[1];
        Class<LearnJVM> learnJVMClass = LearnJVM.class;
        while (true);
    }
}
           

啟動後檢視程序

深入了解JVM之深入Hotspot了解類加載機制前言klass模型類加載過程關于類初始化先後問題總結

可以看到剛才運作的代碼的PID是5428,我們在HSDB裡面去關聯程序:

File > Attach to Hotspot process

進入之後記憶體指針位址:

Tools > Class Browser

深入了解JVM之深入Hotspot了解類加載機制前言klass模型類加載過程關于類初始化先後問題總結

LearnJVM 對象的位址是0x00000007c0060828,然後我們去看這個對象的詳細資訊:

Tools > Inspector

深入了解JVM之深入Hotspot了解類加載機制前言klass模型類加載過程關于類初始化先後問題總結

上面是其中一個方法。

還有一個方法是根據類初始化後尋找

深入了解JVM之深入Hotspot了解類加載機制前言klass模型類加載過程關于類初始化先後問題總結

然後再使用記憶體指針位址:

Tools >Inspector

深入了解JVM之深入Hotspot了解類加載機制前言klass模型類加載過程關于類初始化先後問題總結

使用HSDB來檢視java類對應的klass類(數組)

數組查詢的方式,和非數組的第二種方式類似,如圖所示

深入了解JVM之深入Hotspot了解類加載機制前言klass模型類加載過程關于類初始化先後問題總結

首先是基本數組類型的類型對應:

深入了解JVM之深入Hotspot了解類加載機制前言klass模型類加載過程關于類初始化先後問題總結

引用數組類型的對應類型

深入了解JVM之深入Hotspot了解類加載機制前言klass模型類加載過程關于類初始化先後問題總結

其實還有一個是main方法裡面的string數組的參數也在裡面。

你看小編沒騙大家吧,通過工具類證明了。當然大家如果懂c++,看源碼會更加清晰。

Hotspot源碼的話,大家可以自己搭建openjdk8單步調試環境(加油!)

類加載過程

類的生命周期由七個階段組成,分别是加載,驗證,準備,解析,初始化,使用,解除安裝。

類的加載說的是前5個階段,一般我們都會說加載,連接配接,初始化,因為連接配接包括了驗證準備解析三個階段。

深入了解JVM之深入Hotspot了解類加載機制前言klass模型類加載過程關于類初始化先後問題總結

加載

1、通過類的全限定名擷取存儲該類的class檔案

2、解析成運作時資料,即instanceKlass執行個體,存放在方法區

3、在堆區生成該類的Class對象,即instanceMirrorKlass執行個體

從哪擷取class檔案,由下面這些地方等可以擷取

1、從壓縮包中讀取,如jar、war

2、從網絡中擷取,如Web Applet

3、動态生成,如動态代理、CGLIB

4、由其他檔案生成,如JSP

5、從資料庫讀取

6、從加密檔案中讀取

驗證

1、檔案格式驗證

2、中繼資料驗證

3、位元組碼驗證

4、符号引用驗證

準備

為靜态變量配置設定記憶體、賦初值。

執行個體變量是在建立對象的時候完成指派的,沒有賦初值一說。

如果被final修飾,在編譯的時候會給屬性添加ConstantValue屬性,準備階段直接完成指派,即沒有賦初值這一步

問題:準備階段為什麼要賦初值?

明明可以初始化的時候可以指派,準備階段賦初值的意義在哪裡,其實是這樣的,靜态變量假如在準備階段沒有賦初值的話,在InstanceMirrorKlass中他的靜态變量就沒寫進去,在初始化階段,這個靜态變量就沒有了,會導緻後續報錯。

解析

将常量池中的符号引用轉為直接引用

解析後的資訊存儲在ConstantPoolCache類執行個體中

1、類或接口的解析

2、字段解析

3、方法解析

4、接口方法解析

常量池分為,靜态常量池,運作時常量池和字元串常量池,這邊解析說的常量池為靜态常量池,符号引用為靜态常量池的索引。轉換為直接引用,就是改寫成記憶體位址資訊。

下圖為解析完成後的直接引用檢視:

深入了解JVM之深入Hotspot了解類加載機制前言klass模型類加載過程關于類初始化先後問題總結
深入了解JVM之深入Hotspot了解類加載機制前言klass模型類加載過程關于類初始化先後問題總結

初始化

執行靜态代碼塊,完成靜态變量的指派 指令clinit;

  • 1、如果沒有靜态屬性、靜态代碼段,生成的位元組碼檔案中就沒有clinit方法塊
  • 2、final修飾,不會在clinit方法塊中展現
  • 3、一個位元組碼檔案隻有一個clinit方法塊
  • 4、clinit方法塊中生成的代碼順序與Java代碼的順序是一緻的。這個會影響程式最終結果。

初始化主要是在主動使用時,主動使用主要有以下幾種方式:

1、new、getstatic、putstatic、invokestatic

2、反射

3、初始化一個類的子類會去加載其父類

4、啟動類(main函數所在類)

5、當使用jdk1.7動态語言支援時,如果一個java.lang.invoke.MethodHandle執行個體最後的解析結果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且這個方法句柄所對應的類沒有進行初始化,則需要先出觸發其初始化

關于類初始化先後問題

普通類:

靜态變量

靜态代碼塊

普通變量

普通代碼塊

構造函數

繼承的子類:

父類靜态變量

父類靜态代碼塊

子類靜态變量

子類靜态代碼塊

父類普通變量

父類普通代碼塊

父類構造函數

子類普通變量

子類普通代碼塊

子類構造函數

抽象的實作子類: 接口 - 抽線類 - 實作類

接口靜态變量

抽象類靜态變量

抽象類靜态代碼塊

實作類靜态變量

實習類靜态代碼塊

抽象類普通變量

抽象類普通代碼塊

抽象類構造函數

實作類普通變量

實作類普通代碼塊

實作類構造函數

接口注意:

聲明的變量都是靜态變量并且是final的,是以子類無法修改,并且是固定值不會因為執行個體而變化

接口中能有靜态方法,不能有普通方法,普通方法需要用defalut添加預設實作

接口中的變量必須執行個體化

接口中沒有靜态代碼塊、普通變量、普通代碼塊、構造函數

總結

今天關于類加載就先到這兒了,小編這兒有一些題目,感興趣的夥伴可以聯系我,将代碼給大家,看大家能做對幾個(說不準是猜對的啊,小編這兒有個奇怪的死鎖問題,關鍵是檢測工具檢測不到,認為不算死鎖【感覺逆天了】)。咱們再接再厲加油!