類加載器源碼分析
下面,我們就來(lái)深入的學(xué)習(xí)下類加載器的源碼筷屡,看看到底做了哪些事情涧偷?
上圖呈現(xiàn)是源碼級(jí)別的類加載體系,ClassLoader是基類速蕊,所有的類加載器都需要繼承它(啟動(dòng)類加載器除外)嫂丙。
首先,我們通過(guò)上文中的測(cè)試類來(lái)舉例规哲,一點(diǎn)點(diǎn)剖析類加載的流程跟啤。
創(chuàng)建一個(gè)包下普通的類:com.jiaboyan.test.ObjectTest
public class ObjectTest {
}
將此類編譯,并打包處理:
jar cvf ObjectTest.jar com\jiaboyan\test\ObjectTest.class
將此jar包放入<Java_Runtime_Home>/lib/ext目錄下唉锌;
編寫(xiě)測(cè)試用例:
public class JVMTest5 {
public static void main(String[] agrs) throws ClassNotFoundException {
Class clazz = Class.forName("com.jiaboyan.test.ObjectTest");
System.out.println(clazz.getClassLoader());
}
}
使用Class.forName來(lái)加載com.jiaboyan.test.ObjectTest類隅肥,看內(nèi)部具體流程。
類加載流程
(1)進(jìn)入到Class內(nèi)部袄简,可以看到實(shí)際調(diào)用了native修飾的forName0()方法腥放。
public static Class<?> forName(String className) throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller) throws ClassNotFoundException;
(2)通過(guò)debug來(lái)看,在forName0()后又調(diào)用了AppClassLoader類的loadClass(String var1, boolean var2)方法绿语。
public Class loadClass(String var1, boolean var2) throws ClassNotFoundException {
int var3 = var1.lastIndexOf(46);
if (var3 != -1) {
SecurityManager var4 = System.getSecurityManager();
if (var4 != null) {
var4.checkPackageAccess(var1.substring(0, var3));
}
}
return super.loadClass(var1, var2);
}
在方法的末尾秃症,調(diào)用父類中的loadClass(String name, boolean resolve)方法,也就是ClassLoader類吕粹;
(3)通過(guò)debug來(lái)看种柑,在forName0()后又調(diào)用了ClassLoader類的loadClass(String name, boolean resolve)方法。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
//先從緩存查找該class對(duì)象匹耕,找到就不用重新加載
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果找不到聚请,則委托給父類加載器去加載
c = parent.loadClass(name, false);
} else {
//如果沒(méi)有父類,則委托給啟動(dòng)加載器去加載
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//拋出異常無(wú)需理會(huì)
}
if (c == null) {
long t1 = System.nanoTime();
//如果都沒(méi)有找到稳其,則通過(guò)findClass去查找并加載
c = findClass(name);
.....
}
}
//是否需要在加載時(shí)進(jìn)行解析
if (resolve) {
resolveClass(c);
}
return c;
}
}
loadClass源碼所示:當(dāng)類加載請(qǐng)求到來(lái)時(shí)驶赏,先從緩存中查找該類對(duì)象,如果存在則直接返回既鞠,如果不存在則交給該類加載器的父類加載器去加載煤傍,倘若沒(méi)有父類加載器則交給頂級(jí)啟動(dòng)類加載器去加載,最后仍沒(méi)有找到嘱蛋,則使用findClass()方法去加載蚯姆。
實(shí)際流程是椅寺,類加載請(qǐng)求進(jìn)來(lái)時(shí),this為AppClassLoader對(duì)象蒋失,判斷AppClassLoader對(duì)象的parent父類加載器是否為空。根據(jù)雙親委派模型得知桐玻,此時(shí)的parent一定為ExtClassLoader對(duì)象篙挽。調(diào)用ExtClassLoader的loadClass(String name, boolean resolve)方法。
通過(guò)源碼得知镊靴,ExtClassLoader繼承ClassLoader抽象類铣卡,并且沒(méi)有重寫(xiě)loadClass(String name, boolean resolve)方法。那么偏竟,此時(shí)又進(jìn)入到了上面的loadClass(String name, boolean resolve)中煮落。不過(guò),此時(shí)的this是ExtClassLoader對(duì)象踊谋。
ExtClassLoader對(duì)象的parent父類加載器為null蝉仇,調(diào)用findBootstrapClassOrNull(String name)方法,使用頂層啟動(dòng)類加載器去加載com.jiaboyan.test.ObjectTest類殖蚕。
由于頂層啟動(dòng)類加載器是C++實(shí)現(xiàn)轿衔,我們無(wú)法直接獲取到其引用,最終調(diào)用到了native修飾的findBootstrapClass(String name)方法睦疫。
由于害驹,我們將ObjectTest.jar放在了<Java_Runtime_Home>/lib/ext目錄下,所以頂層啟動(dòng)類加載器加載不到com.jiaboyan.test.ObjectTest類蛤育,繼而拋出異常宛官,返回到ExtClassLoader對(duì)象的loadClass(String name, boolean resolve)方法中來(lái)。
(4)接下來(lái)瓦糕,繼續(xù)調(diào)用findClass(name)方法底洗。
protected Class<?> findClass(final String name) throws ClassNotFoundException{
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction<Class>() {
public Class run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
//生成class對(duì)象
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
throw new ClassNotFoundException(name);
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
}
由于現(xiàn)在的this為ExtClassLoader對(duì)象,所以我們調(diào)用ExtClassLoader對(duì)象的findClass(final String name)方法刻坊。通過(guò)查看源碼發(fā)現(xiàn)枷恕,ExtClassLoader并沒(méi)有findClass方法,不過(guò)再其父類URLClassLoader中有實(shí)現(xiàn)(代碼如上)谭胚。
(5)調(diào)用defineClass(String name, Resource res)方法徐块。
private Class defineClass(String name, Resource res) throws IOException {
long t0 = System.nanoTime();
int i = name.lastIndexOf('.');
URL url = res.getCodeSourceURL();
if (i != -1) {
String pkgname = name.substring(0, i);
Manifest man = res.getManifest();
if (getAndVerifyPackage(pkgname, man, url) == null) {
try {
if (man != null) {
definePackage(pkgname, man, url);
} else {
definePackage(pkgname, null, null, null, null, null, null, null);
}
} catch (IllegalArgumentException iae) {
if (getAndVerifyPackage(pkgname, man, url) == null) {
throw new AssertionError("Cannot find package " + pkgname);
}
}
}
}
java.nio.ByteBuffer bb = res.getByteBuffer();
if (bb != null) {
//獲取資源內(nèi)的字節(jié)流數(shù)組:
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, bb, cs);
} else {
//獲取資源內(nèi)的字節(jié)流數(shù)組:
byte[] b = res.getBytes();
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, b, 0, b.length, cs);
}
}
defineClass(String name, Resource res)方法是用來(lái)將byte字節(jié)流解析成JVM能夠識(shí)別的Class對(duì)象,通過(guò)這個(gè)方法不僅能夠通過(guò)class文件實(shí)例化class對(duì)象灾而,也可以通過(guò)其他方式實(shí)例化class對(duì)象胡控,如通過(guò)網(wǎng)絡(luò)接收一個(gè)類的字節(jié)碼,然后轉(zhuǎn)換為byte字節(jié)流創(chuàng)建對(duì)應(yīng)的Class對(duì)象旁趟。
最后調(diào)用了defineClass(String name, java.nio.ByteBuffer b,CodeSource cs)方法昼激,具體內(nèi)部細(xì)節(jié)筆者不在詳細(xì)陳序,需要說(shuō)明的是:Class對(duì)象依舊使用了native修飾的方法來(lái)完成,內(nèi)部細(xì)節(jié)無(wú)從得知橙困。
private native Class defineClass2(String name, java.nio.ByteBuffer b,
int off, int len, ProtectionDomain pd,
String source);
(6)至此ExtClassLoader對(duì)象的加載階段就此結(jié)束瞧掺,Class對(duì)象返回。當(dāng)返回到AppClassLoader對(duì)象中時(shí)凡傅,c并不為null辟狈,所以無(wú)需再次調(diào)用c = findClass(name)方法,整個(gè)類加載完成夏跷。
通過(guò)上述源碼可知哼转,當(dāng)我們自己定義一個(gè)類加載器時(shí)候,無(wú)需重寫(xiě)loadClass()方法槽华,直接重寫(xiě)自定義的findClass(String name)即可壹蔓。父類加載器加載失敗時(shí)候,最終都會(huì)走到findClass(String name)中來(lái)猫态。
說(shuō)完了類加載主體流程佣蓉,接下來(lái)我們來(lái)研究一點(diǎn)細(xì)節(jié)的東西!G籽F荨!
此時(shí)匆光,將文章拉回上面源碼體系截圖中套像,我們來(lái)看看SecureClassLoader、URLClassLoader類起到了哪些作用终息。
SercureClassLoader
SercureClassLoader繼承ClassLoader夺巩,擴(kuò)展了ClassLoader類的功能,增加對(duì)代碼源和權(quán)限定義類的驗(yàn)證周崭。
URLClassLoader
URLClassLoader繼承SercureClassLoader柳譬,實(shí)現(xiàn)了ClassLoader中定義的方法,例如:findClass()续镇、findResource()等美澳。
在URLClassLoader中有一個(gè)成員變量ucp--URLClassPath對(duì)象,URLClassPath的功能是通過(guò)傳入的路徑信息獲取要加載的字節(jié)碼摸航,字節(jié)碼可以是在.class文件中制跟、可以是在.jar包中,也可以是在網(wǎng)絡(luò)中酱虎。
public URLClassLoader(URL[] urls) {
super();
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
ucp = new URLClassPath(urls);
this.acc = AccessController.getContext();
}
在URLClassPath構(gòu)造中雨膨,需要傳入U(xiǎn)RL[]數(shù)組,通過(guò)這個(gè)URL[]數(shù)組中所指定的位置信息读串,去加載對(duì)應(yīng)的文件聊记。在URLClassPath內(nèi)部會(huì)根據(jù)傳遞的路徑是文件地址撒妈、jar包地址還是網(wǎng)絡(luò)地址來(lái)進(jìn)行判斷,來(lái)生成對(duì)應(yīng)Loader排监。
public URLClassPath(URL[] var1, URLStreamHandlerFactory var2) {
this.path = new ArrayList();
this.urls = new Stack();
this.loaders = new ArrayList();
this.lmap = new HashMap();
this.closed = false;
for(int var3 = 0; var3 < var1.length; ++var3) {
this.path.add(var1[var3]);
}
this.push(var1);
if (var2 != null) {
this.jarHandler = var2.createURLStreamHandler("jar");
}
}
根據(jù)路徑的不同狰右,來(lái)生成不同的Loader:
private URLClassPath.Loader getLoader(final URL var1) throws IOException {
try {
return (URLClassPath.Loader)AccessController.doPrivileged(new PrivilegedExceptionAction<URLClassPath.Loader>() {
public URLClassPath.Loader run() throws IOException {
String var1x = var1.getFile();
if (var1x != null && var1x.endsWith("/")) {
return (URLClassPath.Loader)("file".equals(var1.getProtocol()) ? new URLClassPath.FileLoader(var1) : new URLClassPath.Loader(var1));
} else {
return new URLClassPath.JarLoader(var1, URLClassPath.this.jarHandler, URLClassPath.this.lmap);
}
}
});
} catch (PrivilegedActionException var3) {
throw (IOException)var3.getException();
}
}