java classloader原理深究

來源:互聯網
上載者:User

標籤:

java classloader原理深究

前面已經寫過一篇關於java classloader的拙文java classloader原理初探。

時隔幾年,再看一遍,覺得有些地方顯得太過蒼白,於是再來一篇:

完成一個Java類之後,經過javac編譯,會產生一個class檔案,這個class檔案中包含跟這個類相關的所有基本資料:屬性欄位,方法等。這些都屬於一個類的中繼資料,是不變的部分。在執行過程,則需要根據類的中繼資料資訊產生一個執行個體對象,這個執行個體對象可以根據不同情境擁有不同狀態。也就是說同一個class對應了運行過程中的不同狀態。(請注意這裡是class,不是object)每一個java檔案在被編譯之後,編譯器都會給加入一個public的靜態欄位叫:class。在程式中,我們就可以通過SomeObject.class的方式來擷取一個Class對象。
Java中通過包路徑和類名來確定一個類,通過java api我們知道Class中有個方法是getClassLoader(),但如果一個類被兩個不同classloader載入, 這個方法就會返回不同結果,在這種情況下,雖然是同一個class,但被不同class loader載入,那對應的Class對象就不是同一個。

為什麼要設定環境變數:JAVA_HOME, CLASSPATH?JAVA_HOME對應的是java的安裝根路徑,為了能在系統中隨處都能用到java給我們提供的命令列工具,所以要把JAVA_HOME/bin的路徑添加到PATH中。但是CLASSPATH呢,為什麼需要在CLASSPATH中指定那三個jar(tools.jar, dt.jar, rt.jar)?
先說明一下tools.jar主要是一些java 工具類,可以從openjdk上下載jdk的源碼,找langtools這個路徑下的檔案來看看, 如果想寫一些工具類,比如通過java來分析或編譯java檔案,可以查看一下tools裡面的javac相關的api;dt.jar主要是swing相關的一些類。而rt.jar則是運行時相關的,是java的核心庫中的java類。
再來理解一下java在啟動時類的代理模式載入順序。JAVA中除了Bootstrap class loader 都有一個parent class loader。參閱java 官方對於“Understanding Extension Class Loading”的說明。裡面提到了三個方面的載入內容:一是rt.jar和i18n.jar等基礎類包;二是擴充包;三就是我們classpath指定的依賴包。對於第一部分,由Bootstrap class loader負責載入,所以java包中的類的class loader就是bootstrap class loader,而擴充路徑(一般是在jre/lib/ext)下由 extension class loader(ExtClassLoader)負責. 第三部分的載入就需要應用class loader(AppClassLoader)來負責了。一般情況下,直接通過java啟動時,會註明一下-classpath 或 -cp來標識出依賴包列表(注意,不是一個路徑,而是檔案清單)。代碼中負責初始化相關class loader的過程是在sun.misc.Launcher類中, 下面是Launcher的constructor方法中的實現。
public Launcher() {
        // Create the extension class loader
        ClassLoader extcl;
        try {
            extcl = ExtClassLoader.getExtClassLoader();
        } catch (IOException e) {
            throw new InternalError(
                "Could not create extension class loader", e);
        }

        // 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", e);
        }

        // Also set the context class loader for the primordial thread.
        Thread.currentThread().setContextClassLoader(loader);
…..
}
對於前面提到的相關載入路徑也都在這個類中有相關代碼,不一一列舉。

Java Api中的ClassLoader類中的loadClass方法:
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        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
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

載入某個類時,當前classloader都會先委託parent class loader嘗試載入。這種處理方式,有幾個方面的考慮:一是安全問題,如果攻擊者類比實現了對應的類如java.lang.String, 當通過loadClass來載入類時,會首先到parent classloader中尋找,很明顯可以在bootstrap class loader中找到,這樣可以儘可能保護最關鍵的代碼。一是冗餘問題,通過這種方式可以最大限度的降低重複載入。每個層次的classloader負責對應路徑下的類庫載入,而對於應用實現來說就可以集中在應用系統中。這裡可以考慮一下web container的實現,如tomcat。每個jsp頁面都會最終被編譯成一個class檔案,並放到work路徑中,所以每個web容器都會自建一個classloader來載入指定路徑中的class檔案。

通過上面的說明,可以瞭解到jvm在啟動過程中,如果發現有相同包路徑的情況(不同jar,但相同包)下的同名類,則僅會載入一次,主要看哪個包在最前面。這裡有個問題:為什麼是在前面的包可以起作用,後面不行?如果是由同一個classloader載入還不會被覆蓋嗎?其實如果是自己實現的classloader,那就可以調整這種策略,但java中約定的就是先載入的class會根據起binary name,將class metadata存於永久區,於是後面再有同名的類,都可以被找到,而不需要重新載入。 

java classloader原理深究

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.