java類載入

wangyunpeng0319發表於2017-06-03

1、ClassNotFoundExcetpion 
  我們在開發中,經常可以遇見java.lang.ClassNotFoundExcetpion這個異常,今天我就來總結一下這個問題。對於這個異常,它實質涉及到了java技術體系中的類載入。Java的類載入機制是技術體系中比較核心的部分,雖然它和我們直接打交道不多,但是對其背後的機理有一定理解有助於我們排查程式中出現的類載入失敗等技術問題。 
2、類的載入過程 
  一個java檔案從被載入到被解除安裝這個生命過程,總共要經歷5個階段,JVM將類載入過程分為: 
  載入->連結(驗證+準備+解析)->初始化(使用前的準備)->使用->解除安裝 
(1)載入 
  首先通過一個類的全限定名來獲取此類的二進位制位元組流;其次將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構;最後在java堆中生成一個代表這個類的Class物件,作為方法區這些資料的訪問入口。總的來說就是查詢並載入類的二進位制資料。 
(2)連結: 
  驗證:確保被載入類的正確性; 
  準備:為類的靜態變數分配記憶體,並將其初始化為預設值; 
  解析:把類中的符號引用轉換為直接引用; 
(3)為類的靜態變數賦予正確的初始值 
3、類的初始化 
(1)類什麼時候才被初始化 
  1)建立類的例項,也就是new一個物件 
  2)訪問某個類或介面的靜態變數,或者對該靜態變數賦值 
  3)呼叫類的靜態方法 
  4)反射(Class.forName(“com.lyj.load”)) 
  5)初始化一個類的子類(會首先初始化子類的父類) 
  6)JVM啟動時標明的啟動類,即檔名和類名相同的那個類 
(2)類的初始化順序 
  1)如果這個類還沒有被載入和連結,那先進行載入和連結 
  2)假如這個類存在直接父類,並且這個類還沒有被初始化(注意:在一個類載入器中,類只能初始化一次),那就初始化直接的父類(不適用於介面) 
  3)加入類中存在初始化語句(如static變數和static塊),那就依次執行這些初始化語句。 
  4)總的來說,初始化順序依次是:(靜態變數、靜態初始化塊)–>(變數、初始化塊)–> 構造器;如果有父類,則順序是:父類static方法 –> 子類static方法 –> 父類構造方法- -> 子類構造方法 
4、類的載入 
  類的載入指的是將類的.class檔案中的二進位制資料讀入到記憶體中,將其放在執行時資料區的方法區內,然後在堆區建立一個這個類的java.lang.Class物件,用來封裝類在方法區類的物件。如: 
這裡寫圖片描述 
這裡寫圖片描述

  類的載入的最終產品是位於堆區中的Class物件。Class物件封裝了類在方法區內的資料結構,並且向Java程式設計師提供了訪問方法區內的資料結構的介面。載入類的方式有以下幾種: 
  1)從本地系統直接載入 
  2)通過網路下載.class檔案 
  3)從zip,jar等歸檔檔案中載入.class檔案 
  4)從專有資料庫中提取.class檔案 
  5)將Java原始檔動態編譯為.class檔案(伺服器) 
5、載入器 
  JVM的類載入是通過ClassLoader及其子類來完成的,類的層次關係和載入順序可以由下圖來描述: 
   
這裡寫圖片描述

(1)載入器介紹 
1)BootstrapClassLoader(啟動類載入器) 
  負責載入$JAVA_HOME中jre/lib/rt.jar裡所有的class,載入System.getProperty(“sun.boot.class.path”)所指定的路徑或jar。 
2)ExtensionClassLoader(標準擴充套件類載入器) 
  負責載入java平臺中擴充套件功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包。載System.getProperty(“java.ext.dirs”)所指定的路徑或jar。 
3)AppClassLoader(系統類載入器) 
  負責記載classpath中指定的jar包及目錄中class 
4)CustomClassLoader(自定義載入器) 
  屬於應用程式根據自身需要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現。

(2)類載入器的順序 
1)載入過程中會先檢查類是否被已載入,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已載入就視為已載入此類,保證此類只所有ClassLoader載入一次。而載入的順序是自頂向下,也就是由上層來逐層嘗試載入此類。 
2)在載入類時,每個類載入器會將載入任務上交給其父,如果其父找不到,再由自己去載入。 
3)Bootstrap Loader(啟動類載入器)是最頂級的類載入器了,其父載入器為null


  1. Java虛擬機器的第一個類載入器是Bootstrap,這個載入器很特殊,它不是Java類,因此它不需要被別人載入,它巢狀在Java虛擬機器核心裡面,也就是JVM啟動的時候Bootstrap就已經啟動,它是用C++寫的二進位制程式碼(不是位元組碼),它可以去載入別的類。

    這也是我們在測試時為什麼發現System.class.getClassLoader()結果為null的原因,這並不表示System這個類沒有類載入器,而是它的載入器比較特殊,是BootstrapClassLoader,由於它不是Java類,因此獲得它的引用肯定返回null。

  2. 委託機制具體含義 
    當Java虛擬機器要載入一個類時,到底派出哪個類載入器去載入呢?

    • 首先當前執行緒的類載入器去載入執行緒中的第一個類(假設為類A)。 
      注:當前執行緒的類載入器可以通過Thread類的getContextClassLoader()獲得,也可以通過setContextClassLoader()自己設定類載入器。
    • 如果類A中引用了類B,Java虛擬機器將使用載入類A的類載入器去載入類B。
    • 還可以直接呼叫ClassLoader.loadClass()方法來指定某個類載入器去載入某個類。
  3. 委託機制的意義 — 防止記憶體中出現多份同樣的位元組碼 
    比如兩個類A和類B都要載入System類:

    • 如果不用委託而是自己載入自己的,那麼類A就會載入一份System位元組碼,然後類B又會載入一份System位元組碼,這樣記憶體中就出現了兩份System位元組碼。
    • 如果使用委託機制,會遞迴的向父類查詢,也就是首選用Bootstrap嘗試載入,如果找不到再向下。這裡的System就能在Bootstrap中找到然後載入,如果此時類B也要載入System,也從Bootstrap開始,此時Bootstrap發現已經載入過了System那麼直接返回記憶體中的System即可而不需要重新載入,這樣記憶體中就只有一份System的位元組碼了。

Java中class.forName()和classLoader都可用來對類進行載入。
class.forName()前者除了將類的.class檔案載入到jvm中之外,還會對類進行解釋,執行類中的static塊。
而classLoader只幹一件事情,就是將.class檔案載入到jvm中,不會執行static中的內容,只有在newInstance才會去執行static塊。
Class.forName(name, initialize, loader)帶參函式也可控制是否載入static塊。並且只有呼叫了newInstance()方法採用呼叫建構函式,建立類的物件

相關文章