想聊Java的類加載機制就離不開Java類加載器不撑,這是Java語言的一個很重要的創(chuàng)新點臼勉,曾經(jīng)也是Java流行的重要原因邻吭。當初引入這個機制是為了滿足Java Applet開發(fā)的需求,簡單而言宴霸,就是為了能夠執(zhí)行從從遠程下載過來的的Java類囱晴,JVM咬咬牙引入了Java類加載機制膏蚓,后來的基于jvm的動態(tài)部署,插件化開發(fā)包括大家熱議的熱修復(熱修復其實也有不基于ClassLoader的解決方案畸写,有興趣請看我的熱修復初探)驮瞧,總之很多后來的技術都源于在JVM中引入了類加載器。
JVM:很慚愧艺糜,就做了一點微小的工作剧董,謝謝大家。
加載器
好了破停,講完了ClassLoader的來由翅楼,接下來可以正是介紹一下類加載器。如你所知真慢,當你寫完了一個.java文件的時候毅臊,編譯器會把他編譯成一個由字節(jié)碼組成的class文件,當程序運行時黑界,JVM會首先尋找包含有main()方法的類管嬉,把這個class文件中的字節(jié)碼數(shù)據(jù)讀入進來,轉(zhuǎn)化成JVM中運行時對應的Class對象朗鸠。執(zhí)行這個動作的蚯撩,就叫類加載器。
- ClassLoader:是Java層幾乎所有類加載器的父類烛占,它定義了加載器的基本行為和加載動作
分類
類加載器分為可以大致分為:
-
Bootstrap ClassLoader(啟動類加載器)
- 這個類加載器負責將一些核心的胎挎,被JVM識別的類加載進來,用C++實現(xiàn)忆家,與JVM是一體的犹菇。
-
Extension ClassLoader(擴展類加載器)
- 這個類加載器用來加載 Java 的擴展庫
-
Applicaiton ClassLoader(應用程序類加載器)
- 用于加載我們自己定義編寫的類
-
User ClassLoader (用戶自己實現(xiàn)的加載器)
- 當實際需要自己掌控類加載過程時才會用到,一般沒有用到芽卿。
與之配套的加載機制就是“雙親委派模型”:
雙親委派模型
先看看Java類加載器的體系結構:
類加載邏輯代碼:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
//首先檢查class是否已經(jīng)被加載
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//如果class沒有被加載且已經(jīng)設置parent,那么請求其父加載器加載
if (parent != null) {
/**
*注意當這里調(diào)用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都不能加載待加載的類
*那么則調(diào)用自己的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.先檢查需要加載的類是否已經(jīng)被加載卸例,如果沒有被加載称杨,則委托父加載器加載,父類繼續(xù)檢查筷转,嘗試請父類加載姑原,這個過程是從下-------> 上;
- 2.如果走到頂層發(fā)現(xiàn)類沒有被加載過,那么會從頂層開始往下逐層嘗試加載旦装,這個過程是從上 ------> 下;
需要注意的幾個問題:
- 1页衙,雙親XX 這種說法是有問題的,因為Java世界一直是單親家庭
- 2,事實上加載器之間不是通過繼承店乐,而是通過組合的方式來實現(xiàn)整個加載過程艰躺,即每個加載器都持有上層加載器的引用,所以父加載器是一種籠統(tǒng)的說法眨八。
這里必須要提一提JVM如何判定兩個類你是否相等:
- JVM除了比較類是否相等還要比較加載這兩個類的類加載器是否相等腺兴,只有同時滿足條件,兩個類才能被認定是相等的廉侧。
接下來問題來了页响,為什么雙親委派模型要有三層加載器而不是一層?
實際上段誊,三層類加載器代表了JVM對于待加載類的三個信任層次闰蚕,當需要加載一個全限定名為java.lang.Object的類時,JVM會首先信任頂層的引導類加載器连舍,即優(yōu)先用這個加載器嘗試加載没陡,如果不行,JVM會選擇繼續(xù)信任第二層的拓展類加載器索赏,往下盼玄,知道三層都無法加載,JVM才會選擇信任開發(fā)者自己定義的加載器潜腻。這種”父類“優(yōu)先的加載次序有效的防止了惡意代碼的加載埃儿。
總結
總而言之,雙親委派模型有效解決了以下問題:
- 每一個類都只會被加載一次融涣,避免了重復加載
- 每一個類都會被盡可能的加載(從引導類加載器往下童番,每個加載器都可能會根據(jù)優(yōu)先次序嘗試加載它)
- 有效避免了某些惡意類的加載(比如自定義了Java。lang.Object類暴心,一般而言在雙親委派模型下會加載系統(tǒng)的Object類而不是自定義的Object類)
tips:可以說雙親委派模型主要是為了維護Java類加載的安全妓盲,防止惡意加載杂拨,與此配套的還有命名空間出有效的隔離,命名空間的作用抽象理解就是
- 豎直方向上专普,父加載器中加載的類對于所有子加載器可見
- 水平方向上,子類之間各自加載的類對于各自是不可間的(達到隔離效果)
基本上弹沽,日常的開發(fā)使用的都是使用系統(tǒng)提供的類加載器依照“雙親委派模型”來加載的檀夹,開發(fā)者基本接觸不到加載過程。但是當你要動態(tài)加載自己的外部的類的時候策橘,比如從網(wǎng)絡上下載的class文件炸渡,就需要自定義classloader來實現(xiàn)加載過程。
在Android中丽已,QQZone團隊提出的基于Dex分包的熱修復解決方案就屬于加載外部的類蚌堵,本來應當由開發(fā)者自己實現(xiàn)classloader來實現(xiàn)加載過程,但是Android本身已經(jīng)為我們封裝好了一個classloader,就是DexClassLoader(貼心~~)
事實上吼畏,如今Java中很多插件化開發(fā)督赤,動態(tài)部署,熱修復等動態(tài)技術都是基于Java的類加載器來展開的泻蚊。因此躲舌,我才會想專門用一篇文章總結Java的類加載器和加載機制。后面我會找時間基于HotFix詳細的分析其中的類加載過程性雄。畢竟理論總要落實到代碼才會讓人印象深刻没卸。