Java基礎-類加載器以及加載機制

我是在關於Java的面試題裏瞭解到類加載器的,在這以前從未想過Java裏類是如何被加載、解析的,一直覺得只要Import就行了。事實上Java類加載器是一塊很是重要的內容,能夠用在類層次劃分、OSGi、熱部署、代碼加密等領域。即便業務上可能沒有涉及到,瞭解相關知識對排除BUG也是有幫助的。java

類加載器基本概念

平時在編寫代碼時,想使用什麼類就Import就行了,好像這些類一開始就在JVM裏了同樣,如今咱們知道這是由於JVM自動爲咱們加載了這些類。顧名思義,類加載器的工做主要是加載Java字節碼文件(也就是.class文件)到虛擬機裏,並解析爲java.lang.Class類的一個實例。到這裏,被加載的類仍是不能像平時同樣直接new一個對象出來的。由於一個類總共要經歷加載、驗證、解析、初始化等4個步驟後纔是Java裏的一個類型。後面幾個步驟不是本文重點,你們能夠自行學習。面試

類加載器的組成

類加載器一共有4種,分別是引導類加載器(bootstrap class loader)、擴展類加載器(extensions class loader)、系統類加載器(system class loader)、自定義加載器,它們之間的加載關係以下圖所示:bootstrap

其中,除了引導類加載器是用原生代碼實現,其他的加載器都是繼承自抽象類java.lang.ClassLoader。並且系統自帶的3個加載器都有本身的特殊之處。安全

引導類加載器

引導類加載器是用來加載Java的核心庫,像是java.lang包等這些Java應用必備的類都是引導類加載器加載的。加載路徑是<JAVA_HOME>\lib目錄中的或者是-Xbootclasspath參數所指定的目錄中,被JVM所識別的文件(經過名字識別,名字必須是rt.jar)。由於引導類加載器是用原生代碼實現的,因此不能在Java代碼中直接引用到引導類加載器。架構

擴展類加載器

顧名思義,擴展類加載器是用來加載Java的擴展類庫。加載路徑是<JAVA_HOME>\lib\ext目錄中的或者是java.ext.dirs系統變量所指定的路徑中的全部類庫。學習

系統類加載器

系統類加載器的加載路徑是Java應用的類路徑(CLASSPATH),也就是說在沒有自定義加載器的狀況下,Java應用的類都是由系統類加載器加載的。並且該加載器能夠用ClassLoader類的getSystemClassLoader()方法直接獲取到。this

除了引導類加載器,每一個加載器都有一個父加載器。好比加載器A加載了加載器B,那麼加載器A就是加載器B的父加載器,能夠經過java.lang.ClassLoadergetParent()方法獲取父加載器,並且Java中每一個Class對象都維護着一個加載器引用,能夠經過getClassLoader()方法獲取加載該類的加載器。加密

例以下面這段代碼:spa

public class Main {

    public static void main(String[] args) {
        ClassLoader loader = Main.class.getClassLoader();
        while (loader != null) {
            System.out.println(loader.toString());
            loader = loader.getParent();
        }
    }

}

這裏輸出了Main類的加載器與其全部的父加載器,運行結果:.net

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1540e19d

Process finished with exit code 0

咱們看到Main類的加載器是系統類加載器,它的父加載器是擴展類加載器。擴展類加載器的父加載器應該是引導類加載器纔對,這裏沒有輸出是由於有些JDK的實現裏在父加載器爲引導類加載器的狀況下是返回null的。

雙親委託模式

第一次看到雙親委託模式這個詞的時候就感受意義不明,徹底不知道是什麼意思。在瞭解了加載器的加載過程以後,才發現是一種代理模式。

以上文中的Main類的加載過程爲例,它的加載器爲系統類加載器。可是系統類加載器不會直接去加載這個類,而是先委託給它的父加載器,也就是擴展類加載器。一樣,擴展類加載器也會先委託給它的父加載器,一直委託到引導類加載器纔開始真正的嘗試加載,若是加載失敗就返回由發出委託的加載器嘗試加載。

這樣作的目的是爲了保護Java核心庫和保持類型安全。由於在JVM中判斷兩個類是否相同,不只僅是看它們的全名是否相同,還要判斷它們的加載器是否相同。經過雙親委託模式就能保證每次加載核心庫的加載器都是引導類加載器,從而防止出現相似於多個java.lang.Object類型這種狀況。

自定義加載器

編寫自定義加載器並不困難,只要繼承抽象類java.lang.ClassLoader並覆蓋findClass(String name)方法就好了。不建議覆蓋 loadClass(String name)方法,由於這個方法裏面封裝了前面提到的雙親委託模式,覆蓋可能會致使該模式失效。

// 源碼來自 https://www.ibm.com/developerworks/cn/java/j-lo-classloader
public class FileSystemClassLoader extends ClassLoader { 
 
   private String rootDir; 
 
   public FileSystemClassLoader(String rootDir) { 
       this.rootDir = rootDir; 
   } 
 
   protected Class<?> findClass(String name) throws ClassNotFoundException { 
       byte[] classData = getClassData(name); 
       if (classData == null) { 
           throw new ClassNotFoundException(); 
       } 
       else { 
           return defineClass(name, classData, 0, classData.length); 
       } 
   } 
 
   private byte[] getClassData(String className) { 
       String path = classNameToPath(className); 
       try { 
           InputStream ins = new FileInputStream(path); 
           ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
           int bufferSize = 4096; 
           byte[] buffer = new byte[bufferSize]; 
           int bytesNumRead = 0; 
           while ((bytesNumRead = ins.read(buffer)) != -1) { 
               baos.write(buffer, 0, bytesNumRead); 
           } 
           return baos.toByteArray(); 
       } catch (IOException e) { 
           e.printStackTrace(); 
       } 
       return null; 
   } 
 
   private String classNameToPath(String className) { 
       return rootDir + File.separatorChar 
               + className.replace('.', File.separatorChar) + ".class"; 
   } 
}

參考資料

Java類加載機制與Tomcat類加載器架構

深刻探討 Java 類加載器

相關文章
相關標籤/搜索