前言
雙親委派模型是Java載入類的機制.採用雙親委派模型的好處是Java類隨著它的類載入器一起具備了一種帶有優先順序的層級關係,通過這種層級關係可以避免類的重複載入.
1. 模型基礎
- Bootstrap ClassLoader(啟動類載入器): 負責將%JAVA_HOME%/lib目錄中或-Xbootclasspath中引數指定的路徑中的,並且是虛擬機器識別的(按名稱)類庫載入到JVM中
- Extension ClassLoader(擴充套件類載入器): 負責載入%JAVA_HOME%/lib/ext中的所有類庫
- Application ClassLoader(應用程式載入器): 負責ClassPath中的類庫
2. 為什麼使用雙親委派模型?
1.雙親委派模型最大的好處就是讓Java類同其類載入器一起具備了一種帶優先順序的層次關係。這句話可能不好理解,我們舉個例子。比如我們要載入java.lang.Object類,無論我們用哪個類載入器去載入Object類,這個載入請求最終都會委託給Bootstrap ClassLoader,這樣就保證了所有載入器載入的Object類都是同一個類。如果沒有雙親委派模型,那就亂了套了,完全可能搞出多個不同的Object類。
2.自上而下每個類載入器都會盡力載入.
3. 看看原始碼
1.首先載入類呼叫的loadClass方法,我們找到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;
}
}複製程式碼
- 首先判斷了該類是否已載入.
- 若沒載入,則傳給雙親載入器去載入,
- 若雙親載入器沒能成功載入它,則自己用findClass()去載入.所以是個向上遞迴的過程.
- 自定義載入器時,需要重寫findClass方法,因為是空的,沒有任何內容:
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}複製程式碼
4. 自己動手,編寫一個自己的類載入器
1.首先需要一個編譯好的class檔案,筆者用了一個之前寫的斐波那契的類Fib.class(所在路徑:C:/Users/Think/crabapple),下面是用idea通過反編譯方式開啟的class檔案,注意記下class檔案的包名,在後續程式碼中需要使用類的全限定名稱.
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package crabapple;
public class Fib {
public static int fib(int num) {
return num < 2 ? num : fib(num - 2) + fib(num - 1);
}
}複製程式碼
2.繼承ClassLoader,重寫findClass方法:
class MyClassLoader extends ClassLoader {
private String classPath; // 儲存的地址
/**
* 傳入地址建構函式
* @param classPath
*/
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
/**
* 讀取class檔案
* @param name
* @return
* @throws Exception
*/
private byte[] loadByte(String name) throws Exception {
String inPath = classPath + "/" + name + ".class";
FileInputStream fis = new FileInputStream(inPath);
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
/**
* 重寫findClass方法,讓載入的時候呼叫findClass方法
* @param name
* @return
* @throws ClassNotFoundException
*/
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
// 將位元組碼載入記憶體
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}複製程式碼
- loadByte方法僅用作讀取檔案
- findClass方法才是載入類到記憶體的,注意name必須填全限定名,比如java.lang.Object.
3.測試,一下將使用一些反射機制和class類的方法.
public class ClassLoaderTest extends ClassLoader {
//main函式本該丟擲異常有 ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException,為了好看,簡寫成Exception
public static void main(String[] args) throws Exception {
//初始化類載入器
MyClassLoader myClassLoader=new MyClassLoader("C:/Users/Think/crabapple");
//載入Fib類,筆者class檔案包名為crabapple
Class myClass=myClassLoader.loadClass("crabapple.Fib");
//獲取載入類的例項
Object object=myClass.newInstance();
//獲取該類一個名為fib,且引數為int的方法
Method method=myClass.getMethod("fib",int.class);
//執行這個方法
int result=method.invoke(object,4);
//列印結果
System.out.print(result);
//output
/**
* 3
* Process finished with exit code 0
*/
}
}複製程式碼
- 執行成功
- 我們來分析下,Fib類的載入過程,初始化自定義類載入器後,loadClass方法肯定將其委派到雙親Application ClassLoader,而Application ClassLoader又將其委派到Extension ClassLoader,繼而委派到Bootstrap ClassLoader.但是Bootstrap ClassLoader發現Fib並不在自己的載入能力範圍內,於是移向Extension ClassLoader,同理Extension ClassLoader只能載入/ext中的class,繼而讓給Application ClassLoader,而Application ClassLoader只載入classpath中的類,於是又回到我們自定義的MyClassLoader,幸好我們重寫了findClass方法進而執行了載入,否在findClass丟擲找不到類的異常.至此Fib類載入完成.
JVM系列:
最後
後續會持續更新效能優化專題知識,寫的不好的地方也希望大牛能指點一下,大家覺得不錯可以點個贊在關注下,以後還會分享更多文章!
在這給大家推薦一個微信公眾號,那裡每天都會有技術乾貨、技術動向、職業生涯、行業熱點、職場趣事等一切有關於程式設計師的內容分享。更有海量Java架構、移動網際網路架構相關原始碼視訊,面試資料,電子書籍截止於4月28日免費發放。我看了覺得資源還不錯,如果你們有需要的話,掃描下方二維碼關注wx公眾號免費獲取↓↓↓
資源大本營↓↓↓
Java架構資料
Java原始碼解析,到各種框架學習,再到專案實戰,一應俱全,包括但不限於:Spring、Mybatis等原始碼、Java進階、Java架構師、虛擬機器、效能優化、併發程式設計、資料結構和演算法。