JAVA 類加載

Java類的加載是由虛擬機來完成的,虛擬機把描述類的class文件加載到到內存,並對數據進行校驗、解析、初始化,最終形成能被Java虛擬機直接使用的Java類型。

概述

那麼類加載器是什麼呢
類加載器是一個用來加載類文件的類,Java源代碼通過javac編譯器編譯成類文件,然後jvm來執行類文件的字節碼。類加載器負責加載文件系統、網絡或者其他來源的類文件。

默認類加載器的分類

  • Bootstrap加載器:加載rt.jar中的jdk類文件,它是所有類加載器的父加載器。又稱初始加載器
  • Extension類加載器:先委託給父加載器,也就是Bootstrap,如果沒有成功的話,再從jre/lib/ext目錄下獲取java.ext.dirs系統屬性定義的目錄下加載類。
  • System類加載器:又稱Application類加載器,他負責從classpath環境變量中加載默寫應用相關的類,它是Extension類加載器的子加載器,
  • 除了Bootstrap類加載器是大部分有C寫的,其他的類加載器都是通過java.lang.ClassLoader來實現的。

這裏說明一下java.lang.ClassLoader中幾個重要的方法

//加載指定名稱(包括包名)的二進制類型,供用戶調用的接口
public Class<?> loadClass(String name);
//加載指定名稱(包括包名)的二進制類型,同時指定是否解析(但是,這裏的resolve參數不一定真正能達到解析的效果),供繼承用
protected synchronized Class<?> loadClass(String name, boolean resolve);
protected Class<?> findClass(String name)
//定義類型,一般在findClass方法中讀取到對應字節碼後調用,可以看出不可繼承(說明:JVM已經實現了對應的具體功能,解析對應的字節碼,產生對應的內部數據結構放置到方法區,所以無需覆寫,直接調用就可以了)
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{}

加載過程

類加載過程:分爲三步:裝載、鏈接、初始化,其中鏈接又分爲三個步驟:驗證、準備、解析

  1. 裝載:
    1. 通過一個類的權限定名來獲取定義此類的二進制字節流
    2. 將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構
    3. 在java堆中生成一個代表這個類的java.lang.class對象,做爲方法去這些數據的訪問入口
  2. 鏈接:
    1. 驗證:確保被加載類的正確性,如果有一個惡意的class文件,讓jvm執行,那就不好了,所以要先驗證是否安全,纔會通過。
    2. 準備:爲類的靜態變量分配內存空間,並且將其初始化爲默認值,比如private static Int i = 0;,這些內存將在方法區中進行分配,需要說明的是,這時候進行內存分配的僅是static修飾的變量,而不是實例變量,實例變量將會在對象實例化時隨着對象一起分配在java的堆中。
    3. 解析:把類中符號引用轉換爲直接引用
  3. 初始化:爲類的靜態炳龍賦予正確的初始值

那麼java類的生命週期就產生了

上述中類初始化說到,那麼問題:

  1. 類在什麼情況會初始化
  2. 類初始化

首頁第一個問題:類在什麼時候初始化

  • 創建實例的時候,例如new 一個對象。
  • 訪問類的靜態變量或者靜態方法
  • 類反射
  • 初始化類的子類
  • JVM啓動標明的啓動類,就是文件名和類名相同的那個類

第二個問題:類初始化

  • 如果這個類 還沒有被裝載和鏈接,那就先裝載和鏈接
  • 如果這個類存在直接父類,並且這個類還沒有被初始化(注意:在一個類加載器中,類只能初始化一次)那就初始化直接父類(注意:這個不適用於接口)
  • 加入類中存在初始化語句(如Static變量和static塊),那就依次執行這些初始化語句。

其中要說到雙親委派加載機制

當一個類收到類加載請求時,請把請求委派給父類去完成,每個層次的類加載都是如此,所以 所有的加載請求都傳到了啓動類中去加載,只有當父類加載器返回自己無法加載的時候,子類加載器纔會嘗試自己加載。

如圖

優點:類加載器具備了優先級層次關係,所有加載請求都彙總到頂層的啓動類加載器,例如 java.lang.Object (存放在jre\lib\rt.jar中)它是所有Java類的父類,因此Object會由類加載器來加載,並且加載的都是同一個類。如果不用雙親委派加載模式,每個類自行加載,會出現多個object,程序沒法控制。

看源碼(jdk 1.8)

//方法傳的參數:類名包名,鏈接一個指定的類。這是一個在某些情況下確保類可用的必要方法,詳見 Java 語言規範中“執行”一章對該方法的描述。
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{
    //同步處理
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        // 判斷這個類是否已經加載了
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            //如果沒有加載就判斷此類的父類
            try {
                if (parent != null) {
                    //如果父類爲空,則使用父類加載器
                    c = parent.loadClass(name, false);
                } else {
                    //否則使用系統加載器
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            //如果父類加載器或者系統加載器都加載失敗,則使用自己的findClass方法加載
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        //判斷是否連接這個類
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

這是在網上的一個例子 雙親委派模型代碼實例驗證

public class ClassLoaderTest {

    public static void main(String args[]){
        showMsg("ClassLoaderTest 類的加載器的名稱:"+ ClassLoaderTest.class.getClassLoader().getClass().getName());
        showMsg("System 類的加載器的名稱:"+ System.class.getClassLoader());
        showMsg("List 類的加載器的名稱:"+ List.class.getClassLoader());

        ClassLoader c1 = ClassLoaderTest.class.getClassLoader();

        while (c1 != null){
            System.out.print(c1.getClass().getName()+ "-->");
            c1 = c1.getParent();
        }
        showMsg(c1);
    }

    private static void showMsg(Object msg){
        System.out.println(msg);
    }
}

輸出接口爲

1.ClassLoaderTest 類的加載器的名稱:sun.misc.Launcher$AppClassLoader
2.System 類的加載器的名稱:null
3.List 類的加載器的名稱:null
4.sun.misc.Launcher$AppClassLoader-->sun.misc.Launcher$ExtClassLoader-->null

說明一下:
1.ClassloaderTest 類是用戶自定義的類,位於CLASSPATH ,由系統應用程序加載器加載。
2.System和List 類都是屬於java類的核心類,由祖先類啓動類加載器加載,而啓動類加載器是在JVM內部通過c/c++實現的。並不是Java實現的,自然就不能繼承classLoader類,自然就不能輸出類名
3.箭頭代表加載流程,層級委託,從祖先開始直到系統應用程序加載器才被加載。

測試一下,將上面的Java代碼打包成jar包,放到……jdk/jre/lib/ext下面
如圖

然後再執行代碼,運行結果是:

ClassLoaderTest 類的加載器的名稱:sun.misc.Launcher$ExtClassLoader
System 類的加載器的名稱:null
List 類的加載器的名稱:null
sun.misc.Launcher$ExtClassLoader-->null

從上面可以看出,沒有通過系統類加載器來加載,因爲類的jar包放到ExtClassLoader的加載目錄下,所以當在根目錄加載找到不到後,在ExtClassLoader目錄處找到所以忽略的AppClassLoader階段。

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