1. 程式人生 > >《深入分析JavaWeb技術內幕》之 6- 深入分析ClassLoader工作機制

《深入分析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。