歡迎光臨
每天分享高質量文章

Java 虛擬機器10:類載入器

(點選上方公眾號,可快速關註)


來源:五月的倉頡 ,

www.cnblogs.com/xrq730/p/4845144.html

類與類載入器

虛擬機器設計團隊把類載入階段張的”透過一個類的全限定名來獲取此類的二進位制位元組流”這個動作放到Java虛擬機器外部去實現,以便讓應用程式自己決定如何去獲取所需要的類。實現這個動作的程式碼模組稱為”類載入器”。類載入器雖然只用於實現類的載入動作,但它在Java程式中起到的作用卻遠遠不限定於類載入階段。對於任意一個類,都需要由載入它的類載入器和這個類本身一同確立其在Java虛擬機器中的唯一性,每一個類載入器,都擁有一個獨立的類名稱空間。這句話表達地再簡單一點就是:比較兩個類是否”相等”,只有在這兩個類是由同一個類載入器載入的前提下才有意義,否則即使這兩個類來源於同一個.class檔案,被同一個虛擬機器載入,只要載入它們的類載入器不同,這兩個類必定不相等。

上面說的”相等”,包括代表類的.class物件的equals()方法、isAssignableFrom()方法、isInstance()方法的傳回結果,也包括使用instanceof關鍵字做物件所屬關係判定等情況。

類載入器模型

從Java虛擬機器的角度講,只有兩種不同的類載入器:啟動類載入器Bootstrap ClassLoader,這個類載入器是由C++語言實現的,是虛擬機器自身的一部分;其他類載入器,這些類載入器都由Java語言實現,獨立於虛擬機器外部,並且全部繼承自java.lang.ClassLoader。從開發人員的角度講,類載入器還可以劃分地更加細緻一些,一張圖就能說明:

關於這張圖首先說兩點:

  1. 這三個層次的類載入器並不是繼承關係,而只是層次上的定義

  2. 它並不是一個強制性的約束模型,而是Java設計者推薦給開發者的一種類載入器實現方式

OK,然後一個一個類載入器來看:

1、啟動類載入器Bootstrap ClassLoader

之前說過了這是一個嵌在JVM核心中的載入器。它負責載入的是JAVA_HOME/lib下的類庫,系統類載入器無法被Java程式直接應用

2、擴充套件類載入器Extension ClassLoader

這個類載入器由sun.misc.Launcher$ExtClassLoader實現,它負責用於載入JAVA_HOME/lib/ext目錄中的,或者被java.ext.dirs系統變數指定所指定的路徑中所有類庫,開發者可以直接使用擴充套件類載入器。java.ext.dirs系統變數所指定的路徑的可以透過程式來檢視

public class TestMain

{

    public static void main(String[] args)

    {

        System.out.println(System.getProperty(“java.ext.dirs”));

    }

}

執行結果

E:\MyEclipse10\Common\binary\com.sun.java.jdk.win32.x86_64_1.6.0.013\jre\lib\ext;C:\Windows\Sun\Java\lib\ext

3、應用程式類載入器Application ClassLoader

這個類載入器由sun.misc.Launcher$AppClassLoader實現。這個類也一般被稱為系統類載入器,寫個小程式看下:

public class TestMain

{

    public static void main(String[] args)

    {

        System.out.println(ClassLoader.getSystemClassLoader());

    }

}

執行結果為:

sun.misc.Launcher$AppClassLoader@546b97fd

看到透過”ClassLoader.getSystemClassLoader”,得到的是sun.misc.Launcher$AppClassLoader,這也證明瞭JDK認為Application ClassLoader是系統類載入器。順便根據類載入器模型,列印一下這個類的父載入器:

public class TestMain

{

    public static void main(String[] args)

    {

        System.out.println(ClassLoader.getSystemClassLoader().getParent());

    }

}

執行結果為:

sun.misc.Launcher$ExtClassLoader@535ff48b

看出Application ClassLoader的父載入器確實是Extension ClassLoader,符合圖中的模型。那麼再列印父載入器呢?按照我們的想法應該是Bootstrap ClassLoader了,看下是不是:

public class TestMain

{

    public static void main(String[] args)

    {

        System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());

    }

}

執行結果為:

null

這會打印出來的是null了。其實也很好理解,Bootstrap ClassLoader以外的ClassLoader都是Java實現的,因此這些ClassLoader勢必在Java堆中有一份實體在,所以Extension ClassLoader和Application ClassLoader都能打印出內容來。但是Bootstrap ClassLoader是JVM的一部分,是用C/C++寫的,不屬於Java,自然在Java堆中也沒有自己的空間,所以就傳回null了。所以,如果ClassLoader得到的是null,那麼表示的ClassLoader就是Bootstrap ClassLoader。

另外要說很重要的一點,反編譯一下rt.jar,找到sun.misc.Launcher看一下Application ClassLoader的實現:

static class AppClassLoader extends URLClassLoader

  {

    public static ClassLoader getAppClassLoader(final ClassLoader paramClassLoader)

      throws IOException

    {

      String str = System.getProperty(“java.class.path”);

      final File[] arrayOfFile = str == null ? new File[0] : Launcher.getClassPath(str);

      return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction()

      {

        public Launcher.AppClassLoader run()

        {

          URL[] arrayOfURL = this.val$s == null ? new URL[0] : Launcher.pathToURLs(arrayOfFile);

          return new Launcher.AppClassLoader(arrayOfURL, paramClassLoader);

        }

      });

    }

重點就在第6行,Application ClassLoader只會載入java.class.path下的.class檔案,java.class.path代表的是什麼路徑?列印一下:

public class TestMain

{

    public static void main(String[] args)

    {

        System.out.println(System.getProperty(“java.class.path”));

    }

}

執行結果為:

F:\程式碼\MyEclipse\TestArticle\bin;F:\學習\第三方jar包\XStream\xstream-1.4.jar;F:\學習\第三方jar包\XStream\kxml2.jar

我這裡有新增兩個.jar到CLASSPATH下。那也可以下一個重要的結論了:

Application ClassLoader只能載入專案bin目錄下的.class檔案。

雙親委派模型

最後講一下雙親委派模型,其實上面的類載入器模型圖就是一個雙親委派樣式的圖,這裡把它再講清楚一點。

雙親委派模型是在JDK1.2期間被引入的,其工作過程可以分為兩步:

1、如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把這個請求委派給父類載入器去完成,每一個層次的類載入器都是如此。

2、只有當父載入器反饋自己無法完成這這個載入請求(它的搜尋範圍中沒有找到所需的類)時,子載入器才會嘗試自己去載入

所以,其實所有的載入請求最終都應該傳送到頂層的啟動類載入器中。雙親委派模型對於Java程式的穩定運作很重要,因為Java類隨著它的載入器一起具備了一種帶有優先順序的層次關係。例如java.lang.Object,存放於rt.jar中,無論哪一個類載入器要去載入這個類,最終都是由Bootstrap ClassLoader去載入,因此Object類在程式的各種類載入器環境中都是一個類。相反,如果沒有雙親委派模型,由各個類自己去載入的話,如果使用者自己編寫了一個java.lang.Object,並放在CLASSPATH下,那系統中將會出現多個不同的Object類,Java體系中最基礎的行為也將無法保證,應用程式也將會變得一片混亂。

看完本文有收穫?請轉發分享給更多人

關註「ImportNew」,提升Java技能

贊(0)

分享創造快樂