Java類加載機制(類加載器、雙親委派)

類加載的時機

  1. 創建類的實例:new、反射、反序列化、clone;
  2. 訪問類中的某個靜態變量,或者對靜態變量進行賦值;
  3. 主動調用類的靜態方法;
  4. ClassForName(“包類名”),得到的class已經初始化完成,ClassLoader.loadClass(“包類名”)得到的class是還沒有鏈接(驗證,準備,解析)的;
  5. 完成子類的初始化,也會完成對本類的初始化(接口除外);
  6. 該類是程序引導入口(main入口或者test入口);

類加載過程

  class文件中保存着虛擬機將要執行的指令,當需要某個類的時候,java虛擬機會通過類的全限定名從磁盤中加載class 文件,並創建對應的java.lang.Class對象。將class文件加載到虛擬機的內存,這個過程被稱爲類的加載。
類加載流程

  1. 加載:ClassLoader通過一個類的完全限定名查找此類字節碼文件,並利用字節碼文件創建一個class對象,存儲在方法區;
  2. 驗證:目的在於確保class文件的字節流信息符合當前虛擬機要求,不會危害虛擬機自身的安全,主要包括四種驗證:文件格式的驗證、元數據的驗證、字節碼驗證、符號引用驗證;
  3. 準備:爲類變量(static修飾的字段變量)分配內存並且設置該類變量的初始值,在初始化的階段真正賦值;
  4. 解析:這裏主要的任務是把常量池中的符號引用替換成直接引用,下面就是Java字節碼文件中的符號引用;
#1 = Methodref          #7.#19         // java/lang/Object."<init>":()V
#2 = Fieldref           #20.#21        // java/lang/System.out:Ljava/io/PrintStream;
#3 = Fieldref           #6.#22         // intron/classtest/ClassTest.TEST:Ljava/lang/String;
#4 = Methodref          #23.#24        // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = String             #25            // test
#6 = Class              #26            // intron/classtest/ClassTest
#7 = Class              #27            // java/lang/Object
  1. 初始化:爲類的靜態變量賦值,然後執行累的初始化語句(static代碼塊);
初始化的詳細過程:
  1. 如果類還沒有被加載和鏈接,那就先進行加載和鏈接;
  2. 如果類還存在父類,並且父類還沒有初始化,那就先初始化直接父類;
  3. 如果類中存在初始化語句,順序執行初始化語句;

類加載器

  類加載器是類加載流程的實現者,JDK自帶了三個類加載器:Bootstrap ClassLoader(引導類加載器)、Extension ClassLoader(擴展類加載器)、Application ClassLoader(應用類加載器)。

BootStrap ClassLoader:
  1. JVM自帶的引導類加載器,由C/C++的語言實現,是C++的對象,在Java中打印該加載器對象爲null;
  2. 加載Java的核心類庫,$JAVA_HOME中jre/lib/rt.jar、resource.jar或Java程序運行指定的Xbootclasspath選項jar包;
  3. 指定加載java、javax、sun等開頭的包名類,不能自定義這些包名;
Extension ClassLoader:
  1. Java語言編寫的類加載器:sun.misc.Launcher$ExtClassLoader;
  2. 指定BootStrap ClassLoader爲Parent加載器–> getParent()可以獲取Bootstrap ClassLoader;
  3. 負責加載Java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/ext或-Djava.ext.dirs指定目錄下的jar包(如果我們自定義的class需要交給Ext來加載可以放置在ext的目錄下);
Application ClassLoader:
  1. Java語言編寫的類加載器:sun.misc.Launcher$AppClassLoader;
  2. 該加載器是Java程序默認的類加載器,Java應用的類都是該加載器加載的;
  3. 指定Extension ClassLoader爲parent加載器–> getParent可以獲取Extension ClassLoader;
  4. 負責加載環境變量classpath指定的目錄,或者java.class.path指定的目錄類庫;
雙親委派機制:

  如果一個類加載器收到了加載類的請求,它並不會自己先去加載,而是把這個請求委託給父類加載器去執行,如果父類加載器還存在父類加載器,則進一步向上委託,依次遞歸,請求最後到達頂層的引導類加載器。如果父加載器能夠完成類的加載任務,就會成功返回,倘若父類加載器無法完成任務,子類加載器纔會嘗試自己去加載,這就是雙親委派模式。
雙親委派機制

雙親委派機制核心代碼:
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
            }

            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;
    }
}

  採用雙親委派模式的好處就是Java類隨着它的類加載器一起具備一種帶有優先級的層次關係,通過這種層級關係可以避免類的重複加載,當父類加載器已經加載了該類,子類加載器就不需要再加載一次。其次是考慮到安全因素,Java核心API中定義類型不會被隨意替換,如果我們人爲定義java.lang包並在包內添加一些自定義類,這樣做是不允許的,因爲java.lang是核心的API包,需要訪問權限,強制加載將會報出如下異常。

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