类加载机制--浅谈
一、定義:
? ? 類加載(Class Loading)是一種機制,他描述的是將字節碼以文件形式加載到內存再經過連接、初始化后,最終形成可以被虛擬機直接使用的Java類型地過程。
? ? Class Loading 包含了加載(Loading)、連接(Linking)、初始化(Initialization)三大部分,其中Linking又包含了三個部分:校驗(Verification)、準備? (Preparation)、解析(Resolution)。而一個類的生命周期只是在Class Loader的基礎上多了:使用(Using),卸載(Unloading)兩部分。
Class Loaders的組成:
二、加載階段
虛擬機需要完成以下3件事情:
1)通過一個類的全限定名來獲取定義此類的二進制字節流。
2)將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。
3)在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口。
注意:
? ? 1.JVM在加載數組的時候加載的僅僅是數組的類型類(例如String[] 加載器只會加載String這個類型類),而數組的創建則由JVM直接完成。
這里我們多問幾個為什么:
? ? 2. JVM為什么只加載數組的類型類:
我認為JVM這樣做的目的主要是為了節省時間,我們知道數組里面裝的都是同一種類型的元素,JVM沒必要將一個重復的內容加載多次浪費時間。
? ? 3. N維數組怎么加載:
如果是N維數組,類加載器會從最外層開始一層一層的遞歸加載,直到加載到非數組類型為止。
? ? 4. 引用類型與基本類型加載起來會不會有區別:
其實基本類型早已經在javac階段裝箱成封裝對象了,例如int會被裝箱成Integer,long裝箱成Long等等,所以是沒有區別的。
三、驗證
? ? ? 驗證是連接階段的第一步,這一階段目的是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身安全。
? ? ? 虛擬機規范對這個階段的限制和指導非常籠統,僅僅說了一句如果驗證到輸入的字節流不符合Class文件的存儲格式,就拋出一個java.lang.VerifyError異常或者其子類異常。具體應當檢查哪些方面,如何檢查,何時檢查,都沒有強制要求或明確說明,所以不同的虛擬機對驗證的實現可能會有所不同,但大致上都會完場下面四個階段的檢驗過程:文件格式驗證、元數據驗證、字節碼驗證和符號引用驗證。
? ? ? ? 是連接階段的第一步,這一階段的目的是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身的安全。但從整體上看,驗證階段大致上會完成下面4個階段的檢驗動作:文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證。
四、準備階段
? ? ? ?是正式為類變量分配內存并設置類變量初始值的階段,這些變量所使用的內存都將在方法區中進行分配。
? ? ? ?這個階段中有兩個容易產生混淆的概念需要強調一下,首先,這時候進行內存分配的僅包括類變量(被static修飾的變量),而不包括實例變量,實例變量將會在對象實例化時隨著對象一起分配在Java堆中。其次,這里所說的初始值“通常情況”下是數據類型的零值,假設一個類變量的定義為:
public static int value=123;
那變量value在準備階段過后的初始值為0而不是123,因為這時候尚未開始執行任何Java方法,而把value賦值為123的putstatic指令是程序被編譯后,存放于類構造器<clinit>()方法之中,所以把value賦值為123的動作將在初始化階段才會執行。表7-1列出了Java中所有基本數據類型的零值。
假設上面類變量value的定義變為:public static final int value=123;
編譯時Javac將會為value生成ConstantValue屬性,在準備階段虛擬機就會根據ConstantValue的設置將value賦值為123。
五、解析階段
是虛擬機將常量池內的符號引用替換為直接引用的過程
六、初始化
在連接的準備階段,類變量已賦過一次系統要求的初始值,而在初始化階段,則是根據程序員自己寫的邏輯去初始化類變量和其他資源,舉個例子如下:
public static int value1 = 5;public static int value2 = 6;static{value2 = 66;}在準備階段value1和value2都等于0;
在初始化階段value1和value2分別等于5和66;
- 所有類變量(類變量是聲明在class內,method之外,且使用static修飾的變量)初始化語句和靜態代碼塊都會在編譯時被前端編譯器放在收集器里頭,存放到一個特殊的方法中,這個方法就是<clinit>方法,即類/接口初始化方法,該方法只能在類加載的過程中由JVM調用;
- 編譯器收集的順序是由語句在源文件中出現的順序所決定的,靜態語句塊中只能訪問到定義在靜態語句塊之前的變量;
- 如果超類還沒有被初始化,那么優先對超類初始化,但在<clinit>方法內部不會顯示調用超類的<clinit>方法,由JVM負責保證一個類的<clinit>方法執行之前,它的超類<clinit>方法已經被執行。
- JVM必須確保一個類在初始化的過程中,如果是多線程需要同時初始化它,僅僅只能允許其中一個線程對其執行初始化操作,其余線程必須等待,只有在活動線程執行完對類的初始化操作之后,才會通知正在等待的其他線程。(所以可以利用靜態內部類實現線程安全的單例模式)
- 如果一個類沒有聲明任何的類變量,也沒有靜態代碼塊,那么可以沒有類<clinit>方法;
何時觸發初始化
七、系統的類加載器
| 類加載器名稱 | 加載的范圍 |
| 啟動類加載器 Bootstrap ClassLoader | 存放在<JAVA_HOME>\lib目錄中的,并且是虛擬機 識別的類庫加載到虛擬機內存中 |
| 擴展類加載器 Extension ClassLoader | 存放在<JAVA_HOME>\lib\ext目錄中的所有類庫, 開發者可以直接使用; |
| 應用程序加載器 Application ClassLoader | 加載用戶類路徑上指定的類庫,開發者可以直 接使用,一般情況下這個就是程序中默認的類 加載器; |
八、雙親委派機制
? ? ? ? 某個特定的類加載器在接到加載類的請求 時,首先將加載任務委托給父類加載器, 依次遞歸,如果父類加載器可以完成類加 載任務,就成功返回;只有父類加載器無 法完成此加載任務時,才自己去加載
? ? ? ? 雙親委派模型好處:Java類隨著它的類加載器一起具備了帶 有優先級的層次關系,保證java程序穩 定運行
九、示例代碼
public class SuperClazz {public SuperClazz(){System.out.println("我是父類構造方法");}static {System.out.println("我是父類靜態代碼塊");}public static int value = 123;public static final String STR = "hello world";public static final int WHAT = value; }public class SonClazz extends SuperClazz {public SonClazz(){System.out.println("我是子類構造方法");}static {System.out.println("我是子類靜態代碼塊");} }public class DemoTest { public static void main(String args[]){ /* System.out.println(SonClazz.value);
輸出結果:
我是父類靜態代碼塊
123
結論:對于靜態字段,只有直接定義這個字段的類,才能被初始化。
*/
/*
SuperClazz[] aa = new SuperClazz[10];
輸出結果:(什么也沒有輸出)
結論:只是聲明了一個數組形式的變量,類沒有初始化
*/
/*
System.out.println(SonClazz.STR);
輸出結果:hello world
結論:編譯期的傳播優化,因為是常量,所以就會直接編譯到了運行的類里面,就不會再去定義的類里面找了
,STR還是在常量池里面的;
*/
/**
我是父類靜態代碼塊
123
//System.out.println(SonClazz.WHAT);
} ? public class DemoTest2 {static{i=0;System.out.println(i);//這句編譯器會報錯:Cannot reference a field before it is defined(非法向前應用) }static int i=1; } public class DemoTest2 {static{i=2; // System.out.println(i); }static int i=1;public static void main(String args[]){System.out.println(i);} }輸出結果:1
?
轉載于:https://www.cnblogs.com/lys-lyy/p/10771751.html
總結
- 上一篇: Peter's smokes -poj
- 下一篇: IOS自动化打包平台