我是在關於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.ClassLoader
的getParent()
方法獲取父加載器,並且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"; } }