前言
小編最近涉獵比較廣泛,有點貪多嚼不爛的感覺,但是每天有學不完的知識(有的是溫故而知新),很充實也很滿足,重拾技術的熱情。今天為大家帶來JVM的類加載機制,首先,是否和小編曾經一樣,不知道學習JVM有和作用,好像除了面試的時候通通背一遍做幾題關于JVM類加載的面試題,之後進入公司後都是寫一些業務代碼。這是大家深有感觸的。那小編為什麼要學習JVM,這邊羅列了兩點;第一:為了搞清楚Java代碼運作的本質。第二:真正遇到JVM相關問題時能夠解決,并有相應的能力進行調優。
JVM有幾大子產品組成:
- 類加載的子系統
- 記憶體模型
- 執行引擎
- 垃圾收集器(GC)
- JIT(熱點代碼緩存)
這篇部落格跟其他大部分部落格可能不太一樣,希望大家有耐心可以看完。接下來咱們講一下最開始的,就是類的加載機制,進入正題。
klass模型
何為klass模型,在說明這個問題之前,大家有沒有想過我們編寫的java類在JVM中是以何種形式存在的,其實他就是以klass模型存在的,klass模型即java類在jvm中存在的形式。下圖為klass模型類的繼承結構:
這邊小編說明一下
從繼承關系上也能看出來,類的元資訊是存儲在原空間的
普通的Java類在JVM中對應的是instanceKlass類的執行個體,再來說下它的三個字類
Java中的數組不是靜态資料類型,是動态資料類型,即是運作期生成的,Java數組的元資訊用ArrayKlass的子類來表示:
- InstanceMirrorKlass:用于表示java.lang.Class,Java代碼中擷取到的Class對象,實際上就是這個C++類的執行個體,存儲在堆區,學名鏡像類
- InstanceRefKlass:用于表示java/lang/ref/Reference類的子類
- InstanceClassLoaderKlass:用于周遊某個加載器加載的類
- TypeArrayKlass:用于表示基本類型的數組
- 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
啟動後的頁面如下圖
這邊jdk安裝方式不同,可能啟動連接配接出現這個報錯。(至少小編是這樣的,如果沒有請自行跳過)
解決方法:去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);
}
}
啟動後檢視程序
可以看到剛才運作的代碼的PID是5428,我們在HSDB裡面去關聯程序:
File > Attach to Hotspot process
進入之後記憶體指針位址:
Tools > Class Browser
LearnJVM 對象的位址是0x00000007c0060828,然後我們去看這個對象的詳細資訊:
Tools > Inspector
上面是其中一個方法。
還有一個方法是根據類初始化後尋找
然後再使用記憶體指針位址:
Tools >Inspector
使用HSDB來檢視java類對應的klass類(數組)
數組查詢的方式,和非數組的第二種方式類似,如圖所示
首先是基本數組類型的類型對應:
引用數組類型的對應類型
其實還有一個是main方法裡面的string數組的參數也在裡面。
你看小編沒騙大家吧,通過工具類證明了。當然大家如果懂c++,看源碼會更加清晰。
Hotspot源碼的話,大家可以自己搭建openjdk8單步調試環境(加油!)
類加載過程
類的生命周期由七個階段組成,分别是加載,驗證,準備,解析,初始化,使用,解除安裝。
類的加載說的是前5個階段,一般我們都會說加載,連接配接,初始化,因為連接配接包括了驗證準備解析三個階段。
加載
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、接口方法解析
常量池分為,靜态常量池,運作時常量池和字元串常量池,這邊解析說的常量池為靜态常量池,符号引用為靜态常量池的索引。轉換為直接引用,就是改寫成記憶體位址資訊。
下圖為解析完成後的直接引用檢視:
初始化
執行靜态代碼塊,完成靜态變量的指派 指令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添加預設實作
接口中的變量必須執行個體化
接口中沒有靜态代碼塊、普通變量、普通代碼塊、構造函數
總結
今天關于類加載就先到這兒了,小編這兒有一些題目,感興趣的夥伴可以聯系我,将代碼給大家,看大家能做對幾個(說不準是猜對的啊,小編這兒有個奇怪的死鎖問題,關鍵是檢測工具檢測不到,認為不算死鎖【感覺逆天了】)。咱們再接再厲加油!