1. 程式人生 > >JVM理論:(三/6)類加載器、雙親委派

JVM理論:(三/6)類加載器、雙親委派

rap 自定義 pan 類名 唯一性 sso 返回 工作過程 lse

一、類與類加載器

  允許類加載階段中的“通過一個類的全限定名來獲取描述此類的二進制字節流”這個動作可以讓應用程序自己決定如何去獲取所需要的類。實現這個動作的代碼模塊稱為“類加載器”。

  類加載器雖然只用於實現類的加載動作,但它在Java程序中起到的作用卻遠遠不限於類加載階段。對於任意一個類,都需要由加載它的類加載器和這個類本身一同確立其在Java虛擬機中的唯一性,每一個類加載器,都擁有一個獨立的類名稱空間。

  比較兩個類是否“相等”,只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則,即使這兩個類來源於同一個Class文件,被同一個虛擬機加載,只要加載它們的類加載器不同,那這兩個類就必定不相等。

這裏所指的“相等”,包括代表類的Class對象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結果,也包括使用instanceof關鍵字做對象所屬關系判定等情況。

二、雙親委派模型

  從Java虛擬機的角度來講,只存在兩種不同的類加載器:一種是啟動類加載器,使用C++語言實現,是虛擬機自身的一部分。另一種就是所有其他的類加載器,這些類加載器都由Java語言實現,獨立於虛擬機外部,並且全都繼承自抽象類java.lang.ClassLoader。

  從Java開發人員的角度來看,類加載器還可以劃分得更細致一些,絕大部分Java程序都會使用到以下3種系統提供的類加載器:

  1)啟動類加載器,這個類將器負責將存放在<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數所指定的路徑中的,並且是虛擬機識別的類庫加載到虛擬機內存中(僅按照文件名識別,如rt.jar,名字不符合的類庫即使放在lib目錄中也不會被加載)。啟動類加載器無法被Java程序直接引用,用戶在編寫自定義類加載器時,如果需要把加載請求委派給引導類加載器,那直接使用null代替即可

  2)擴展類加載器,負責加載<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫,開發者可以直接使用擴展類加載器

  3)應用程序類加載器,是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱它為系統類加載器。它負責加載用戶類路徑(ClassPath)上所指定的類庫,開發者可以直接使用這個類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器

  我們的應用程序都是由這3種類加載器互相配合進行加載的,如果有必要,還可以加入自己定義的類加載器。

雙親委派模型的層次關系

  雙親委派模型要求除了頂層的啟動類加載器外,其余的類加載器都應當有自己的父類加載器。這裏類加載器之間的父子關系一般不會以繼承的關系來實現,而是都使用組合關系來復用父加載器的代碼。

  下圖展示的類加載器之間的這種層次關系,稱為類加載器的雙親委派模型。並不是一個強制性的約束模型,而是Java設計者推薦給開發者的一種類加載器實現方式。

  技術分享圖片 

雙親委派模型的工作過程

  如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應該傳送到頂層的啟動類加載器中,只有當父加載器反饋自己無法完成這個加載請求(它的搜索範圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載。

雙親委派模型的好處

  有一個顯而易見的好處就是Java類隨著它的類加載器一起具備了一種帶有優先級的層次關系。

  例如類java.lang.Object,它存放在rt.jar之中,無論哪一個類加載器要加載這個類,最終都是委派給處於模型最頂端的啟動類加載器進行加載,因此Object類在程序的各種類加載器環境中都是同一個類。

  相反,如果沒有使用雙親委派模型,由各個類加載器自行去加載的話,如果用戶自己編寫了一個稱為java.lang.Object的類,並放在程序的ClassPath中,那系統中將會出現多個不同的Object類,Java類型體系中最基礎的行為也就無法保證,應用程序也將會變得一片混亂。

雙親委派模型代碼實現

  實現雙親委派的代碼都集中在java.lang.ClassLoader的loadClass()方法之中。代碼如下。

protected synchronized Class<?> loadClass(String name, Boolean resolve) throws ClassNotFoundException{
    //1 檢查請求的類是否已經被加載過  
    Class c = findLoadedClass(name);
    if(c == null){ 
        try{
            if(parent != null){ 
                //2 沒有則調用父類加載器的loadClass()方法;
                c = parent.loadClass(name, false);
            }else{  
                //3 若父類加載器為null,則使用啟動類加載器作為父加載器;
                c = findBootstrapClassOrNull(name);
            }
        }catch(ClassNotFoundException e){
            //4 父類加載器無法完成類加載請求,拋出ClassNotFoundException異常  
        }
        if(c == null){
            //5 在父類加載器無法加載時,再調用本身的findClass方法進行類加載
            c = findClass(name);
        }
    }
    if(resolve){
        resolveClass(c);
    }
    return c;
}

  邏輯為:先檢查是否已經被加載過,若沒有加載則調用父加載器的loadClass()方法,若父加載器為空則默認使用啟動類加載器作為父加載器。如果父類加載失敗,拋出ClassNotFoundException異常後,再調用自己的findClass()方法進行加載。

小例子

package java.lang;
public class String{
    public static void main(String[] args ){    
    }
}

輸出結果:
錯誤: 在類 java.lang.String 中找不到主方法, 請將主方法定義為:
   public static void main(String[] args)

  這裏的String類不是明明有main方法嗎?根據雙親委托模型java.lang.String最後會委托給啟動類加載器Bootstrap ClassLoader加載,Bootstrap ClassLoader只能加載JAVA_HOME\jre\lib中的class類(J2SE API),J2SE API中確實有一個java.lang.String,但這個類和我們自定義的類不同,Bootstrap ClassLoader以為找到了這個類,就開始加載J2SE API中的java.lang.String,但這個類是沒有main方法的。

參考鏈接:

  https://blog.csdn.net/lxlmycsdnfree/article/details/72841841

JVM理論:(三/6)類加載器、雙親委派