Java類加載機制-筆記3(類加載器的分類)

在Java類生命周期中,只有加載步驟中的讀取二進制流與初始化部分,能夠被上層開發(fā)者,也就是大部分的Java程序員控制。而剩下的所有步驟,都是由JVM掌控,其中細節(jié)由JVM的開發(fā)人員處理,對上層開發(fā)者來說是個黑盒。
為什么要這么做呢?
這是一種面向對象中開閉原則和封裝思想的設計。JVM 將類加載內部復雜的實現(xiàn)封裝了起來,拒絕上層開發(fā)者修改,只提供了一個擴展接口,用于class文件二進制流的讀取。而就是這么一點點的活動空間,也成了上層開發(fā)者施展拳腳的舞臺,比如說基于該特性實現(xiàn)的 動態(tài)代理、熱部署等等功能。

Java的類加載器

類加載器的分類屬于JVM規(guī)范,是一種抽象的概念,各個不同的JVM實現(xiàn)的方式是不一定一樣的,JVM規(guī)范中類加載器分為兩大類

  • 啟動類加載器
  • 非啟動類加載器
以HotSpot虛擬機實現(xiàn)為例

在HotSpot中,Bootstrap ClassLoader是采用C/C++來實現(xiàn)的,是嵌套在了JVM內部,無法作為對象來被程序所引用,它主要用來加載Java的核心類庫,比如說 <JAVA_HOME>/lib 路徑下的jar包,或者是由啟動參數(shù)來指定路徑下的核心類庫,此外,為了安全性Bootstrap ClassLoader只加載了包名在白名單中的文件,比如說由java,javax,sun開頭的包,非Bootstrap ClassLoader又分為三類。分別為:

  • Extension ClassLoader
  • Application ClassLoader
  • User ClassLoader

在 HotSpot中,非Bootstrap ClassLoader都采用java來實現(xiàn),他們都繼承自 java.lang.ClassLoader 這個類,他們可以作為對象被引用,那么接下來我們來介紹這三種類加載器的區(qū)別:

  • Extension ClassLoader 是由Launcher 的內部類ExtClassLoader來實現(xiàn)的,主要來加載<JAVA_HOME>/lib/ext 目錄下,或者是由系統(tǒng)變量指定的路徑中的類庫。Extension ClassLoader希望加載的是javaApi的擴展,是對java類庫的一些補充能力。
  • Application ClassLoader 是由Launcher 的內部類AppClassLoader來實現(xiàn)的,主要來加載環(huán)境變臉classpath或者是系統(tǒng)屬性指定路徑下的類庫,Application ClassLoader 希望加載的是上層程序員編寫的代碼以及一些第三方的類庫,我們平時編寫的代碼基本上都是由Application ClassLoader 來加載的。

問題:我們可不可以用Extension ClassLoader 來加載我們自己寫的代碼呢?

可以這么做,但是完全沒有必要,這種操作不符合規(guī)范,工程項目是講究分層和抽象的,就好像你可以把一個項目的代碼都寫在一個文件里,但是是不推薦這么做的。

  • User ClassLoader 用戶自己編寫的類加載器,上面提及到的幾種類加載器,都只能從本地文件中獲取字節(jié)碼來進行加載。而User ClassLoader可以讓用戶能夠獲取任何來源的字節(jié)碼,并對他們進行加載,這就印證了在java類加載機制中允許用戶從各個渠道獲取class文件的二進制流來進行加載的結論。如果要編寫一個User ClassLoader,其實非常簡單,我們前面說到在HotSpot中,非Bootstrap ClassLoader都是采用java來實現(xiàn)的,他們都繼承自java.lang.ClassLoader這個類,用戶自定義的類加載器,其實只需要繼承java.lang.ClassLoader ,然后單獨實現(xiàn)獲取二進制流的邏輯,而后續(xù)的步驟必須讓java.lang.ClassLoader作為內置的邏輯來處理,用戶無權進行重寫和干涉。

問題:

  1. 不同的類加載器,除了讀取二進制流的動作和范圍不一樣,后續(xù)的加載器邏輯是否也不一樣?
  2. 遇到限定名一樣的類,這么多類加載器會不會產生混亂?

JVM規(guī)范中,規(guī)定:每個類加載器都有屬于自己的命名空間
即使你用不同的類加載器加載了同一個限定名的類,那么JVM也會認為這是兩個不同的類,深入理解JVM這本書中,有這樣的一個例子:

image.png
public class Main {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = getClass().getResourceAsStream(fileName);
                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    e.printStackTrace();
                    throw new ClassNotFoundException(name);
                }
            }
        };
        Object obj = myLoader.loadClass("com.example.demo0413.test.A").newInstance();
        System.out.println(obj.getClass());
        System.out.println(obj instanceof A);
    }
}

可以看到我們首先使用匿名類的方式實現(xiàn)了一個User ClassLoader ,我們之前所說的,在HotSpot中所有非Bootstrap ClassLoader 都是java.lang.ClassLoader的子類,那么我們這里就重寫了它的loadClass 方法,loadClass方法的內容呢就是直接從文件中讀取class文件,然后調用父類的defineClass來進行后續(xù)的加載操作,最后使用加載輸出的 class 來進行對象的實例化,并且對產生的對象進行類型判斷,我們可以看到對象的類限定名是一樣的,但是instanceof 的結果卻輸出了false,看上去很奇怪,這是因為我們自定義的這個類分別被默認的Application ClassLoader 和我們自定義的User ClassLoader加載了,在最后的判斷中,JVM認為這是兩個完全不同的Class,這就印證了我們上面提到的每個類加載器都屬于自己的命名空間這一點。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容