java類加載器(java.lang.ClassLoader) 與 Class.forName()


引文
http://www.ibm.com/developerworks/cn/java/j-lo-classloader/

相關引文:深入剖析 Eclipse 類裝入器



熟悉此過程,有助於理解和排查  
 java.lang.ClassNotFoundException異常(方法 loadClass()拋出);
java.lang.NoClassDefFoundError異常(方法 defineClass()拋出
  java.lang.ClassCastException異常

下面首先介紹 java中類裝載器,然後再簡單分析 類裝載過程

類加載器的樹狀組織結構

Java 中的類加載器大致可以分成兩類,一類是系統提供的,另外一類則是由 Java 應用開發人員編寫的。系統提供的類加載器主要有下面三個:

  • 引導類加載器(bootstrap class loader):它用來加載 Java 的核心庫,是用原生代碼來
  • 實現的,並不繼承自 java.lang.ClassLoader
  • 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載 Java 類。
  • 系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應用的類都是由它來完成加載的。可以通過 
  • ClassLoader.getSystemClassLoader()來獲取它。

除了系統提供的類加載器以外,開發人員可以通過繼承 java.lang.ClassLoader類的方式實現自己的類加載器,以滿足一些特殊的需求。

除了引導類加載器之外,所有的類加載器都有一個父類加載器。通過 表 1中給出的 getParent()方法可以得到。對於系統提供的類加載器來說,系統類加載器的父類加載器是擴展類加載器,而擴展類加載器的父類加載器是引導類加載器;對於開發人員編寫的類加載器來說,其父類加載器是加載此類加載器 Java 類的類加載器。因爲類加載器 Java 類如同其它的 Java 類一樣,也是要由類加載器來加載的。一般來說,開發人員編寫的類加載器的父類加載器是系統類加載器。類加載器通過這種方式組織起來,形成樹狀結構。樹的根節點就是引導類加載器。圖 1中給出了一個典型的類加載器樹狀組織結構示意圖,其中的箭頭指向的是父類加載器。

圖 1. 類加載器樹狀組織結構示意圖


類裝入器首先判斷要求它裝入的類是否與過去裝入的類相同。如果相同,就返回上次返回的類(即保存在緩存中的類)。如果不是,就把裝入類的機會交給父類。這兩步遞歸地以深度優先的方式重複。如果父類返回 null(或拋出 ClassNotFoundException),那麼類裝入器會在自己的路徑中尋找類的源。

因爲父類類裝入器總是先得到裝入類的機會,所以類裝入器裝入的類最靠近根。這意味着所有核心引導類都是由引導裝入器裝入的,這就保證裝入了類(例如 java.lang.Object)的正確版本。這也可以讓類裝入器看到自己或父類或祖先裝入的類,但是不能看到子女裝入的類。

圖 1 顯示了三個標準的類裝入器及裝載路徑:

圖 1. 類裝入器委託模型


類加載器的代理模式


類加載器在嘗試自己去查找某個類的字節代碼並定義它時,會先代理給其父類加載器,由父類加載器先去嘗試加載這個類,依次類推。在介紹代理模式背後的動機之前,首先需要說明一下 Java 虛擬機是如何判定兩個 Java 類是相同的。Java 虛擬機不僅要看類的全名是否相同,還要看加載此類的類加載器是否一樣。只有兩者都相同的情況,才認爲兩個類是相同的。即便是同樣的字節代碼,被不同的類加載器加載之後所得到的類,也是不同的






類加載器與 Web 容器()

Tomcat類加載機制理解參考文章:
對於運行在 Java EE™容器中的 Web 應用來說,類加載器的實現方式與一般的 Java 應用有所不同。不同的 Web 容器的實現方式也會有所不同。以 Apache Tomcat 來說,每個 Web 應用都有一個對應的類加載器實例。該類加載器也使用代理模式,所不同的是它是首先嚐試去加載某個類,如果找不到再代理給父類加載器。這與一般類加載器的順序是相反的。這是 Java Servlet 規範中的推薦做法,其目的是使得 Web 應用自己的類的優先級高於 Web 容器提供的類。這種代理模式的一個例外是:Java 核心庫的類是不在查找範圍之內的。這也是爲了保證 Java 核心庫的類型安全。

絕大多數情況下,Web 應用的開發人員不需要考慮與類加載器相關的細節。下面給出幾條簡單的原則:

  • 每個 Web 應用自己的 Java 類文件和使用的庫的 jar 包,分別放在 WEB-INF/classes和 WEB-INF/lib目錄下面。

  • 多個應用共享的 Java 類文件和 jar 包,分別放在 Web 容器指定的由所有 Web 應用共享的目錄下面
  • 當出現找不到類的錯誤時,檢查當前類的類加載器和當前線程的上下文類加載器是否正確。




下面介紹一種加載類的方法:Class.forName

Class.forName

Class.forName是一個靜態方法,同樣可以用來加載類。該方法有兩種形式:Class.forName(String name, boolean initialize, ClassLoader loader)和 Class.forName(String className)。第一種形式的參數 name表示的是類的全名;initialize表示是否初始化類loader表示加載時使用的類加載器。第二種形式則相當於設置了參數 initialize的值爲 trueloader的值爲當前類的類加載器。Class.forName的一個很常見的用法是在加載數據庫驅動的時候。如Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()用來加載 Apache Derby 數據庫的驅動。

案例1:
  1. Class.forName("com.mysql.jdbc.Driver");   
  2. String url = "jdbc:mysql://127.0.0.1/test?useUnicode=true&characterEncoding=utf-8";   
  3. String user = "";   
  4. String psw = "";   
  5. Connection con = DriverManager.getConnection(url,user,psw);

其實這句話:Class.forName("com.mysql.jdbc.Driver");可以暫用一下代替:
com.mysql.jdbc.Driver driver = new com.mysql.jdbc.Driver();//這樣會創建一個這個類的實例

其目的主要是執行Driver的初始化,運行期靜態塊兒:
com.mysql.jdbc.Driver的源碼:



案例2、另一種方法



,第一個輸出的是 Test7類的類加載器,即系統類加載器。它是 sun.misc.Launcher$AppClassLoader類的實例;第二個輸出的是擴展類加載器,是 sun.misc.Launcher$ExtClassLoader類的實例。需要注意的是這裏並沒有輸出引導類加載器,這是由於有些 JDK 的實現對於父類加載器是引導類加載器的情況,getParent()方法返回 null

類裝入的階段

類的裝入實際上可以分成三個階段:裝入、鏈接和初始化。

雖然不是所有的問題,但至少大多數與類裝入有關的問題都可以追溯到在這三個階段中發生的某個問題。所以,對於每一階段的深入理解有助於對類裝入問題的診斷。圖 2 顯示了這三個階段:

圖 2. 類裝入的階段

裝入 階段包括:找到必要的類(通過查找每個類路徑)並裝入字節碼。在 JVM 中,裝入階段爲類對象提供了非常基本的內存結構。在這一階段不處理方法、字段和引用的其他類。所以,類還不能使用。

鏈接 是三個階段中最複雜的一個。可以把它分成三個主要階段:

  • 字節碼驗證。 類裝入器對於類的字節碼要做許多檢測,以確保格式正確、行爲正確。
  • 類準備。 這個階段準備代表每個類中定義的字段、方法和實現接口所必需的數據結構。
  • 解析。 在這個階段,類裝入器裝入類所引用的其他所有類。可以用許多方式引用類:
    • 超類
    • 接口
    • 字段
    • 方法簽名
    • 方法中使用的本地變量

初始化 階段,類中包含的靜態初始化器都被執行。在這一階段末尾,靜態字段被初始化成默認值。

在這三個階段末尾,類被完整地裝入,可以使用了。請注意可以用惰性方式執行類裝入,所以類裝入過程的某些部分可能在第一次使用類的時候才執行,而不是在裝入時執行。

顯式裝入與隱式裝入

類裝入的方式有兩種 —— 顯式 或 隱式,兩者之間有些細微差異。

顯式 類裝入發生在使用以下方法調用裝入的類的時候:

  • cl.loadClass()cl 是 java.lang.ClassLoader 的實例)
  • Class.forName()(啓動的類裝入器是當前類定義的類裝入器)

當調用其中一個方法的時候,指定的類(以類名爲參數)由類裝入器裝入。如果類已經裝入,那麼只是返回一個引用;否則,裝入器會通過委託模型裝入類。

隱式 類裝入發生在由於引用、實例化或繼承導致裝入類的時候(不是通過顯式方法調用)。在每種情況下,裝入都是在幕後啓動的,JVM 會解析必要的引用並裝入類。與顯式類裝入一樣,如果類已經裝入了,那麼只是返回一個引用;否則,裝入器會通過委託模型裝入類。

類的裝入通常組合了顯式和隱式類裝入。例如,類裝入器可能先顯式地裝入一個類,然後再隱式地裝入它引用的所有類











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