類的加載機制

ClassLoader

Java程序並不是一個原生的可執行文件,而是由許多獨立的類文件組成,每一個文件對應一個Java類。此外,這些類文件並非立即全部裝入內存的,而是根據程序需要裝入內存。ClassLoader專門負責類文件裝入到內存。
數組類的 Class 對象不是由類加載器創建的,而是由 Java 運行時根據需要自動創建。數組類的類加載器由 Class.getClassLoader() 返回,該加載器與其元素類型的類加載器是相同的;如果該元素類型是基本類型,則該數組類沒有類加載器。
這裏寫圖片描述

從上圖我們就可以看出類加載器之間的父子關係(注意不是類的集繼承關係)。
Bootstrap ClassLoader:BootStrap 是最頂層的類加載器,它是由C++編寫並且已經內嵌到JVM中了,主要用來讀取Java的核心類庫JRE/lib/rt.jar。
Extension ClassLoader:負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包
App ClassLoader(System ClassLoader):負責記載classpath中指定的jar包及目錄中class
Custom ClassLoader:屬於應用程序根據自身需要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現ClassLoader

JVM有四種類型的類加載器,即java是如何區分一個類該由哪個類加載器來完成呢?
在這裏java採用了委託模型機制,這個機制簡單來講,就是“類裝載器有載入類的需求時,會先請示其Parent使用其搜索路徑幫忙載入,如果Parent 找不到,那麼才由自己依照自己的搜索路徑搜索類”。具體流程如下:
1、”A類加載器”加載類時,先判斷該類是否已經加載過了;
2、如果還未被加載,則首先委託其”A類加載器”的”父類加載器”去加載該類,這是一個向上不斷搜索的過程,當A類所有的”父類加載器”(包括bootstrap classloader)都沒有加載該類,則回到發起者”A類加載器”去加載;
3、如果還加載不了,則拋出ClassNotFoundException。

ClassLoader抽象類的幾個關鍵方法如下:

loadClass()
此方法負責加載指定名字的類,ClassLoader的實現方法爲先從已經加載的類中尋找,如沒有則繼續從parent ClassLoader中尋找,如仍然沒找到,則從System ClassLoader中尋找,最後再調用findClass方法來尋找,如要改變類的加載順序,則可覆蓋此方法

findLoadedClass()
此方法負責從當前ClassLoader實例對象的緩存中尋找已加載的類,調用的爲native的方法。

findClass()
此方法直接拋出ClassNotFoundException,因此需要通過覆蓋loadClass或此方法來以自定義的方式加載相應的類。

findSystemClass()
此方法負責從SystemClassLoader中尋找類,如未找到,則繼續從Bootstrap ClassLoader中尋找,如仍然爲找到,則返回null。

defineClass()
此方法負責將二進制的字節碼轉換爲Class對象

resolveClass()
此方法負責完成Class對象的鏈接,如已鏈接過,則會直接返回。

Class.forName()與ClassLoader.loadClass()
這兩方法都可以通過一個給定的類名去定位和加載這個類名對應的 java.long.Class 類對象,區別如下:
1. 初始化
Class.forName()會對類初始化,而loadClass()只會裝載或鏈接。ClassLoader.loadClass()加載的類對象是在第一次被調用時才進行初始化的。
可以利用上述的差異。比如,要加載一個靜態初始化開銷很大的類,就可以選擇提前加載該類(以確保它在classpath下),但不進行初始化,直到第一次使用該類的域或方法時才進行初始化
2. 類加載器可能不同
Class.forName(String)方法(只有一個參數),使用調用者的類加載器來加載, 也就是用加載了調用forName方法的代碼的那個類加載器。當然,它也有個重載的方法,可以指定加載器。相應的,ClassLoader.loadClass()方法是一個實例方法(非靜態方法)調用時需要自己指定類加載器,那麼這個類加載器就可能是也可能不是加載調用代碼的類加載器(調用代碼類加載器通getClassLoader0()獲得)

類的加載過程

類的加載要經過三步:裝載(Load),鏈接(Link),初始化(Initializ)。其中鏈接又可分爲校驗(Verify),準備(Prepare),解析(Resolve)三步。

