# ClassLoader ###### tags: `Java` ## **Class載入過程**  1. Load : 查找.class 檔案 2. **Verify** :驗證class的正確性、magic code、JDK version、MD5、父類、介面、final類是否被繼承、參考類型是否符合實例類型、全限定名稱是否正確、類別可見性是否正確 **Prepare** : 初始化類變量,在此階段類變量會被給予初值 Byte : 0 ; Char : '\u0000' ; Short : 0 ; Int : 0 ; Float 0.0f ; Double : 0.0d ; Long : 0L ; Boolean : false ; ref-type : null ; 但若是final修飾的類變量則會直接賦予該值 **Resolve** : 在常量池中尋找類別、介面、方法..的符號引用轉換成直接引用 1.類/介面解析 : 當ref變數不是一個陣列時,會將ref變數全限定名稱傳給載入類別的類載入器載入,若是一個陣列則不需要載入該類別而會在heap開一個代表該類的連續記憶體空間。解析完後還需要進行符號引用的驗證 2.屬性 : 如果該類本身包含這個變量的引用或值則會加載並返回,若沒有則查找父類或介面,都沒有則拋錯誤 3.靜態方法 : 在類方法表中發現class_index中索引是一個介面則返回錯誤,如果找不到則往父類找,如果找到是抽象方法則拋錯,找不到也拋錯。 4.介面方法 : 在介面方法中發現class_index中索引是一個類則拋錯誤 因為介面方法表和類方法表是有區別在常量池有 "Constant_Methodref_info"、"Constant_Interfaceref_info"兩種型別 解析過程同靜態方法 3. Initialize :在此階段最主要做的事情就是執行 < clinit >() 此方法會將所有類變量賦予在編寫時的值。< clinit >() 是在編譯時期產生的因此他已經包含在class中。< clinit >() 中包含了所有靜態區塊、靜態變量並且保證順序性。 另外< clinit >() 和構造函數不同不須顯示調用並且虛擬機保證從父類的 < clinit >()最先執行。 ## **ClassLoader**  JVM 三大ClassLoader 1. BootStrap ClassLoader 最頂層ClassLoader , 為c++編寫主要負責核心類庫載入 可透過系統參數查詢加載了哪些資源 System.getProperty("sun.boot.class.path") 2. Ext ClassLoader 父加載器為BootStrap ClassLoader 主要負責JAVA_HOME下的jre/lib/ext子目錄內的資源 可透過系統參數查詢加載了哪些資源 System.getProperty("java.ext.dirs") 3. Appication ClassLoader 父加載器為Ext ClassLoader 負責加載classpath下的資源 為自定義加載器的預設父加載器 可透過 -classpath or -cp 指定路徑 可透過系統參數查詢加載了哪些資源 System.getProperty("java.class.path") 4. 自定義加載器 父委託機制 當一個加載器執行了loadclass後該加載器並不會馬上載入 而是委託給父加載器直到最頂層後才會開始依次向下載入 加載過程 : 詳見ClassLoader.loadclass(name) 特殊情況繞過加載器 假設某class存在application classloader的加載範圍內則 1.將class loader的父加載器掛到ext classloader上 2.將父classloader指定為null 類別加載器 1. 命名空間 每一個類加載器"實例"都有各自的命名空間 命名空間的組成為該加載器及其所有父加載器組成的 Ex.1 Classloader classloader = this.getClass().getClassLoader(); Class a = classloader.loadclass("com.test.Test"); Class b = classloader.loadclass("com.test.Test"); => a == b 同一個類加載器實例分別載入相同的類別,會得到相同的類別實例 Ex.2 CustomClassLoader c1 = new CustomClassLoader(); CustomClassLoader c2 = new CustomClassLoader(); Class a = c1.loadclass("com.test.Test"); Class b = c2.loadclass("com.test.Test"); => a != b 不同的類加載器實例分別載入相同的類別,會得到不同的類別實例  2. 執行時包 在寫程式時會將類別用包進行隔離,目的是為了封裝並且防止同名衝突 包名和類名組了成全限定名稱 在JVM執行時class會有一個執行時包是由類加載器的命名空間和類的全限定名稱組成 ex. BootstrapClassLoader.java.lang.String BootstrapClassLoader.ExtClassLoader.ApplicationClassLoader.java.lang.HackString String和HackString是由不同的類加載器載入各自有不同的運行時包,因此HackString是無法取得String的公開方法及變數的 3. 初始類加載器 4. 卸載 JVM起動過程中會載入很多類運行時也是 在JVM起動時指定 -verbose:class可以看到運行期間到底載入了多少類 物件實體在沒有任何引用的情況下會在垃圾回收線程被GC回收掉 而class實體只有在滿足下列三點情況下財會被回收:實體卸載 1.該類所有實例都已經被GC 2.加載該類的類載入器實體被回收 3.該類的class實例沒有在其他地方被引用 可能應用 : 熱部屬 **ThreadContextClassLoader** 預設為程式進入點該class的類載入器 若是沒有特別使用則不會有任何地方使用他 取得方式 Thread.currentThread().getContextClassLoader(); 要處理的問題 解決父委託機制的缺點 提供載入Service Provider Interface的方法 打破父委託機制的方法 以第一個圖來說根據父委託機制 Custom Classloader1 可以取得以上的ClassLoader所有加載的類但在Applicatoin Classloader以上的加載器則無法取得Custom Classloader1所加載的類,於是當上層的加載器需要透過下層加載器載入類(ex:JDBC)時 會透過Thread內所記錄的ContextClassLoader載入下層所引入的類 JDBC機制 JDK定義出Driver Interface 而各家廠商(Mysql Mssql Oracle)實作該介面 透過ContextClassLoader載入各家廠商實作 載入方式 1.實作Driver的靜態區塊會對DriverManager註冊自己的實例 2.DriverManager透過ContextClassLoader載入Driver 使用上注意 ContextClassLoader 在許多JDK的class中可能被用到 因此如果有設定需求也必須在使用完之後復歸否則可能造成不可預期的錯誤 ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); // call some API that uses reflection without taking ClassLoader param } finally { Thread.currentThread().setContextClassLoader(originalClassLoader); } 可能應用? 透過切換Thread Pool 區分各個Thread可使用的類別
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up