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