Java類加載器(死磕3)


【正文】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文件加載到虛擬機的內存,這個過程稱爲類加載,這個就是類加載的過程。

類加載的過程分爲五步:加載、驗證、準備、解析、初始化。

wpsD7AD.tmp


一、加載:


通過一個類的完全限定名稱,查找此類字節碼”.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


在顯示加載場景中,A加載B,一般情況下,B的加載器就是A的加載器。演示類ClassLoaderDemo加載了SystemConfig類,看看後者的加載器是啥?

代碼如下:

 

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)


wpsD7BE.tmp


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<>();
前面提到,每一個加載器,都有一個自己的查找範圍,是一系列的jar包或者類路徑,稱之爲查找路徑。形象的說,這個查找路徑,就像是ClassLoader加載類的專屬的地盤。

其源碼如下:

 // The paths searched for libraries

 private static String usr_paths[];

 private static String sys_paths[];
講了這麼多,終於到了關鍵點——ClassLoader加載一個類的祕密在哪裏?。

加載類的方法是:

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羣


無編程不創客,無案例不學習。 一定記得去跑一跑案例哦


類加載器   全目錄

1.導入

2. JAVA類加載器分類

3. 揭祕ClassLoader抽象基類

4. 神祕的雙親委託機制

5. 入門案例:自定義一個文件系統的自定義classLoader

6. 基礎案例:自定義一個網絡類加載器

7. 中級案例:設計一個加密的自定義網絡加載器

8. 高級案例1:使用ASM技術,結合類加載器,解密AOP原理

9. 高級案例2:上下文加載器原理和案例

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章