类加载器源码分析
下面,我们就来深入的学习下类加载器的源码,看看到底做了哪些事情?
上图呈现是源码级别的类加载体系,ClassLoader是基类,所有的类加载器都需要继承它(启动类加载器除外)。
首先,我们通过上文中的测试类来举例,一点点剖析类加载的流程。
创建一个包下普通的类: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类,看内部具体流程。
类加载流程
(1)进入到Class内部,可以看到实际调用了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()后又调用了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);
}
在方法的末尾,调用父类中的loadClass(String name, boolean resolve)方法,也就是ClassLoader类;
(3)通过debug来看,在forName0()后又调用了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);
.....
}
}
//是否需要在加载时进行解析
if (resolve) {
resolveClass(c);
}
return c;
}
}
loadClass源码所示:当类加载请求到来时,先从缓存中查找该类对象,如果存在则直接返回,如果不存在则交给该类加载器的父类加载器去加载,倘若没有父类加载器则交给顶级启动类加载器去加载,最后仍没有找到,则使用findClass()方法去加载。
实际流程是,类加载请求进来时,this为AppClassLoader对象,判断AppClassLoader对象的parent父类加载器是否为空。根据双亲委派模型得知,此时的parent一定为ExtClassLoader对象。调用ExtClassLoader的loadClass(String name, boolean resolve)方法。
通过源码得知,ExtClassLoader继承ClassLoader抽象类,并且没有重写loadClass(String name, boolean resolve)方法。那么,此时又进入到了上面的loadClass(String name, boolean resolve)中。不过,此时的this是ExtClassLoader对象。
ExtClassLoader对象的parent父类加载器为null,调用findBootstrapClassOrNull(String name)方法,使用顶层启动类加载器去加载com.jiaboyan.test.ObjectTest类。
由于顶层启动类加载器是C++实现,我们无法直接获取到其引用,最终调用到了native修饰的findBootstrapClass(String name)方法。
由于,我们将ObjectTest.jar放在了<Java_Runtime_Home>/lib/ext目录下,所以顶层启动类加载器加载不到com.jiaboyan.test.ObjectTest类,继而抛出异常,返回到ExtClassLoader对象的loadClass(String name, boolean resolve)方法中来。
(4)接下来,继续调用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();
}
}
由于现在的this为ExtClassLoader对象,所以我们调用ExtClassLoader对象的findClass(final String name)方法。通过查看源码发现,ExtClassLoader并没有findClass方法,不过再其父类URLClassLoader中有实现(代码如上)。
(5)调用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) {
//获取资源内的字节流数组:
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, bb, cs);
} else {
//获取资源内的字节流数组:
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字节流解析成JVM能够识别的Class对象,通过这个方法不仅能够通过class文件实例化class对象,也可以通过其他方式实例化class对象,如通过网络接收一个类的字节码,然后转换为byte字节流创建对应的Class对象。
最后调用了defineClass(String name, java.nio.ByteBuffer b,CodeSource cs)方法,具体内部细节笔者不在详细陈序,需要说明的是:Class对象依旧使用了native修饰的方法来完成,内部细节无从得知。
private native Class defineClass2(String name, java.nio.ByteBuffer b,
int off, int len, ProtectionDomain pd,
String source);
(6)至此ExtClassLoader对象的加载阶段就此结束,Class对象返回。当返回到AppClassLoader对象中时,c并不为null,所以无需再次调用c = findClass(name)方法,整个类加载完成。
通过上述源码可知,当我们自己定义一个类加载器时候,无需重写loadClass()方法,直接重写自定义的findClass(String name)即可。父类加载器加载失败时候,最终都会走到findClass(String name)中来。
说完了类加载主体流程,接下来我们来研究一点细节的东西!!!!
此时,将文章拉回上面源码体系截图中,我们来看看SecureClassLoader、URLClassLoader类起到了哪些作用。
SercureClassLoader
SercureClassLoader继承ClassLoader,扩展了ClassLoader类的功能,增加对代码源和权限定义类的验证。
URLClassLoader
URLClassLoader继承SercureClassLoader,实现了ClassLoader中定义的方法,例如:findClass()、findResource()等。
在URLClassLoader中有一个成员变量ucp--URLClassPath对象,URLClassPath的功能是通过传入的路径信息获取要加载的字节码,字节码可以是在.class文件中、可以是在.jar包中,也可以是在网络中。
public URLClassLoader(URL[] urls) {
super();
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
ucp = new URLClassPath(urls);
this.acc = AccessController.getContext();
}
在URLClassPath构造中,需要传入URL[]数组,通过这个URL[]数组中所指定的位置信息,去加载对应的文件。在URLClassPath内部会根据传递的路径是文件地址、jar包地址还是网络地址来进行判断,来生成对应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");
}
}
根据路径的不同,来生成不同的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();
}
}