Java 類載入器以及載入機制

拉丁吳發表於2016-09-03

想聊Java的類載入機制就離不開Java類載入器,這是Java語言的一個很重要的創新點,曾經也是Java流行的重要原因。當初引入這個機制是為了滿足Java Applet開發的需求,簡單而言,就是為了能夠執行從從遠端下載過來的的Java類,JVM咬咬牙引入了Java類載入機制,後來的基於jvm的動態部署,外掛化開發包括大家熱議的熱修復(熱修復其實也有不基於ClassLoader的解決方案,有興趣請看我的熱修復初探),總之很多後來的技術都源於在JVM中引入了類載入器。

JVM:很慚愧,就做了一點微小的工作,謝謝大家。

載入器

好了,講完了ClassLoader的來由,接下來可以正是介紹一下類載入器。如你所知,當你寫完了一個.java檔案的時候,編譯器會把他編譯成一個由位元組碼組成的class檔案,當程式執行時,JVM會首先尋找包含有main()方法的類,把這個class檔案中的位元組碼資料讀入進來,轉化成JVM中執行時對應的Class物件。執行這個動作的,就叫類載入器。

  • ClassLoader:是Java層幾乎所有類載入器的父類,它定義了載入器的基本行為和載入動作

分類

類載入器分為可以大致分為:

  • Bootstrap ClassLoader(啟動類載入器)
    • 這個類載入器負責將一些核心的,被JVM識別的類載入進來,用C++實現,與JVM是一體的。
  • Extension ClassLoader(擴充套件類載入器)
    • 這個類載入器用來載入 Java 的擴充套件庫
  • Applicaiton ClassLoader(應用程式類載入器)
    • 用於載入我們自己定義編寫的類
  • User ClassLoader (使用者自己實現的載入器)
    • 當實際需要自己掌控類載入過程時才會用到,一般沒有用到。

與之配套的載入機制就是“雙親委派模型”:

雙親委派模型

先看看Java類載入器的體系結構: classloader.jpg 類載入邏輯程式碼:

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            //首先檢查class是否已經被載入
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //如果class沒有被載入且已經設定parent,那麼請求其父載入器載入
                    if (parent != null) {
                        /**
                         *注意當這裡呼叫parent.loadClass()方法找不到Class時會丟擲ClassNotFoundException異常,但是該異常是被捕獲的
                         */
                        c = parent.loadClass(name, false);
                    } else {
                      //如果沒有設定parent類載入器,則尋找BootstrapClss並嘗試使用Boot loader載入
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                /**
                 *如果當前這個loader所有的父載入器以及頂層的Bootstrap ClassLoader都不能載入待載入的類
                 *那麼則呼叫自己的findClass()方法來載入
                 */                
                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;
        }
    }複製程式碼

“雙親委派模型”簡單來說就是:

  • 1.先檢查需要載入的類是否已經被載入,如果沒有被載入,則委託父載入器載入,父類繼續檢查,嘗試請父類載入,這個過程是從下-------> 上;
  • 2.如果走到頂層發現類沒有被載入過,那麼會從頂層開始往下逐層嘗試載入,這個過程是從上 ------> 下;

需要注意的幾個問題:

  • 1,雙親XX 這種說法是有問題的,因為Java世界一直是單親家庭
  • 2,事實上載入器之間不是通過繼承,而是通過組合的方式來實現整個載入過程,即每個載入器都持有上層載入器的引用,所以父載入器是一種籠統的說法

這裡必須要提一提JVM如何判定兩個類你是否相等:

  • JVM除了比較類是否相等還要比較載入這兩個類的類載入器是否相等,只有同時滿足條件,兩個類才能被認定是相等的。

接下來問題來了,為什麼雙親委派模型要有三層載入器而不是一層?

實際上,三層類載入器代表了JVM對於待載入類的三個信任層次,當需要載入一個全限定名為java.lang.Object的類時,JVM會首先信任頂層的引導類載入器,即優先用這個載入器嘗試載入,如果不行,JVM會選擇繼續信任第二層的擴充類載入器,往下,知道三層都無法載入,JVM才會選擇信任開發者自己定義的載入器。這種”父類“優先的載入次序有效的防止了惡意程式碼的載入。

總結

總而言之,雙親委派模型有效解決了以下問題:

  • 每一個類都只會被載入一次,避免了重複載入
  • 每一個類都會被儘可能的載入(從引導類載入器往下,每個載入器都可能會根據優先次序嘗試載入它)
  • 有效避免了某些惡意類的載入(比如自定義了Java。lang.Object類,一般而言在雙親委派模型下會載入系統的Object類而不是自定義的Object類)

tips:可以說雙親委派模型主要是為了維護Java類載入的安全,防止惡意載入,與此配套的還有名稱空間出有效的隔離,名稱空間的作用抽象理解就是

  • 豎直方向上,父載入器中載入的類對於所有子載入器可見
  • 水平方向上,子類之間各自載入的類對於各自是不可間的(達到隔離效果)

基本上,日常的開發使用的都是使用系統提供的類載入器依照“雙親委派模型”來載入的,開發者基本接觸不到載入過程。但是當你要動態載入自己的外部的類的時候,比如從網路上下載的class檔案,就需要自定義classloader來實現載入過程。

在Android中,QQZone團隊提出的基於Dex分包的熱修復解決方案就屬於載入外部的類,本來應當由開發者自己實現classloader來實現載入過程,但是Android本身已經為我們封裝好了一個classloader,就是DexClassLoader(貼心~~)

事實上,如今Java中很多外掛化開發,動態部署,熱修復等動態技術都是基於Java的類載入器來展開的。因此,我才會想專門用一篇文章總結Java的類載入器和載入機制。後面我會找時間基於HotFix詳細的分析其中的類載入過程。畢竟理論總要落實到程式碼才會讓人印象深刻。

本文如有紕漏,歡迎指出。

相關文章