《深入分析JavaWeb技術內幕》之 6- 深入分析ClassLoader工作機制
深入分析ClassLoader工作機制
Java 源程式(.java 檔案)在經過 Java 編譯器編譯之後就被轉換成 Java 位元組程式碼(.class 檔案)。類載入器負責讀取Java位元組程式碼,並轉換成 java.lang.Class類的一個例項。每個這樣的例項用來表示一個Java 類。通過此例項的 newInstance()方法就可以創建出該類的一個物件。
Java 中的類載入器大致可以分成兩類:
一類是系統提供的:
- 引導類載入器(bootstrapclass loader):它用來載入 Java 的核心庫,是用原生程式碼而不是java來實現的,並不繼承自java.lang.ClassLoader,除此之外基本上所有的類載入器都是java.lang.ClassLoader類的一個例項。
- 擴充套件類載入器(extensionsclass loader):它用來載入 Java 的擴充套件庫。Java 虛擬機器的實現會提供一個擴充套件庫目錄(一般為%JRE_HOME%/lib/ext)。該類載入器在此目錄裡面查詢並載入 Java 類。
- 系統類載入器(systemclass loader或 App class loader):它根據當前Java 應用的類路徑(CLASSPATH)來載入 Java 類。一般來說,Java 應用的類都是由它來完成載入的。可以通過 ClassLoader.getSystemClassLoader() 來獲取它。
另外一類則是由 Java 應用開發人員編寫的:
- 開發人員可以通過繼承java.lang.ClassLoader 類的方式實現自己的類載入器,以滿足一些特殊的需求
classLoader是類載入器,負責將Class載入到JVM中,還有一個作用是審查每個類由誰載入,它是一種父優先的等級載入機制。
還有一個任務是,將class位元組碼重新解析成JVM統一要求的物件格式。
載入類過程:
假如loader2的parent為loader1,loader1的parent為system class loader。假設loader2被要求裝載類MyClass,在parent delegation模型下,loader2首先請求loader1代為裝載,loader1再請求系統類裝載器去裝載MyClass。若系統裝載器能成功裝載,則將MyClass所對應的Class物件的reference返回給loader1,loader1再將reference返回給loader2,從而成功將類MyClass裝載進虛擬機器。若系統類裝載器不能裝載MyClass,loader1會嘗試裝載MyClass,若loader1也不能成功裝載,loader2會嘗試裝載。若所有的parent及loader2本身都不能裝載,則裝載失敗。
6.1 ClassLoader類結構分析
defineClass(byte[], int, int): 將byte位元組流解析成JVM能識別的class物件。
findClass(String):
loadClass(String): 獲取class物件。
resolveClass(Class<?>):
ClassLoader是抽象類,有很多子類,如果我們要實現自己的ClassLoader,一般會繼承URLClassLoader, 做修改就好了。
6.2 ClassLoader的等級載入機制
JVM提供三層ClassLoader,這三層ClassLoader可以分為兩種型別
(1)Bootstrap ClassLoader: 載入JVM自身工作需要的類,完全由JVM自己控制,別人訪問不到這個類,沒有高一級的載入器,也沒有子載入器。
(2)ExtClassLoader:
(3)AppClassLoader:
如果我們要實現自己的類載入器,不管是直接實現抽象類ClassLoader,還是繼承URLClassLoader,或其他子類,它的父載入器都是AppClassLoader。
ExtClassLoader和AppClassLoader都繼承了URLClassLoader類。
ClassLoader的等級載入機制
(1)JVM平臺提供三層的ClassLoader,這三層ClassLoader可以分為兩類,分別是服務JVM自身的,和服務廣大普通類的。分別是:
-
<1>BootstrapClassLoader:主要載入JVM自身工作所需要的類,該ClassLoader沒有父類載入器和子類載入器
-
<2>ExtClassLoader:這個類載入器同樣是JVM自身的一部分,但是不是由JVM實現,主要用於載入System.getProperty(“java.ext.dirs”)目錄地下的類,如本機的值“D:\java\jdk7\jre\lib\ext;C:\Windows\Sun\Java\lib\ext”
-
<3>AppClassLoader:載入System.getProperty("java.class.path")(注意了在ide中執行程式時,該值通常是該專案的classes資料夾)中的類。所有的自定義類載入器不管直接實現ClassLoader,是繼承自URLClassLoader或其子類,其父載入器(注意:父載入器與父類的分別)都是AppClassLoader,因為不管呼叫哪個父類的構造器,最終都將呼叫getSystemClassLoader作為父載入器,而該方法返回的正是AppClassLoader。(當應用程式中沒有其他自定義的classLoader,那麼除了System.getProperty(“java.ext.dirs”)目錄中的類,其他類都由AppClassLoader載入)
6.3 如何載入class檔案
三個步驟:
1 找到.class檔案並把這個檔案包含的位元組碼載入到記憶體。
2 位元組碼校驗,Class類資料結構分析以及相應的記憶體分配和最後的符號表連結
3 類中靜態屬性和初始化賦值,已經靜態塊的執行等。
6.3.1 載入位元組碼到記憶體
URLClassLoader如何實現findclass()的,URLCclassLoader中通過一個URLClassPath類幫助取得要載入的class檔案位元組流,而這個URLClasspath定義了到哪裡去找這個
class檔案,如果找到,再讀它的byte位元組流,通過definClass()來建立類物件。根據路徑的不同(是檔案還是jar包)來建立FIleLoader或JarLoader.
6.3.2 驗證與解析
位元組碼驗證,驗證格式正確,行為正確。
類準備,這個階段準備代表每個類中定義的欄位,方法和實現介面所必須的資料結構。
解析,裝入類所引用的其他所有類。
6.3.3 初始化class物件
執行靜態初始化器,靜態欄位會為初始化為預設值。
6.4 常見載入類錯誤分析
6.4.1 ClassNotFoundException
顯示載入類三種方法:
Class.forName();
ClassLoader.loadClass()
ClassLoader.findSystemClass()
6.4.2 NoClassDefFoundError
類可能沒加包名。
6.4.3 UnsatisfiedLinkError
可能是誤刪了lib檔案
6.4.4 ClassCastExcetption
JVM做型別轉換的檢查規則:
- 普通物件,物件必須是目標類的例項或是目標類子類的例項。如果目標類是介面,那麼可以把它當做實現了該介面的一個子類。
- 對於陣列型別,目標型別必須是陣列型別或java.lang,Object, java.lang,Clonble, java.io.serializable
如果不滿足上面的規則,JVM就會報錯:
要想避免這個錯誤 有兩種方式:
容器型別中顯示地指明容器物件型別,
通過instanceof檢查是不是目標型別,然後在強轉。
6.4.5 ExceptinInInitializerError
6.5 常用的ClassLoader分析
這裡分析了servlet的ClassLoader
6.6 如何實現自己的ClassLoader
為什麼要實現自己的classLoader
在自己的路徑下查詢自定義的class檔案,不一定在classPath下面。
對我們的類做特殊處理,比如加密傳輸類在網路之間。這就需要在載入到JVM之前先解密。
可以定義類的實現機制,實現熱部署。
6.6.1 載入自定義路徑下的class檔案
6.6.2 載入自定義格式的class檔案
6.7 實現類的熱部署
JVM在載入類之前會檢查類是否已經被載入過,也就是要呼叫findLoadedClass()方法檢視是否能返回類例項
JVM表示兩個類是否是同一個類有兩個條件:
一是看類的完整類名是否一樣。
二是看載入這個類的ClassLoader是否是同一個(是指ClassLoader例項是否是同一個)。
實現熱部署:可以用ClassLoader的兩個例項載入同名的類。
6.8 java應不應該動態載入類
JAVA修改一個類,必須重啟JVM。