1. 程式人生 > >Java類載入器(深磕3)

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 |>  [email protected]


在顯示載入場景中,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 |>  [email protected]


%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 |>  [email protected]


%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:上下文載入器原理和案例