加載
ClassLoader就是用來裝載的。通過指定的className,找到二進制碼,生成Class實例,放到JVM中。
在加載階段,虛擬機需要完成以下三件事(虛擬機規範對這三件事的要求並不具體,因此虛擬機實現與具體應用的靈活度相當大):
1,通過一個類的全限定名獲取定義這個類的二進制流
可以從jar包、ear包、war包中獲取,可以從網絡中獲取(Applet),可以運行時生成(動態代理),可以通過其它文件生成(Jsp)等。
2,將這個字節流代表的靜態存儲結構轉化爲方法區的運行時數據結構。
3,在Java堆中生成一個代表這個類的java.lang.Class對象,作爲方法區這些數據的訪問入口。

鏈接
鏈接就是把load進來的class合併到JVM的運行時狀態中。可以把它分成三個主要階段:
校驗:對二進制字節碼的格式進行校驗,以確保格式正確、行爲正確。這一階段主要是爲了確保Class文件的字節流中包含的信息複合當前虛擬機的要求,並且不會危害虛擬機自身的安全。主要驗證過程包括:
1.文件格式驗證:驗證字節流文件是否符合Class文件格式的規範,並且能被當前虛擬機正確的處理。
2.元數據驗證:是對字節碼描述的信息進行語義分析,以保證其描述的信息符合Java語言的規範。
3.字節碼驗證:主要是進行數據流和控制流的分析,保證被校驗類的方法在運行時不會危害虛擬機。
4.符號引用驗證:符號引用驗證發生在虛擬機將符號引用轉化爲直接引用的時候,這個轉化動作將在解析階段中發生。
準備:準備類中定義的字段、方法和實現接口所必需的數據結構。比如會爲類中的靜態變量賦默認值(int等:0, reference:null, char:’\u0000’)。
準備階段正式爲類變量分配內存並設置初始值。這裏的初始值並不是初始化的值,而是數據類型的默認零值。這裏提到的類變量是被static修飾的變量,而不是實例變量。關於準備階段爲類變量設置零值的唯一例外就是當這個類變量同時也被final修飾,那麼在編譯時,就會直接爲這個常量賦上目標值。
如:
pirvate static int size = 12;
那麼在這個階段,size的值爲0,而不是12。 final修飾的類變量將會賦值成真實的值。
解析:裝入類所引用的其他所有類,虛擬機將常量池中的符號引用替換爲直接引用。可以用許多方式引用類:超類、接口、字段、方法簽名、方法中使用的本地變量。

初始化
在準備階段,變量已經賦過一次系統要求的初始值,在初始化階段,則是根據程序員通過程序的主觀計劃區初始化類變量和其他資源。
類初始化前,它的直接父類一定要先初始化(遞歸),但它實現的接口不需要先被初始化。類似的,接口在初始化前,父接口不需要先初始化。

有且只有4種情況必須立即對類進行初始化:
1,遇到new(使用new關鍵字實例化對象)、getstatic(獲取一個類的靜態字段,final修飾符修飾的靜態字段除外)、putstatic(設置一個類的靜態字段,final修飾符修飾的靜態字段除外)和invokestatic(調用一個類的靜態方法)這4條字節碼指令時,如果類還沒有初始化,則必須首先對其初始化
2,使用java.lang.reflect包中的方法對類進行反射調用時,如果類還沒有初始化,則必須首先對其初始化
3,當初始化一個類時,如果其父類還沒有初始化,則必須首先初始化其父類
4,當虛擬機啓動時,需要指定一個主類(main方法所在的類),虛擬機會首選初始化這個主類
除了上面這4種方式,所有引用類的方式都不會觸發初始化,稱爲被動引用。如:通過子類引用父類的靜態字段,不會導致子類初始化;通過數組定義來引用類,不會觸發此類的初始化;引用類的靜態常量不會觸發定義常量的類的初始化,因爲常量在編譯階段已經被放到常量池中了。

感謝

本文非原創,經作者同意後轉載!
感謝作者冰河winner

http://blog.csdn.net/u012152619/article/details/46964775
類的加載機制

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章