【正文】Java類加載器( CLassLoader ) 死磕3:
揭祕 ClassLoader抽象基類
本小節目錄
3.1. 類的加載分類:隱式加載和顯示加載
3.2. 加載一個類的五步工作
3.3. 如何獲取類的加載器
3.4 解刨加載器——ClassLoader抽象基類揭祕
3.5. loadClass 關鍵源代碼分析
3.1. 揭祕ClassLoader抽象基類
3.1.1. 類的加載分類:隱式加載和顯示加載
java中類是動態加載的,jvm啓動的時候,並不會一次性加載所有的class文件,而是根據需要去動態加載。一是加快啓動的速度,二是節約內存。如果一次性加載全部jar包的所有class,速度會很慢。
動態載入一個class類,有兩種方式:
(1) implicit隱式加載
即通過實例化才載入的特性來動態載入class。比如:
IPet dog=new Dog();
在新建對象時,如果Dog.class類沒有加載,則JVM 會在背後調用當前的AppClassLoader,加載Dog.class,並且初始化。
(2) explicit顯式加載
explicit顯式方式,又分兩種方式:
一是:java.lang.Class的forName()方法。 比如:
Class<?> aClass = Class.forName(className);
二是:java.lang.ClassLoader的loadClass()方法。 比如:
FileClassLoader classLoader = new FileClassLoader(null,baseDir);
Class<?> aClass = classLoader.loadClass(className);
下面有兩個問題:
兩種顯示加載的方式,有何區別呢?
兩種顯示加載的方式,有何聯繫呢?
花開兩朵,各表一枝。 現在先介紹通過第二種ClassLoader顯式加載方法 ,其類的加載過程。然後再進行兩種方式的比對。
在介紹ClassLoader顯式加載前,先回顧一下類的加載過程。
3.1.2. 加載一個類的五步工作
在瘋狂創客圈的《死磕java》工程源碼中,有一個常用的系統屬性的配置類——SystemConfig 。下面以次爲例,展示一下類的加載過程。
源代碼如下:
package com.crazymakercircle.config; ..................... @ConfigFileAnno(file = "/system.properties") public class SystemConfig extends ConfigProperties { static { Logger.info("開始加載配置文件到SystemConfig"); //依照註解裝載配置項 loadAnnotations(SystemConfig.class); } .................................... @ConfigFieldAnno(proterty = "debug") public static boolean debug; /** * 寵物工廠類的名稱 */ @ConfigFieldAnno(proterty = "pet.factory.class") public static String PET_FACTORY_CLASS; /** * 寵物模塊的類路徑 */ @ConfigFieldAnno(proterty = "pet.lib.path") public static String PET_LIB_PATH; }
編譯完成後,這個”.java”文件經過Java編譯器編譯成拓展名爲”.class”文件——SystemConfig.class,這個”.class”文件中保存着Java代碼經轉換後的虛擬機指令。
當需要使用這個類時,虛擬機將會加載它的SystemConfig.class”文件,並創建對應的class對象,將class文件加載到虛擬機的內存,這個過程稱爲類加載,這個就是類加載的過程。
類加載的過程分爲五步:加載、驗證、準備、解析、初始化。
一、加載:
通過一個類的完全限定名稱,查找此類字節碼”.class”文件,讀入內存形成字節碼流。並創建一個Class對象。
二、驗證
檢查字節流中包含信息符合虛擬機要求,不會危害虛擬機自身安全。主要包括四種驗證,文件格式驗證,元數據驗證,字節碼驗證,符號引用驗證。
三、準備
主要的工作是,在方法區分配靜態變量的內存,並且進行內存的初始化,設置初始值即0。
四、解析
主要將常量池中的符號引用進行翻譯,翻譯爲直接引用,也就是在內存中的地址。
五、初始化
首先,如果類的存在靜態變量需要進行賦值,在這個階段完成。
其次,如果有static 靜態塊,在這個階段執行。
再次,若該類具有超類,則對其進行初始化。
實例中,通過這一步,完成執行SystemConfig 類的static塊的執行,並且爲每一個配置項賦值,因爲都是靜態的。
通過這個五步,一個類的全限定名的”.class”文件,完成了轉換爲一個與目標類對應的java.lang.Class對象實例的工作。 實際的工作,遠遠比上面的陳述的負責。爲了方便理解和記憶,上面進行了大大的簡化,只是提取出主要的特徵。
以上五步的中間3個步驟,驗證、準備、解析,合起來有一個統稱,叫類的鏈接。
3.1.3. 如何獲取類的加載器
使用 getClassLoader() 方法,可以取得類的加載器。如果是應用程序的class path下的類,加載器一般爲AppClassLoader 類型。當前,並不是絕對的,這個後面講到如何去定製和修改。
查看一下當前實例的應用程序類所屬的classLoader,代碼如下:
public static void showCurrentClassLoader() { Logger.info(""); Logger.info("顯示當前類的ClassLoader::"); Logger.info("class=" + ClassLoaderDemo.class.getCanonicalName()); ClassLoader loader = ClassLoaderDemo.class.getClassLoader(); Logger.info("Loader=" + loader.toString()); }
結果如下:
showCurrentClassLoader |> 顯示當前類的ClassLoader:: showCurrentClassLoader |> class=com.crazymakercircle.classLoaderDemo.base.ClassLoaderDemo showCurrentClassLoader |> Loader=sun.misc.Launcher$AppClassLoader@18b4aac2
代碼如下:
public static void showAppLoader()
{
String className = "com.crazymakercircle.config.SystemConfig";
Class<?> target = null;
try
{
//根據類名 顯示加載加載類
target = Class.forName(className);
} catch (ClassNotFoundException e)
{
e.printStackTrace();
}
Logger.info("顯示加載的類的ClassLoader:");
Logger.info("class=" + target.getCanonicalName());
ClassLoader loader = target.getClassLoader();
Logger.info("Loader=" + loader.toString());
}
結果如下:
showAppLoader |> 顯示加載的類的ClassLoader: showAppLoader |> class=com.crazymakercircle.config.SystemConfig showAppLoader |> Loader=sun.misc.Launcher$AppClassLoader@18b4aac2
%java.ext.dirs% 下jar中類的加載器,類型一定爲Extention ClassLoader。比方說,DNSNameService 域名服務類是一個典型的 lib\ext 中的dnsns.jar包中的類,我們來看下他的加載器。代碼如下:
public static void showExtClassLoader() { ClassLoader loader = DNSNameService.class.getClassLoader(); Logger.info(""); Logger.info("%java.ext.dirs% 下類的ClassLoader:"); Logger.info("class=" + DNSNameService.class.getCanonicalName()); Logger.info("Loader=" + loader.toString()); }
結果如下:
showExtClassLoader |> %java.ext.dirs% 下類的ClassLoader: showExtClassLoader |> class=sun.net.spi.nameservice.dns.DNSNameService showExtClassLoader |> Loader=sun.misc.Launcher$ExtClassLoader@12a3a380
%java.home% 下jar中類的加載器,我們可以理所當然的認爲,一定是Bootstrap ClassLoader 加載器類型。 比方說,String 類是一個典型的系統屬性%java.home% 目錄下 rt.jar 中的類,我們來看下他的加載器。代碼如下:
public static void showBootstrapClassLoader() { ClassLoader loader = String.class.getClassLoader(); Logger.info(""); Logger.info("%java.home%下類的ClassLoader:"); Logger.info("class=" + String.class.getCanonicalName()); Logger.info("Loader=" + loader); }
遺憾的時,結果與前面兩個加載器,大不一樣。
結果如下:
showBootstrapClassLoader |> %java.home%下類的ClassLoader: showBootstrapClassLoader |> class=java.lang.String showBootstrapClassLoader |> Loader=null
1.1.4. 解刨加載器——揭祕ClassLoader抽象基類
ClassLoader類是Java 中的 所有ClassLoader 的基類,在java.lang包中。這是一個抽象類。
ClassLoader類,包含了所有類加載器的三個重要組成部分:
(1)加載成功的Class 對象的緩存;
(2)類的查找路徑
(3)loadClass(String name)
ClassLoader加載器將加載成功的Class 對象,加入一個Vector 動態數組中,避免重複加載。 這個Vector 動態數組,也就是加載成功的classes的緩存。
源碼如下:
// The classes loaded by this class loader. The only purpose of this table // is to keep the classes from being GC'ed until the loader is GC'ed. private final Vector<Class<?>> classes = new Vector<>();
其源碼如下:
// The paths searched for libraries private static String usr_paths[]; private static String sys_paths[];
加載類的方法是:
ClassLoader的loadClass(String name)方法,是類加載器中一個比較重要的方法。其作用是,用於加載一個類。
這個方法,比較理想的流程,大致如下面所示:
(1)首先在緩存中找,是否已經加載。如果找到就返回。
(2)如果找不到,就去查找路徑查找文件。如果找出類的.class字節碼,則去完成加載一個類的五步工作,放入自己的緩存中,然後返回給調用者。
loadClass(String name)方法,充分將前面的 classes 緩存和查找路徑利用起來,將他們串在了一起。
實際上,這僅僅只是一個理想化的結果。
實際的類加載流程,遠遠比以上的假想流程,複雜得多。
直接來看 loadClass 方法的源代碼吧,這樣反而簡單、粗暴、直接。
1.1.5. loadClass 關鍵源代碼分析
loadClass 方法的源代碼如下:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { //在加載器的緩存中,按照類名,查找已經加載好的現成類 Class<?> c = findLoadedClass(name); //如果加載成功,就直接返回了 //如果還沒有找到,尷尬了 if (c == null) { .... try { if (parent != null) { //優先讓父加載器去加載,若父加載器不爲空的話 c = parent.loadClass(name, false); } else { //若父加載器爲空,則通過Bootstrap Classloader 加載 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { .... } if (c == null) { // 如果父加載器、啓動類加載器,都沒有找到 // 才調用findclass,從自己的地盤,類路徑加載 c = findClass(name); .... } } ....... return c; } } }
概況一下,loadClass的大致流程如下:
1. 執行findLoadedClass(String)去緩存檢測這個class是不是已經加載過了。 在加載器的緩存中,按照類名,查找已經加載好的現成類。如果找到,直接返回了。
2. 如果沒有找到,執行parent父加載器的loadClass方法。通過父加載器加載。注意:這裏不是去自己的地盤查找class文件,而是優先通過父加載器加載。這點,非常重要。具體原因,後面會講到。
3. 若父加載器爲空,則通過Bootstrap Classloader 加載。
4. 如果父加載器、啓動類加載器,都沒有找到,才調用findclass,從自己的地盤,自己的類路徑去查找字節碼文件,通過findClass(String)查找。
抽絲剝繭之後,loadClass 方法的源代碼其實也不過如此,比較簡單。
但是,這裏已經有兩個疑問:
(1)一個加載器的parent是誰?
(2)爲什麼優先從parent加載,而不是從自己的地盤加載?
欲知後事如何,請看下回分解。
源碼:
代碼工程: classLoaderDemo.zip
下載地址:在瘋狂創客圈QQ羣文件共享。
瘋狂創客圈:如果說Java是一個武林,這裏的聚集一羣武癡, 交流編程體驗心得
QQ羣鏈接:瘋狂創客圈QQ羣
無編程不創客,無案例不學習。 一定記得去跑一跑案例哦
類加載器 全目錄
5. 入門案例:自定義一個文件系統的自定義classLoader