想聊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類載入器的體系結構: 類載入邏輯程式碼:
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詳細的分析其中的類載入過程。畢竟理論總要落實到程式碼才會讓人印象深刻。
本文如有紕漏,歡迎指出。