要深入瞭解ClassLoader,首先就要知道ClassLoader是用來乾什麼的,顧名思義,它就是用來載入Class文件到JVM,以供程式使用 的。我們知道,java程式可以動態載入類定義,而這個動態載入的機制就是通過ClassLoader來實現的,所以可想而知ClassLoader的重 要性如何
要深入瞭解ClassLoader,首先就要知道ClassLoader是用來乾什麼的,顧名思義,它就是用來載入Class文件到JVM,以供程式使用 的。我們知道,java程式可以動態載入類定義,而這個動態載入的機制就是通過ClassLoader來實現的,所以可想而知ClassLoader的重 要性如何。
既然ClassLoader是用來載入類到JVM中的,那麼ClassLoader又是如何被載入呢?難道它不是java的類?
JDK 預設提供瞭如下幾種ClassLoader:
1. Bootstrp loader
Bootstrp載入器是用C++語言寫的,它是在Java虛擬機啟動後初始化的,它主要負責載入%JAVA_HOME%/jre/lib,-Xbootclasspath參數指定的路徑以及%JAVA_HOME%/jre/classes中的類。
1. ExtClassLoader
Bootstrp loader載入ExtClassLoader,並且將ExtClassLoader的父載入器設置為Bootstrploader.ExtClassLoader是用Java寫的,具體來說就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要載入%JAVA_HOME%/jre/lib/ext,此路徑下的所有classes目錄以及java.ext.dirs系統變數指定的路徑中類庫。
2. AppClassLoader
Bootstrp loader載入完ExtClassLoader後,就會載入AppClassLoader,並且將AppClassLoader的父載入器指定為 ExtClassLoader。AppClassLoader也是用Java寫成的,它的實現類是sun.misc.Launcher$AppClassLoader,另外我們知道ClassLoader中有個getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要負責載入classpath所指定的位置的類或者是jar文檔,它也是Java程式預設的類載入器。
當運行一個程式的時候,JVM啟動,運行bootstrap classloader,該ClassLoader載入java核心API(ExtClassLoader和AppClassLoader也在此時被加 載),然後調用ExtClassLoader載入擴展API,最後AppClassLoader載入CLASSPATH目錄下定義的Class,這就是一 個程式最基本的載入流程。
上面大概講解了一下ClassLoader的作用以及一個最基本的載入流程,接下來將講解一下ClassLoader載入的方式,這裡就不得不講一下ClassLoader在這裡使用了雙親委托模式進行類載入。
每一個自定義ClassLoader都必須繼承ClassLoader這個抽象類,而每個ClassLoader都會有一個parent ClassLoader,我們可以看一下ClassLoader這個抽象類中有一個getParent()方法,這個方法用來返回當前 ClassLoader的parent,註意,這個parent不是指的被繼承的類,而是在實例化該ClassLoader時指定的一個 ClassLoader,如果這個parent為null,那麼就預設該ClassLoader的parent是bootstrap classloader,這個parent有什麼用呢?
我們可以考慮這樣一種情況,假設我們自定義了一個ClientDefClassLoader,我們使用這個自定義的ClassLoader載入 java.lang.String,那麼這裡String是否會被這個ClassLoader載入呢?事實上java.lang.String這個類並不 是被這個ClientDefClassLoader載入,而是由bootstrap classloader進行載入,為什麼會這樣?實際上這就是雙親委托模式的原因,因為在任何一個自定義ClassLoader載入一個類之前,它都會先 委托它的父親ClassLoader進行載入,只有當父親ClassLoader無法載入成功後,才會由自己載入,在上面這個例子里,因為 java.lang.String是屬於java核心API的一個類,所以當使用ClientDefClassLoader載入它的時候,該 ClassLoader會先委托它的父親ClassLoader進行載入,上面講過,當ClassLoader的parent為null 時,ClassLoader的parent就是bootstrap classloader,所以在ClassLoader的最頂層就是bootstrap classloader,因此最終委托到bootstrap classloader的時候,bootstrap classloader就會返回String的Class。
我們來看一下ClassLoader中的一段源代碼:
protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException{ // 首先檢查該name指定的class是否有被載入 Class c = findLoadedClass(name); if (c == null) { try { if (parent != null) { //如果parent不為null,則調用parent的loadClass進行載入 c = parent.loadClass(name, false); }else{ //parent為null,則調用BootstrapClassLoader進行載入 c = findBootstrapClass0(name); } }catch(ClassNotFoundException e) { //如果仍然無法載入成功,則調用自身的findClass進行載入 c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }
從上面一段代碼中,我們可以看出一個類載入的大概過程與之前我所舉的例子是一樣的,而我們要實現一個自定義類的時候,只需要實現findClass方法即可。
為什麼要使用這種雙親委托模式呢?
第一個原因就是因為這樣可以避免重覆載入,當父親已經載入了該類的時候,就沒有必要子ClassLoader再載入一次。
第二個原因就是考慮到安全因素,我們試想一下,如果不使用這種委托模式,那我們就可以隨時使用自定義的String來動態替代java核心api中定義類 型,這樣會存在非常大的安全隱患,而雙親委托的方式,就可以避免這種情況,因為String已經在啟動時被載入,所以用戶自定義類是無法載入一個自定義的 ClassLoader。
上面對ClassLoader的載入機制進行了大概的介紹,接下來不得不在此講解一下另外一個和ClassLoader相關的類,那就是Class類,每 個被ClassLoader載入的class文件,最終都會以Class類的實例被程式員引用,我們可以把Class類當作是普通類的一個模板,JVM根 據這個模板生成對應的實例,最終被程式員所使用。
我們看到在Class類中有個靜態方法forName,這個方法和ClassLoader中的loadClass方法的目的一樣,都是用來載入class的,但是兩者在作用上卻有所區別。
那為啥要自定義classloader呢?
我覺得,主要有以下原因: 1.類隔離。不想讓某些類被其他類看見。 2.安全因素。比如我有一個自定義的加密類的文件,只有用我自己的classloader才能解析成正常的類文件並運行。 3.功能因素。對類載入器有其他的需求之類。