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

Java 基礎中一些值得聊的話題(加載篇)

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


來源:笨狐狸,

blog.csdn.net/liweisnake/article/details/17842877

在開始Java的類加載旅程之前,可以先參考這裡瞭解一些類加載器在Tomcat中的應用。

http://blog.csdn.net/liweisnake/article/details/8470285

在最初執行java這個命令時,便會呼叫 ClassLoader 的 getSystemClassLoader 方法顯式或者隱式加載 main 方法所在的類及其所取用的類。getSystemClassLoader 會傳回 AppClassLoader,後者是 URLClassLoader 的一個子類。

先有雞還是先有蛋?

所以,最初的一個問題是:先有雞還是先有蛋?因為 ClassLoader 的整套體系是打包在 jre/lib/rt.jar 中的。只有 rt.jar 先被加載進來,才能夠加載別的類;但是 rt.jar 又是被誰加載的呢?自然就是大名鼎鼎的 BootstrapClassLoader。它就是“雞”。所以嚴格來講,BootStrapClassLoader 並不是整個體系中的一部分(可以用 -Xbootclasspath 指定bootstrap 加載的位置)。

當 rt.jar 被加載進來後,ClassLoader 會呼叫 getSystemClassLoader,其中最重要的一步就是初始化 Launcher、ExtClassLoader 以及AppClassLoader,另外就是將 ContextClassLoader 設為 AppClassLoader。ExtClassLoader 與 AppClassLoader 都是 URLClassLoader 的子類,分別會加載 java.ext.dirs 和 java.class.path 路徑下的 jar資源,前者一般指向 jre/lib/ext 下的所有jar,後者就是我們經常念叨的classpath。區分這兩個 ClassLoader 的主要目的是,讓他們形成層級關係,ExtClassLoader 為 AppClassLoader 的父 ClassLoader,有了層級關係,便可隨意使用雙親委托模型了。

ClassLoader extcl;

        try {

            extcl = ExtClassLoader.getExtClassLoader();

        } catch (IOException e) {

            throw new InternalError(

                “Could not create extension class loader”);

        }

 

        // Now create the class loader to use to launch the application

        try {

            loader = AppClassLoader.getAppClassLoader(extcl);

        } catch (IOException e) {

            throw new InternalError(

                “Could not create application class loader”);

        }

 

        // Also set the context class loader for the primordial thread.

        Thread.currentThread().setContextClassLoader(loader);

ClassLoader究竟幹了什麼?

接下來一個比較重要的問題是ClassLoader究竟幹了什麼?通常我們只知道它加載了一個類進了jvm,但是具體做了什麼呢?

Java設計者把classloader加載一個類的過程分為4步:

  • 第一步,從某個地方得到我們想要的位元組碼二進制流;

  • 第二步,讀入位元組碼流並轉化為Class;

  • 第三步,鏈接;

  • 第四步,初始化。

其中,第二步一般比較固定,因此ClassLoader提供了defineClass來完成這步;

protected final Class > defineClass(String name, byte[] b, int off, int len,

                                         ProtectionDomain protectionDomain)

        throws ClassFormatError

    {

        protectionDomain = preDefineClass(name, protectionDomain);

 

        Class c = null;

        String source = defineClassSourceLocation(protectionDomain);

 

        try {

            c = defineClass1(name, b, off, len, protectionDomain, source);

        } catch (ClassFormatError cfe) {

            c = defineTransformedClass(name, b, off, len, protectionDomain, cfe,

                                       source);

        }

 

        postDefineClass(c, protectionDomain);

        return c;

    }

而 ClassLoader 提供了另一個方法 findClass 來完成第一步及第二步,即從某個地方讀入類的二進制流,然後呼叫 defineClass 傳回 Class

protected Class > findClass(final String name)

         throws ClassNotFoundException

ClassLoader提供了resolveClass方法完成第三步鏈接的工作。

protected final void resolveClass(Class > c)

除非特殊需要,否則儘量多載 findClass 而不是 loadClass。

loadClass 是 Java 1.0 就存在的類,為了增強可擴展性,將findClass和resolveClass封裝到了loadClass中,一般我們只需要定義類的加載路徑,因此僅需改寫findClass。

通常我們顯示加載類一般會用到ClassLoader.loadClass、Class.forName,他們的區別見這裡

http://blog.csdn.net/liweisnake/article/details/8857744

resolveClass做了什麼?

resolveClass最終呼叫了一個本地方法做link,這裡的link主要做了這麼幾步事情:

驗證Class以確保類裝載器格式和行為正確;

準備後續步驟所需的資料結構;

解析所取用的其他類。

關於這些內容的具體細節,請參考這裡

http://blog.csdn.net/ns_code/article/details/17881581

ClassNotFoundException、NoClassDefFoundError、ClassCastException常見問題

ClassNotFoundException一般發生在顯式類加載;NoClassDefFoundError一般發生在隱式加載;ClassCastException一般發生在jar包衝突,比如某個jar包已經被更上層的加載器加載了,但你卻要求他強制轉為下層加載器加載的同名類;

鏈接的相關知識

接下來講講鏈接的相關知識。

為什麼會發明聯結器?

最初程式員寫程式都在一個檔案里,隨著程式規模的增加,逐漸發現越來越難以維護,擴展。於是,分多個檔案和模塊就成為必然。

但是檔案間必然相互呼叫,這就帶來了另一個問題:檔案A取用了B的某個變數或方法,但是運行時A並不知道他們在記憶體中的位置。於是人們發明瞭鏈接,這種方式會在編譯階段將需要取用的變數或者方法作個記號,這時形成A.o和B.o兩個標的檔案;通過ld聯結器的鏈接,會將B中變數或方法的地址重新修改A的記號最終形成一個可執行檔案,實際上就是把A和B合在一起工作了。

這種方式就是靜態鏈接。

但是靜態鏈接有個問題,比如A需要B模塊的方法,C需要B模塊的變數,D需要B模塊的方法……如此一來,當我們編譯為A, C, D幾個可執行檔案時,他們都會在記憶體中取用B,即B在記憶體中有多份拷貝。這帶來很多問題:首先浪費了記憶體;其次修改了B模塊需要重新修改併發布A, C, D幾個可執行檔案,這是非常不方便的;於是動態鏈接的一個思想是在A, C, D呼叫時再確定記號的地址,而B則通過延遲加載的方式按需加載到記憶體(如果已經加載則不再重覆)。這樣一來,記憶體中總是只有一份B的拷貝,解決了上面的問題。Java的類加載機制即是這種靈活的方式。 

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

關註「ImportNew」,提升Java技能

赞(0)

分享創造快樂