1. 程式人生 > >JVM系列【3】Class檔案載入過程

JVM系列【3】Class檔案載入過程

## JVM系列筆記目錄 > - 虛擬機器的基礎概念 > - class檔案結構 > - class檔案載入過程 > - jvm記憶體模型 > - JVM常用指令 > - GC與調優 ## Class檔案載入過程 JVM載入Class檔案主要分3個過程:Loading 、Linking、Initialzing ### 1.Loading Loading的過程就是通過類載入器將`.class`檔案載入到jvm記憶體中過程。需要理解雙親委派機制、類載入器ClassLoader,載入過程如下。 ![file](https://img2020.cnblogs.com/other/1295651/202010/1295651-20201010180008200-1812774875.jpg) #### ClassLoader 不同的類載入器載入範圍不一樣,以Java8中的為例。 > - BootClassLoader 載入範圍`sun.boot.class.paht` > - ExtClassLoader 載入範圍`java.ext.dirs` > - AppClassLoader 載入範圍`java.class.path` > - CustomClassLoader 可自定義載入範圍 前三個載入器來自JDK的Launcher類,三個ClassLoader作為Launcher的內部類,感興趣可以檢視下原始碼。 ![file](https://img2020.cnblogs.com/other/1295651/202010/1295651-20201010180008550-1839035063.jpg) 開發者也可以自定義的ClassLoader,自定義記載範圍。 #### 雙親委派機制 自底向上檢查該類是否已經載入,parent方向;自頂向下進行類的實際查詢和載入,child方向。 類的載入遵循雙親委派機制,主要是出於安全的考慮。雙親委派機制是如何實現的,下面原始碼會解釋。 ![file](https://img2020.cnblogs.com/other/1295651/202010/1295651-20201010180008913-99731138.jpg) **注意:**雙親委派中存在所謂的父載入器並不是載入器的載入器,只是翻譯的問題,別混淆了類的繼承概念。 #### ClassLoader原始碼 ![file](https://img2020.cnblogs.com/other/1295651/202010/1295651-20201010180009278-318444682.jpg) ClassLoader原始碼中比較重要的一個函式是`loadClass()`,執行過程是:`findLoadedClass()`->`parrent.loadClass()`->`findClass()`,第一步是自底向上查詢是否已經載入,第二步是自頂向下查詢載入類。這裡就規定或是說實現了雙親委派機制。詳細見`ClassLoader`的原始碼。 #### 自定義ClassLoader 如何自定義ClassLoader?可以繼承ClassLoader類,重新自己的`findClass()`,在裡面呼叫`defineClass()`來實現自定義載入特定範圍的類。 #### 如何打破雙親委派機制,哪種情形下打破過? 從上面的ClassLoader原始碼中大概能看出是如何實現了雙親委派機制的,從這入手可以通過2種方式打破該機制: > 1. super(parent)指定parent會打破該機制 > 2. 自定義ClassLoader重寫`loadClass()`也可以打破 何時打破過?雙親委派機制並不是不能打破,某些特殊場景下也會選擇打破該機制。 > 1. JDK 1.2之前,自定義ClassLoader必須重寫`loadClass()`,打破過。 > 2. 執行緒ThreadContextClassLoader可以實現基礎類呼叫實現類程式碼,通過thread.setContextClassLoader指定。 > 3. 熱啟動熱部署,如tomcat都有自己模組指定的classloader,可以載入同一類庫的不同版本。 #### Class執行方式 Class執行方式分為3種:解釋執行、編譯執行、混合執行,各有優缺點,可通過引數指定。 - 1.解釋執行:使用bytecode intepreter 直譯器解釋執行,該模式啟動很快,執行稍慢,可通過`-Xint`引數指定該模式。 - 2.編譯執行:使用 Just in time Complier JIT編譯器編譯執行,該模式執行很快,編譯很慢,可通過`-Xcomp`引數指定該模式。 - 3.混合執行:預設的模式,直譯器+熱點程式碼編譯,開始解釋執行,啟動較快,對熱點程式碼進行實時監測和編譯成原生代碼執行,可通過`-Xmixed`引數指定該模式。 > 熱點程式碼監測:多次被呼叫的方法用方法計數器,多次被呼叫的迴圈用迴圈計數器,可通過引數`-XX:CompileThreshold = 10000`指定觸發JIT編譯的閾值。 ### 2.Linking Linking連結的過程分3個階段:Vertification、Preparation、Resolution。 - Vertification: 驗證Class檔案是否符合JVM規定。 - Preparation:給靜態成員變數賦預設值 - Resolution:將類、方法、屬性等符號引用解釋為直接引用;常量池中的各種符號引用解釋為指標、偏移量等記憶體地址的直接引用 ### 3. Initializing 呼叫初始化程式碼`clint`,給靜態成員變數賦初始值。 這裡可以瞭解下必須初始化的5種情況: > - `new getstatic putstatic invokestatic`指令,訪問final變數除外 > - `java.lang.reflect`對類進行反射呼叫時 > - 初始化子類的時候,父類必須初始化 > - 虛擬機器啟動時,被執行的主類必須初始化 > - 動態語言支援`java.lang.invoke.MethodHandler`解釋的結果為`REF_getstatic REF_putstatic REF_invokestatic`的方法控制代碼時,該類必須初始化。 ### 4.總結思考 設計模式中單例模式的雙重檢查的實現,`INSTANCE`是否需要加`valatile`? ```java public class Mgr06 { // 是否需要加volatile? private static volatile Mgr06 INSTANCE; private Mgr06() { } public static Mgr06 getInstance() { if (INSTANCE == null) { //雙重檢查 synchronized (Mgr06.class) { if(INSTANCE == null) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } // new 了物件,不為null,但未完成變數的初始化複製,物件處於半初始化狀 態,其它執行緒有可能取到半初始化的物件。 INSTANCE = new Mgr06(); } } } return INSTANCE; } } ``` 個人認為是需要加的。思考方向, `class`檔案load到記憶體,給靜態變數賦預設值,再賦初始值,new 物件的時候,首先要申請記憶體空間,然後給成員變數賦預設值,接下來給成員變數賦初始值,這個過程中物件有可能處於半初始化狀態,多執行緒併發下別的執行緒有可能取到半初始化的物件,加volatile可保證執行緒的可見性。 > 知識分享,轉載請註明出處。學無先後,達者為先!