JVM-类加载原理
寫在前面
我們知道我們編寫的java代碼,會經過編譯器編譯成字節碼文件(class文件),再把字節碼文件裝載到JVM中,映射到各個內存區域中,我們的程序就可以在內存中運行了。那么字節碼文件是怎樣裝載到JVM中的呢?中間經過了哪些步驟?常說的雙親委派模式又是怎么回事?本文主要搞清楚這些問題。
類裝載流程
1、加載
加載是類裝載的第一步,首先通過class文件的路徑讀取到二進制流,解析二進制流將里面數據結構(類型、常量等)載入到方法區,在java堆中生成對應的java.lang.Class對象用類封裝類在方法區中的數據結構。
2.1、驗證
驗證的主要目的就是判斷class文件的合法性,比如class文件一定是以0xCAFEBABE開頭的,另外對版本號也會做驗證,例如如果使用java1.8編譯后的class文件要再java1.6虛擬機上運行,因為版本問題就會驗證不通過。除此之外還會對元數據、字節碼進行驗證,機構驗證,語義驗證,字節碼驗證。
2.2、準備
準備過程就是分配內存,給類的一些字段設置初始值,例如:public static int v=1;
這段代碼在準備階段v的值就會被初始化為0,只有到后面類初始化階段時才會被設置為1。
但是對于static final(常量),在準備階段就會被設置成指定的值,例如:public static final? int v=1;
這段代碼在準備階段v的值就是1。
對于int類型的靜態變量分配4個字節的內存空間,并且默認值為0。long類型的靜態變量分配8個字節的內存空間,默認值為0。布爾(false)
2.3、解析
解析過程就是將符號引用替換為直接引用,例如某個類繼承java.lang.object,原來的符號引用記錄的是“java.lang.object”這個符號,憑借這個符號并不能找到java.lang.object這個對象在哪里?而直接引用就是要找到java.lang.object所在的內存地址,建立直接引用關系,這樣就方便查詢到具體對象。或者A類中調用了B類對象的fun()方法,那么b.fun()就是符號引用,會轉換為B類fun()的具體地址。
3、初始化
初始化過程,主要包括執行類構造方法、static變量賦值語句,staic{}語句塊,需要注意的是如果一個子類進行初始化,那么它會事先初始化其父類,保證父類在子類之前被初始化。所以其實在java中初始化一個類,那么必然是先初始化java.lang.Object,因為所有的java類都繼承自java.lang.Object。
觸發類初始化的場景
1.創建類的實例。
2:訪問類或者接口的靜態變量,或者給靜態變量賦值。
3.調用類的靜態方法。(只有當出現訪問的靜態變量或者靜態方法確實在當前類或者接口中定義時,才可以認為是對類或者接口的主動使用)
4.反射(如 Class.forName("com.a.b.c.Test"))
5.初始化一個類的子類。
6.Java虛擬機啟動時被標記為啟動類的類
系統中的ClassLoader
BootStrap Classloader (啟動ClassLoader) 只加載 jre/lib/下面的類
Extension ClassLoader (擴展ClassLoader)只加載 jre/lib/ext/下面的類
App ClassLoader(應用 ClassLoader) 加載環境變量Path
Custom ClassLoader(自定義ClassLoader)
每個ClassLoader都有另外一個ClassLoader作為父ClassLoader,BootStrap Classloader除外,它沒有父Classloader。ClassLoader加載機制如下:
?
類的加載
?類的加載并不需要等到某個類被“首次主動使用”時再加載它。
JVM規范允許類加載器在預料某個類將要被使用時就預先加載它,如果預先加載過程中遇到了.class文件缺失或者存在錯誤,類加載器必須在程序主動使用該類時報告錯誤(LinkageError錯誤),如果這個類一直沒有被程序使用,那么類加載器就一直不會報告這個錯誤。
調用ClassLoader類的loadClass方法加載一個類,并不是對一個類的主動使用,并不會導致類的初始化(僅僅是類的加載)。
?靜態常量
編譯時靜態常量 static final a = 6/3; //不會觸發類的初始化
允許時靜態常量 static final a = Math.random(100); // 會觸發類的初始化
?
轉載于:https://www.cnblogs.com/chihirotan/p/11516276.html
總結
- 上一篇: yum安装php7.2
- 下一篇: 面向关系数据库的智能索引调优方法