定義:類加載器
ClassLoader作用:把class加載到jvm,程序可正常運行。
jvm啟動時,并不會一次性加載所有的class,而是選擇動態選擇加載(防止一次性加載太多,內存壓力太大)。
Java語言自帶的三個類加載器:
Bootstrap ClassLoader :
最頂層加載器,主要加載核心類庫(%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等)。
Extention ClassLoader:
擴展類加載器(%JRE_HOME%\lib\ext目錄下的jar包和class文件)
Appclass Loader:
也稱為SystemAppClass ,加載當前應用的classpath的所有類
執行順序:從上到下依次執行。
通過sun.misc.Launcher類的源碼,可以的到相關信息:
1,Launcher初始化了ExtClassLoader和AppClassLoader。
2,Launcher中并沒有看見BootstrapClassLoader,但通過System.getProperty("sun.boot.class.path")得到了字符串bootClassPath,這個應該就是BootstrapClassLoader加載的jar包路徑。這個路徑的內容全是jre目錄下的jar與class文件
BootstrapClassLoader 測試
System.out.println(System.getProperty("sun.boot.class.path"));
得到:
C:\Program Files\Java\jre1.8.0_91\lib\resources.jar;
C:\Program Files\Java\jre1.8.0_91\lib\rt.jar;
C:\Program Files\Java\jre1.8.0_91\lib\sunrsasign.jar;
C:\Program Files\Java\jre1.8.0_91\lib\jsse.jar;
C:\Program Files\Java\jre1.8.0_91\lib\jce.jar;
C:\Program Files\Java\jre1.8.0_91\lib\charsets.jar;
C:\Program Files\Java\jre1.8.0_91\lib\jfr.jar;
C:\Program Files\Java\jre1.8.0_91\classes
ExtClassLoader 測試
System.out.println(System.getProperty("java.ext.dirs"));
得到:
C:\Program Files\Java\jre1.8.0_91\lib\ext;C:\Windows\Sun\Java\lib\ext
AppClassLoader 測試
System.out.println(System.getProperty("java.class.path"));
打印得到:
D:\workspace\ClassLoaderDemo\bin
每個類加載器都有一個父加載器
ClassLoader cl = Test.class.getClassLoader();
System.out.println("ClassLoader is:"+cl.toString());
System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());
得到:
ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@15db9742
AppClassLoader的父加載器是ExtClassLoader,
可以通過getParent()方法獲取。
父加載器不是父類
static class ExtClassLoader extends URLClassLoader {}
static class AppClassLoader extends URLClassLoader {}
ExtClassLoader和AppClassLoader同樣繼承自URLClassLoader。
getParent()方法在ClassLoader當中。
getParent分為兩種情況:
1.如果構造方法當中有賦值指定,則parent為指定。
2.如果創建時沒有指定,則通過Launcher.getClassLoader獲取,則默認為AppClassLoader。
AppClassLoader的parent是ExtClassLoader,ExtClassLoader的parent是null.
Bootstrap ClassLoader是由C/C++編寫,并不是一個java類,無法在java代碼中獲得引用。它沒有父類也就是沒有父容器,它可以作為ClassLoader的父加載器。
雙親委托:
雙親委托
一個類加載器加載class或resouces時,是通過委托模式。
首先判斷是否已經加載成功,
如果么有(先不自己找),通過父加載器,然后遞歸下去,直到BootstrapClassLoader,
找到直接返回,
沒有找到則一級級(ExtClassLoader->AppClassLoader-->...)返回直到找到對象。
- 一個AppClassLoader查找資源時,先看看緩存是否有,緩存有從緩存中獲取,否則委托給父加載器。
- 遞歸,重復第1部的操作。
- 如果ExtClassLoader也沒有加載過,則由Bootstrap ClassLoader出面,它首先查找緩存,如果沒有找到的話,就去找自己的規定的路徑下,也就是sun.mic.boot.class下面的路徑。找到就返回,沒有找到,讓子加載器自己去找。
- Bootstrap ClassLoader如果沒有查找成功,則ExtClassLoader自己在java.ext.dirs路徑中去查找,查找成功就返回,查找不成功,再向下讓子加載器找。
- ExtClassLoader查找不成功,AppClassLoader就自己查找,在java.class.path路徑下查找。找到就返回。如果沒有找到就讓子類找,如果沒有子類會怎么樣?拋出各種異常。
加載詳情圖:
重要方法:
loadClass()
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,檢測是否已經加載
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//父加載器不為空則調用父加載器的loadClass
c = parent.loadClass(name, false);
} else {
//父加載器為空則調用Bootstrap Classloader
//這也解釋了ExtClassLoader的parent為null,但仍然說Bootstrap ClassLoader是它的父加載器。
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();
//父加載器沒有找到,則調用findclass
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(),生成最終的Class對象。
resolveClass(c);
}
return c;
}
}
上面的代碼也解釋了雙親委托機制。
自定義classLoader建議:覆蓋findClass()方法,而不要直接改寫loadClass()方法。
用途:
這些類加載器默認只能加載指定目錄的jar或資源文件。當你想要突破內置加載器的限制時,可以玩出花樣來。
如果要想動態加載某些磁盤的資源,或通過網絡下載class后加載,這樣就可以自定義classLoader去做這些事。
步驟:
1. 編寫一個類繼承自ClassLoader抽象類。
2. 復寫它的findClass()方法。
3. 在findClass()方法中調用defineClass()。
defineClass()能將class二進制內容轉換成Class對象,如果不符合要求的會拋出各種異常。
注意點:一個ClassLoader在創建時如果沒有指定parent,則parent默認為AppClassLoader,這樣就能夠保證它能訪問系統內置加載器加載成功的class文件。
常見用途:將一個class用特定規則加密,然后在自定義的ClassLoader進行解密后在程序中加載使用。只有在我們自定義的加載器里能解密,提高了程序安全性。
Context ClassLoader 線程上下文類加載器
每個Thread都有一個相關聯的ClassLoader,默認是AppClassLoader。并且子線程默認使用父線程的ClassLoader除非子線程特別設置。(Thread.currentThread().setContextClassLoader(xxxLoader);)
總結:
1. ClassLoader用來加載class文件的。
2. 系統內置的ClassLoader通過雙親委托來加載指定路徑下的class和資源。
3. 可以自定義ClassLoader一般覆蓋findClass()方法。
4. ContextClassLoader與線程相關,可以獲取和設置,可以繞過雙親委托的機制。