本文主要講述Java ClassLoader的工作原理,這為後面將Android App代碼熱替換或者插件化升級做鋪墊
ClassLoader即常說的類加載器,其功能是用於從Class文件加載所需的類,主要場景用於熱部署、代碼熱替換等場景。
系統提供3種的類加載器:Bootstrap ClassLoader、Extension ClassLoader、Application ClassLoader
啟動類加載器,一般由C++實現,是虛擬機的一部分。該類加載器主要職責是將JAVA_HOME路徑下的\lib目錄中能被虛擬機識別的類庫(比如rt.jar)加載到虛擬機內存中。Java程序無法直接引用該類加載器
擴展類加載器,由Java實現,獨立於虛擬機的外部。該類加載器主要職責將JAVA_HOME路徑下的\lib\ext目錄中的所有類庫,開發者可直接使用擴展類加載器。 該加載器是由sun.misc.Launcher$ExtClassLoader實現。
應用程序類加載器,該加載器是由sun.misc.Launcher$AppClassLoader實現,該類加載器負責加載用戶類路徑上所指定的類庫。開發者可通過ClassLoader.getSystemClassLoader()方法直接獲取,故又稱為系統類加載器。當應用程序沒有自定義類加載器時,默認采用該類加載器。
ClassLoader.java
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader(); //初始化系統類加載器 【見下文】
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader ccl = getCallerClassLoader();
if (ccl != null && ccl != scl && !scl.isAncestor(ccl)) {
sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
return scl;
}
系統類加載器初始化:
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
scl = l.getClassLoader();
try {
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}
if (oops != null) {
if (oops instanceof Error) {
throw (Error) oops;
} else {
throw new Error(oops);
}
}
}
sclSet = true;
}
}
ClassLoader的雙親委派模型中,各個ClassLoader之間的關系是通過組合關系來復用父加載器。當一個ClassLoader收到來類加載的請求,首先把該請求委派該父類ClassLoader處理,當父類ClassLoader無法處理時,才由當前類ClassLoader來處理。對於每個ClassLoader這個方式,也就是父類的優先於子類處理類加載的請求,那麼也就是說任何一個請求第一次處理的便是最頂層的Bootstrap ClassLoader(啟動類加載器)。
類加載器的層級查找順序依次為:啟動類加載器,擴展類加載器,系統類加載器。系統類加載器是默認的應用程序類加載器。
這樣的好處是不同層次的類加載器具有不同優先級,比如所有Java對象的超級父類java.lang.Object,位於rt.jar,無論哪個類加載器加載該類,最終都是由啟動類加載器進行加載,保證安全。即使用戶自己編寫一個java.lang.Object類並放入程序中,雖能正常編譯,但不會被加載運行,保證不會出現混亂。那麼有人會繼續追問,如果自己再自定義一個類加載器來加載自己定義的java.lang.Object類呢? 這樣做也是不會成功的,虛擬機將會拋出一異常。
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 {
//當父類的加載器為空,則調用啟動類加載器來加載該類
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//非空父類的類加載器無法找到相應的類,則拋出異常
}
if (c == null) {
//當父類加載器無法加載時,則調用findClass方法來加載該類
long t1 = System.nanoTime();
c = findClass(name); //用戶可通過覆寫該方法,來自定義類加載器
//用於統計類加載器相關的信息
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//對類進行link操作
resolveClass(c);
}
return c;
}
}
當開發者需要自定義類加載器時,可通過覆寫loadClass()方法或者findClass()。
每一個ClassLoader都擁有自己獨立的類名稱空間,類是由ClassLoader將其加載到Java虛擬機中,故類是由加載它的ClassLoader和該類本身一起確定其在Java 運行時環境的唯一性。故只有同一個ClassLoader加載的同一個類,才能算是Java 運行時環境中的相同的兩個類。哪怕是來自同一個Class文件,即使被同一個虛擬機加載的兩個類,只要ClassLoader不同,那麼也屬於不同的類。對於equals()、isinstanceof()等方法來判斷對象的相等或所屬關系都是需要基於同一個ClassLoader。
自定義類加載器示例:
package com.yuanhh.classloader;
import java.io.IOException;
import java.io.InputStream;
public class ClassLoadDemo{
public static void main(String[] args) throws Exception {
ClassLoader clazzLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String clazzName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(clazzName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
String currentClass = "com.yuanhh.classloader.ClassLoadDemo";
Class<?> clazz = clazzLoader.loadClass(currentClass);
Object obj = clazz.newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof com.yuanhh.classloader.ClassLoadDemo);
}
}
上面代碼的輸出結果:
class com.yuanhh.classloader.ClassLoadDemo
false
輸出結果的第一行,可以看出這個對象的確是com.yuanhh.classloader.ClassLoadDemo
實例化的對象;但第二句是false,這是由於代碼中的obj是由用戶自定義的類加載器clazzLoader來加載的,可通過obj.getClass().getClassLoader()獲取該對象的類加載器為com.yuanhh.classloader.ClassLoadDemo$xxx,而虛擬機本身會由系統類加載器加載的類ClassLoadDemo,可通過ClassLoadDemo.class.getClassLoader()得其類加載器為sun.misc.Launcher$AppClassLoader@XXX。所以可得出結論:即使都是來自同一個Class文件,加載器不同,仍然是兩個不同的類,所以返回值是false。
通過Class.forName()
方法加載的類,采用的是系統類加載器。