JVM的類加載過程總體來說分為三個階段:
1、類的加載
類的加載過程通過一個類的全限定名獲取定義此類的二進制字節流,然后將這個字節流所代表的靜態數據結構轉化為方法區的運行時數據結構,最后在內存中生成一個代表這個類的java.lang.Class對象,作為方法去這個類的各種數據的訪問入口。
加載.class文件的方式:
- 從本地系統中直接加載
- 從zip壓縮包中讀取,成為日后jar、war格式的基礎
- 運行時計算,比如動態代理
- 由其他文件生成,如jsp
- 從加密文件獲取,比如使用自定義類加載器加載加密文件,來保證Class文件不會被反編譯
2、鏈接階段
a)驗證
確保class文件的字節流中包含的信息符合虛擬機要求,保證被加載類的正確性,不會危害到虛擬機自身的安全,主要包括文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證。
b)准備
為類變量分配內存並且設置類變量的默認初始值,即零值。這里不包含用final修飾的static,因為final在編譯的時候就會分配了,准備階段會顯式初始化,比如說在這里final static int a = 10;就不會把a設為0值了,而會直接分配內存,放入方法區中,並設初始值10。這里不會為實例變量分配初始化,類變量會分配在方法區中,實例變量會隨着對象一起分配到堆中。
c)解析
通常解析操作往往伴隨着JVM在初始化后再執行,將符號引用轉換成直接引用,
3、初始化過程
初始化階段就是執行類構造器方法<clint>()的過程。
此方法不需要定義,是javac編譯器自動收集類中的所有類變量的賦值動作和靜態代碼塊中的語句合並而來。

如果該類具有父類,必須保證先執行父類的<clint>()再執行子類的<clint>(),並且一個類的<clint>()在多線程下被枷鎖。比如下方例子,a線程搶到執行權后,在初始化Test類的時候一直進入死循環,導致類始終無法初始化完畢,所以此時a線程無法初始完類,b線程也一直等待。
public class Main
{
public static void main(String[] args)
{
Runnable runnable = () -> {
System.out.println(Thread.currentThread().getName() + "初始化開始");
Test t = new Test();
System.out.println(Thread.currentThread().getName() + "初始化結束");
};
Thread a = new Thread(runnable);
Thread b = new Thread(runnable);
a.start();
b.start();
}
}
class Test {
static {
if (true) {
System.out.println("初始化當前類");
while (true) {
}
}
}
}
輸出:
Thread-0初始化開始
初始化當前類
Thread-1初始化開始
