Java 類的加載

眾所周知,一個.java文件中所寫的代碼,需要經由編譯器(Javac)進行編譯,獲得.class二進制字節碼文件。而后在運行期間,JVM將使用到的類從.class文件中加載到內存成為java.lang.Class類的一個實例,就可以被JVM進行使用了。

對我這樣的小白來說,僅憑大學時候學的編譯原理還不足以說清其中奧秘。今天想說的是關于類的加載過程,不同(何謂不同?)的類是通過何種方式進行加載的。

0. 不同的類

先拋出一個問題,何謂不同的類?分類方式有多種,既然說加載,就從加載的角度區分。

第一類:Java基礎類庫(rt.jar)

在$JAVA_HOME/jre/lib目錄下存放著rt.jar包,如果用壓縮軟件打開便能看到其中保存著許多java語言的基礎類。

rt.jar

第二類:Java擴展類庫(ext庫)

在$JAVA_HOME/jre/lib/ext目錄下 ,可以看到有很多jar包,他們是作為Java的擴展類庫存放起來的。

ext/*.jar

第三類:自建類

自建類,即是我們在編輯器中自己寫的.java文件對應的類。

第四類:其他類

這個類型和上面就有一些不一樣了,我們暫時將非上述三種類作為其他類。可能是存放在一個本地目錄中jar包內部的類,也可能是存放在一個遠端服務器中的類,等等。

以上便是從類加載的角度,先對類進行了一定的區分。


1. 不同的類加載器(ClassLoader)

一個Java類需要通過類加載器進行加載動作后,才能被后續的代碼使用。
其實根據上面對類的分類,大家應該能猜到類加載器也會是四種:

  • 引導加載器Bootstrap ClassLoader

  • 擴展加載器Extension Class Loader

  • 系統應用加載器APP Class Loader

  • 自定義加載器Custom Class Loader

引導加載器Bootstrap ClassLoader

什么是Bootstrap?

Tall boots may have a tab, loop or handle at the top known as a bootstrap, allowing one to use fingers or a boot hook tool to help pulling the boots on.
--- From Wikipedia

我在很多地方見過這個單詞,字面意思就是“靴靽”、“拔靴帶”。下圖中靴子上面的有黃色字樣的小帶子即是bootstrap。

Bootstrap

那在計算機領域Bootstrap到底意味著什么呢?

In general, bootstrapping usually refers to a self-starting process that is supposed to proceed without external input. In computer technology the term usually refers to the process of loading the basic software into the memory of a computer after power-on or general reset, especially the operating system which will then take care of loading other software as needed.
--- From Wikipedia

說白了就是世界的拓荒者,混沌初開的始祖元神。那么這種類加載器必然是需要加載Java基礎類庫中的類了。
BootstrapClassLoader是用C/C++語言編寫的,JVM會通過特殊方式對其先進行加載,而后通過該加載器進行其他基礎類的加載。它也可用于加載-Xbootclasspath參數指定的路徑中的類。

擴展加載器

由BootstrapClassLoader喚起來加載Java擴展類庫中的類。還可以加載-D java.ext.dirs選項指定的目錄。

系統應用加載器

加載當前項目中$CLASSPATH目錄下的類

自定義加載器

用戶可以對ClassLoader進行extends,而后實現自己定制的ClassLoader。

2. 類加載器的關系-雙親委派模型

先上一張老圖(from http://blog.csdn.net/gjanyanlig/article/details/6818655/):

雙親委派模型

按照上圖的順序,一層層地進行父親(parent)的設置,即Custom ClassLoader的parent是App ClassLoader,以此類推。
注意:此處其實是進行兒子類中的變量(parent)的設置,而不是類的繼承關系。所以只能稱其為“父親類”和“兒子類”,而不能稱為“父類”和“子類”。

而上面的圖其實表達的就是ClassLoader在進行類加載時的源碼:

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 先檢查該類是否已經被加載
            // 此方法的實現已附在后面
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        //  調用父親的loadClass方法
                        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
                    PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    protected final Class<?> findLoadedClass(String name) {
        if (!checkName(name))
            return null;
        return findLoadedClass0(name);
    }

    private final native Class<?> findLoadedClass0(String name);

可以發現這段代碼的要點為:

  1. 由子至父進行查找當前類是否已經被加載,即遞歸調用父親的loadClass方法。
  2. 當某個類加載器中已找到對應類,則停止進行find。
  3. 如果一直未找到對應類,遞歸調用會直到某個加載器類的parent為null停止,這也表示Bootstrap ClassLoader的parent其實是為null的。
  4. 由于是遞歸調用,所以當一直為找到對應類時,首先嘗試進行類加載的是Bootstrap,調用的主要方法即是findClass函數。
  5. 如果父親類沒有完成加載的話,才會一層層讓兒子類去嘗試加載,即自父至子的嘗試加載類。

關于Java類加載的機制其實有很多靈活的利用方式,可以參見文章http://blog.csdn.net/briblue/article/details/54973413 ,我這里便不再進行車輪的重造。

關于Java 9

另外想提一點的是Java 9的問世,對以上知識點進行了一些改動:
將ClassLoader分為三個級別:

  • Bootstrap Loader具有最高優先級和權限,主要是核心的系統類;
  • Platform Loader用于擴展的一些系統類,例如SQL,XML等;
  • Application Loader主要用于應用程序的Loader。

在這三個級別的Loader下面有一個統一Module 管理,用于控制和管理模塊間的依賴關系,可讀性,可訪問性等。可以查看BuildinClassLoader進行深入的學習。另外推薦一篇簡書的文章:http://www.lxweimin.com/p/b133abd54d27, 敘述了很多Java 9的新特性,值得關注。

現階段我也在一點點地學習當中,把所學寫下來,這一過程也讓我對很多細節有了更深刻的認識。繼續努力,堅持下去!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容