個人筆記--Java類加載
java類加載流程:
其實是通過ClassLoader(類加載器)去把對應的class文件類型動態加載到JVM中。
其中類加載器有三個,在sun.misc.Launcher這個Java虛擬機的入口應用裏,Launcher初始化ExtClassLoader和AppClassLoader。並通過System.getProperty(“sun.boot.class.path”)得到了BootStrapClassLoader。
加載順序:
BootStrapClassLoader(啟動類加載器):通過sun.boot.class.path(可修改加載路徑)加載JRE目錄下的jar包和class文件。 ExtClassLoader(擴展類加載器):通過java.ext.dirs(可修改加載路徑)加載lib\ext。 AppClassLoader(系統類加載器):加載當前java工程目錄bin下的class文件。
AppClassLoader的parent是ExtClassLoader,ExtClassLoader的parent是null。
Bootstrap ClassLoader是由C/C++編寫的,它本身是虛擬機的一部分,所以它並不是一個JAVA類,也就是無法在java代碼中獲取它的引用。
Bootstrap沒有父加載器,但是它卻可以作用一個ClassLoader的父加載器。比如ExtClassLoader。
雙親委托機制:
一個類加載器查找class和resource時,是通過“委托模式”進行的,它首先判斷這個class是不是已經加載成功,如果沒有的話它並不是自己進行查找,而是先通過父加載器,然後遞歸下去,直到Bootstrap ClassLoader,如果Bootstrap classloader找到了,直接返回,如果沒有找到,則一級一級返回,最後到達自身去查找這些對象。
委托是從下向上,然後具體查找過程卻是自上至下。
重要方法:
loadClass():
執行findLoadedClass(String)去檢測這個class是不是已經加載過了。
執行父加載器的loadClass方法。如果父加載器為null,則jvm內置的加載器去替代,也就是Bootstrap ClassLoader。這也解釋了ExtClassLoader的parent為null,但仍然說Bootstrap ClassLoader是它的父加載器。
如果向上委托父加載器沒有加載成功,則通過findClass(String)查找。
如果class在上面的步驟中找到了,參數resolve又是true的話,那麽loadClass()又會調用resolveClass(Class)這個方法來生成最終的Class對象。
雙親委托源代碼:
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
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
long t1 = System.nanoTime();
//父加載器沒有找到,則調用findclass
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//調用resolveClass()
resolveClass(c);
}
return c;
}
}
不管是Bootstrap ClassLoader還是ExtClassLoader等,這些類加載器都只是加載指定的目錄下的jar包或者資源。如果在某種情況下,我們需要動態加載一些東西呢?比如從D盤某個文件夾加載一個class文件,或者從網絡上下載class主內容然後再進行加載,這樣可以嗎?
如果要這樣做的話,需要我們自定義一個classloader。
步驟:
編寫一個類繼承自ClassLoader抽象類。
復寫它的findClass()方法。
在findClass()方法中調用defineClass()。
註意:如果要編寫一個classLoader的子類,也就是自定義一個classloader,建議覆蓋findClass()
方法,而不要直接改寫loadClass()
方法。
defineClass():
這個方法在編寫自定義classloader的時候非常重要,它能將class二進制內容轉換成Class對象,如果不符合要求的會拋出各種異常。
Context ClassLoader:線程上下文類加載器
查看Thread.java源碼可以發現:
ContextClassLoader只是一個成員變量,通過setContextClassLoader()
方法設置,通過getContextClassLoader()
設置。
每個Thread都有一個相關聯的ClassLoader,默認是AppClassLoader。並且子線程默認使用父線程的ClassLoader除非子線程特別設置。
個人筆記--Java類加載