JVM類載入過程
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初始化開始