- ClassLoader的具體作用就是將字節(jié)碼格式文件加載到虛擬機(jī)中去婶博。Java中是把class文件加載到JVM荡陷。Android中是把dex/odex文件加載入虛擬機(jī)豆励。
- 當(dāng)JVM啟動(dòng)的時(shí)候诵次,不會(huì)一下子把所有的class文件加載進(jìn)JVM薛夜,而是根據(jù)需要去動(dòng)態(tài)加載速梗。
JAVA類加載
- 在Java中有三個(gè)類加載器
-
Bootstrap ClassLoader:啟動(dòng)類加載器肮塞,最頂層的加載類襟齿。負(fù)責(zé)加載JDK中的核心類庫(kù),如:rt.jar枕赵、resources.jar猜欺、charsets.jar等。他是由C++實(shí)現(xiàn)的拷窜,并不繼承自
java.lang.ClassLoader
开皿。 - Extention ClassLoader:擴(kuò)展類加載器,負(fù)責(zé)加載Java的擴(kuò)展類庫(kù)篮昧,默認(rèn)加載JAVA_HOME/jre/lib/ext/目下的所有jar赋荆。
- Application ClassLoader:應(yīng)用類加載器,負(fù)責(zé)加載應(yīng)用程序classpath目錄下的所有jar和class文件懊昨。一般來(lái)說(shuō)窄潭,Java 應(yīng)用的類都是由它來(lái)完成加載的。
-
Bootstrap ClassLoader:啟動(dòng)類加載器肮塞,最頂層的加載類襟齿。負(fù)責(zé)加載JDK中的核心類庫(kù),如:rt.jar枕赵、resources.jar猜欺、charsets.jar等。他是由C++實(shí)現(xiàn)的拷窜,并不繼承自
父加載器
-
父加載器不是父類酵颁。 先看下AppClassLoader和ExtClassLoader的繼承關(guān)系嫉你。
我們來(lái)看下ClassLoader的源碼:
public abstract class ClassLoader { //父加載器 private final ClassLoader parent; private static ClassLoader scl; private ClassLoader(Void unused, ClassLoader parent) { this.parent = parent; ... } protected ClassLoader(ClassLoader parent) { this(checkCreateClassLoader(), parent); } protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader()); } public final ClassLoader getParent() { if (parent == null) return null; return parent; } public static ClassLoader getSystemClassLoader() { initSystemClassLoader(); if (scl == null) { return null; } 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; //通過(guò)Launcher獲取ClassLoader 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; } } ... }
再來(lái)看看
sun.misc.Launcher
,它是一個(gè)java虛擬機(jī)的入口:public class Launcher { private static Launcher launcher = new Launcher(); private static String bootClassPath = System.getProperty("sun.boot.class.path"); public static Launcher getLauncher() { return launcher; } private ClassLoader loader; public Launcher() { ClassLoader extcl; try { //初始化ExtClassLoader extcl = ExtClassLoader.getExtClassLoader(); } catch (IOException e) { throw new InternalError( "Could not create extension class loader", e); } try { //初始化AppClassLoader loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) { throw new InternalError( "Could not create application class loader", e); } //設(shè)置AppClassLoader為線程上下文類加載器 Thread.currentThread().setContextClassLoader(loader); } public ClassLoader getClassLoader() { return loader; } static class ExtClassLoader extends URLClassLoader { private File[] dirs; public static ExtClassLoader getExtClassLoader() throws IOException { final File[] dirs = getExtDirs(); return new ExtClassLoader(dirs); } public ExtClassLoader(File[] dirs) throws IOException { super(getExtURLs(dirs), null, factory); this.dirs = dirs; } ... } static class AppClassLoader extends URLClassLoader { public static ClassLoader getAppClassLoader(final ClassLoader extcl) throws IOException{ final String s = System.getProperty("java.class.path"); final File[] path = (s == null) ? new File[0] : getClassPath(s); URL[] urls = (s == null) ? new URL[0] : pathToURLs(path); return new AppClassLoader(urls, extcl); } AppClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent, factory); } ... } }
從以上的源碼中我們可以知道parent的賦值是在ClassLoader對(duì)象的構(gòu)造方法中,它有兩個(gè)情況:
由外部類創(chuàng)建ClassLoader時(shí)直接傳入一個(gè)ClassLoader為parent材义。
外界不指定parent時(shí)均抽,由
getSystemClassLoader()
方法生成,也就是在sun.misc.Laucher
通過(guò)getClassLoader()
獲取其掂,也就是AppClassLoader油挥。直白的說(shuō),一個(gè)ClassLoader創(chuàng)建時(shí)如果沒(méi)有指定parent款熬,那么它的parent默認(rèn)就是AppClassLoader深寥。
從loader = AppClassLoader.getAppClassLoader(extcl);
說(shuō)明AppClassLoader的parent是ExtClassLoader。
但是ExtClassLoader并沒(méi)有直接對(duì)parent賦值贤牛。它調(diào)用了它的父類也就是URLClassLoder的構(gòu)造方法并傳遞了3個(gè)參數(shù)惋鹅。
public URLClassLoader(URL[] urls, ClassLoader parent,URLStreamHandlerFactory factory) {
super(parent);
}
真相大白,ExtClassLoader的parent為null殉簸。但是實(shí)際上ExtClassLoader父類加載器是BootstrapClassLoader闰集,我們可以從雙親委托中找到蛛絲馬跡。
雙親委托
? 類加載器在加載類或者其他資源時(shí)般卑,使用的是如上圖所示的雙親委派模型武鲁,這種模型要求除了頂層的BootStrap ClassLoader外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器蝠检,如果一個(gè)類加載器收到了類加載請(qǐng)求沐鼠,首先會(huì)把這個(gè)請(qǐng)求委派給父類加載器加載,只有父類加載器無(wú)法完成類加載請(qǐng)求時(shí),子類加載器才會(huì)嘗試自己去加載饲梭。要理解雙親委派乘盖,可以查看ClassLoader.loadClass方法。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 檢查是否已經(jīng)加載過(guò)
Class<?> c = findLoadedClass(name);
if (c == null) {
// 沒(méi)有被加載過(guò)
long t0 = System.nanoTime();
// 首先委派給父類加載器加載
try {
if (parent != null) {
//父加載器不為空則調(diào)用父加載器的loadClass
c = parent.loadClass(name,false);
} else {
//父加載器為空則調(diào)用Bootstrap Classloader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 如果父類加載器無(wú)法加載憔涉,才嘗試加載
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;
}
}
? 在雙親委托把類加載事件一直往上傳遞订框,一直傳到ExtClassLoader,由于ExtClassLoader中的parent為null而傳給BootStrapClassLoader兜叨。所以說(shuō)ExtClassLoader的父加載器為BootStrapClassLoader布蔗。之所以ExtClassLoader不持有BootStrapClassLoader的引用,是因?yàn)锽ootstrap ClassLoader是由C/C++編寫的浪腐,它本身是虛擬機(jī)的一部分,所以它并不是一個(gè)JAVA類顿乒,也就是無(wú)法在java代d碼中獲取它的引用议街。
- 優(yōu)點(diǎn):通過(guò)雙親委托可以避免重復(fù)加載和保證安全性。當(dāng)父親已經(jīng)加載了該類的時(shí)候璧榄,就沒(méi)有必要子ClassLoader再加載一次特漩。如果我們自定義一個(gè)String來(lái)動(dòng)態(tài)替換java核心api中定義的類型,這樣會(huì)存在非常大的安全隱患骨杂,而雙親委托的方式涂身,就可以避免這種情況,因?yàn)镾tring已經(jīng)在啟動(dòng)時(shí)就被引導(dǎo)類加載器(Bootstrcp ClassLoader)加載搓蚪,所以永遠(yuǎn)也無(wú)法加載一個(gè)自己寫的String蛤售,除非你改變JDK中ClassLoader搜索類的默認(rèn)算法。