類加載器源碼分析
下面,我們就來深入的學(xué)習(xí)下類加載器的源碼,看看到底做了哪些事情?

上圖呈現(xiàn)是源碼級別的類加載體系,ClassLoader是基類,所有的類加載器都需要繼承它(啟動類加載器除外)。
首先,我們通過上文中的測試類來舉例,一點點剖析類加載的流程。
創(chuàng)建一個包下普通的類:com.jiaboyan.test.ObjectTest
public class ObjectTest {
}
將此類編譯,并打包處理:
jar cvf ObjectTest.jar com\jiaboyan\test\ObjectTest.class
將此jar包放入<Java_Runtime_Home>/lib/ext目錄下;
編寫測試用例:
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來加載com.jiaboyan.test.ObjectTest類,看內(nèi)部具體流程。
類加載流程
(1)進(jìn)入到Class內(nèi)部,可以看到實際調(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)通過debug來看,在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)通過debug來看,在forName0()后又調(diào)用了ClassLoader類的loadClass(String name, boolean resolve)方法。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
//先從緩存查找該class對象,找到就不用重新加載
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果找不到,則委托給父類加載器去加載
c = parent.loadClass(name, false);
} else {
//如果沒有父類,則委托給啟動加載器去加載
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//拋出異常無需理會
}
if (c == null) {
long t1 = System.nanoTime();
//如果都沒有找到,則通過findClass去查找并加載
c = findClass(name);
.....
}
}
//是否需要在加載時進(jìn)行解析
if (resolve) {
resolveClass(c);
}
return c;
}
}
loadClass源碼所示:當(dāng)類加載請求到來時,先從緩存中查找該類對象,如果存在則直接返回,如果不存在則交給該類加載器的父類加載器去加載,倘若沒有父類加載器則交給頂級啟動類加載器去加載,最后仍沒有找到,則使用findClass()方法去加載。
實際流程是,類加載請求進(jìn)來時,this為AppClassLoader對象,判斷AppClassLoader對象的parent父類加載器是否為空。根據(jù)雙親委派模型得知,此時的parent一定為ExtClassLoader對象。調(diào)用ExtClassLoader的loadClass(String name, boolean resolve)方法。
通過源碼得知,ExtClassLoader繼承ClassLoader抽象類,并且沒有重寫loadClass(String name, boolean resolve)方法。那么,此時又進(jìn)入到了上面的loadClass(String name, boolean resolve)中。不過,此時的this是ExtClassLoader對象。
ExtClassLoader對象的parent父類加載器為null,調(diào)用findBootstrapClassOrNull(String name)方法,使用頂層啟動類加載器去加載com.jiaboyan.test.ObjectTest類。
由于頂層啟動類加載器是C++實現(xiàn),我們無法直接獲取到其引用,最終調(diào)用到了native修飾的findBootstrapClass(String name)方法。
由于,我們將ObjectTest.jar放在了<Java_Runtime_Home>/lib/ext目錄下,所以頂層啟動類加載器加載不到com.jiaboyan.test.ObjectTest類,繼而拋出異常,返回到ExtClassLoader對象的loadClass(String name, boolean resolve)方法中來。
(4)接下來,繼續(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對象
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對象,所以我們調(diào)用ExtClassLoader對象的findClass(final String name)方法。通過查看源碼發(fā)現(xiàn),ExtClassLoader并沒有findClass方法,不過再其父類URLClassLoader中有實現(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)方法是用來將byte字節(jié)流解析成JVM能夠識別的Class對象,通過這個方法不僅能夠通過class文件實例化class對象,也可以通過其他方式實例化class對象,如通過網(wǎng)絡(luò)接收一個類的字節(jié)碼,然后轉(zhuǎn)換為byte字節(jié)流創(chuàng)建對應(yīng)的Class對象。
最后調(diào)用了defineClass(String name, java.nio.ByteBuffer b,CodeSource cs)方法,具體內(nèi)部細(xì)節(jié)筆者不在詳細(xì)陳序,需要說明的是:Class對象依舊使用了native修飾的方法來完成,內(nèi)部細(xì)節(jié)無從得知。
private native Class defineClass2(String name, java.nio.ByteBuffer b,
int off, int len, ProtectionDomain pd,
String source);
(6)至此ExtClassLoader對象的加載階段就此結(jié)束,Class對象返回。當(dāng)返回到AppClassLoader對象中時,c并不為null,所以無需再次調(diào)用c = findClass(name)方法,整個類加載完成。
通過上述源碼可知,當(dāng)我們自己定義一個類加載器時候,無需重寫loadClass()方法,直接重寫自定義的findClass(String name)即可。父類加載器加載失敗時候,最終都會走到findClass(String name)中來。
說完了類加載主體流程,接下來我們來研究一點細(xì)節(jié)的東西!?。?!
此時,將文章拉回上面源碼體系截圖中,我們來看看SecureClassLoader、URLClassLoader類起到了哪些作用。
SercureClassLoader
SercureClassLoader繼承ClassLoader,擴(kuò)展了ClassLoader類的功能,增加對代碼源和權(quán)限定義類的驗證。
URLClassLoader
URLClassLoader繼承SercureClassLoader,實現(xiàn)了ClassLoader中定義的方法,例如:findClass()、findResource()等。
在URLClassLoader中有一個成員變量ucp--URLClassPath對象,URLClassPath的功能是通過傳入的路徑信息獲取要加載的字節(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)造中,需要傳入URL[]數(shù)組,通過這個URL[]數(shù)組中所指定的位置信息,去加載對應(yīng)的文件。在URLClassPath內(nèi)部會根據(jù)傳遞的路徑是文件地址、jar包地址還是網(wǎng)絡(luò)地址來進(jìn)行判斷,來生成對應(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ù)路徑的不同,來生成不同的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();
}
}