ClassLoader工作机制
ClassLoader工作机制
1. ClassLoader概念
简单理解:ClassLoader就是类加载器,用来动态加载class文件到JVM中,并转换成java.lang.class类的一个实例。与普通程序不同的是。Java程序(class文件)并不是本地的可执行程序。当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Java class加载到JVM里面运行,负责加载Java class的这部分就叫做ClassLoader。
2. Java默认提供的ClassLoader
Java默认提供三个ClassLoader, BootStrap ClassLoader、
Extension ClassLoader和App ClassLoader。
3. ClassLoader之间的关系
ExtClassLoader和AppClassLoader都继承了URLClassLoader类,而URLClassLoader又实现了抽象类ClassLoader,在创建Launcher对象的时候首先会创建ExtClassLoader,然后将ExtClassLoader对象作为父加载器创建AppClassLoader对象,而通过Launcher.getClassLoader()方法获取的ClassLoader就是AppClassLoader对象。所以如果在java应用中没有定义其它ClassLoader,那么除了System.getProperty(“java.ext.dirs”)目录下的类是由ExtClassLoader加载外,其它类都由AppClassLoader来加载。
Launcher源代码:
public class Launcher {
public Launcher() {
try {
extcl = ExtClassLoader.getExtClassLoader();// 创建ExtClassLoader
} catch (IOException e) {
throw new InternalError( "Could not create extension classloader", e);
}
try {
loader = AppClassLoader.getAppClassLoader(extcl); // 将ExtClassLoader对象作为父加载器创建AppClassLoader
} catch (IOException e) {
throw new InternalError( "Could not create application classloader", e);
}
}
……
static class ExtClassLoader extends URLClassLoader {};// ExtClassLoader继承URLClassLoader类
……
static class AppClassLoader extends URLClassLoader {};// AppClassLoader继承URLClassLoader类
……
从如上的代码我们可以看出,Launcher 类中包含 ExtClassLoader 和 AppClassLoader,而ExtClassLoader 和 AppClassLoader又继承自 URLClassLoader。在 Launcher的构造方法中, 会先通过ExtClassLoader.getExtClassLoader()方法创建 ExtClassLoader 的对象 extcl,然后再将extcl作为父加载器创建AppClassLoader的对象。
4. 每个类加载器都有一个父加载器
4.1 ClassLoader的父加载器
首先,我们要明确的知道一点,父加载器不是父类,一个类的父加载器有两种情况:
- 由外部类创建ClassLoader时直接指定一个ClassLoader为parent。
- 由getSystemClassLoader()方法生成,也就是在sun.misc.Laucher通过getClassLoader()获取,也就是上面我们所说过的AppClassLoader。直白的说,一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。
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); // 指定parent创建
}
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());// 未指定parent,由getSystemClassLoader()方法生成
}
public final ClassLoader getParent()
{
if (parent == null) return null;
return parent;
}
…
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
return scl; // 返回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();// scl赋值
…
}
…
}
…
}
从ClassLoader的源代码我们可以看到,ClassLoader中有两个受保护的构造方法,其中一个需要传入parent参数,通过parent创建ClassLoader;另一个是无参构造方法,在这个无参构造方法中则是通过getSystemClassLoader()创建ClassLoader。在getSystemClassLoader()方法中我们可以看到返回的是一个scl的对象,而在scl的初始化话方法initSystemClassLoader()里面,scl是通过sun.misc.Launcher.getClassLoader()方法赋值的。
4.2 Bootstrap ClassLoader
Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用,JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件Bootstrap没有父加载器,但是它却可以作用一个ClassLoader的父加载器。比如ExtClassLoader。因此通过ExtClassLoader的getParent方法获取的值为Null,但它仍然是具有父加载器的。
5. 双亲委托加载方式
一个类加载器查找class和resource时,是通过“委托模式”进行的,它首先判断这个class是不是已经加载成功,如果没有的话它并不是自己进行查找,而是先“询问”父加载器是否加载,如果没有,则再往上询问,直到Bootstrap ClassLoader,如果Bootstrap classloader找到了,直接返回,如果没有找到,则一级一级返回,最后到达自身去查找这些对象。这种机制就叫做双亲委托。
如上图所示,双亲委托加载机制有分为5个步骤:
- 一个AppClassLoader查找资源时,先看看缓存是否有,缓存有从缓存中获取,否则委托给父加载器。
- 递归,重复第1部的操作。
- 如果ExtClassLoader也没有加载过,则由Bootstrap ClassLoader出面,它首先查找缓存,如果没有找到的话,就去找自己的规定的路径下,也就是sun.mic.boot.class下面的路径。找到就返回,没有找到,让子加载器自己去找。
- Bootstrap ClassLoader如果没有查找成功,则ExtClassLoader自己在java.ext.dirs路径中去查找,查找成功就返回,查找不成功,再向下让子加载器找。
- ExtClassLoader查找不成功,AppClassLoader就自己查找,在java.class.path路径下查找。找到就返回。如果没有找到就让子类找,如果没有子类会怎么样?抛出各种异常。
LoadClass方法源码:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) {
// 检测class文件是否已经加载
Class<?> c = findLoadedClass(name);
if (c == null) {// class文件未加载
long t0 = System.nanoTime();
try {
if (parent != null) {
// 父加载器不为空则递归调用父加载器的loadClass
c = parent.loadClass(name, false);
} else {
//父加载器为空则调用Bootstrap Classloader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
long t1 = System.nanoTime
c = findClass(name);// 父加载器没有找到,则调用自身的findclass方法查找
…
}
…
}
if (resolve) {
resolveClass(c); // 调用resolveClass()生成最终的class实例
}
return c;
}
}
通过上面代码和图片我们可以看出,类加载具体步骤如下:
- 执行findLoadedClass(String)去检测这个class是不是已经加载过了。
- 执行父加载器的loadClass方法。如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器。
- 如果向上委托父加载器没有加载成功,则通过findClass(String)查找。
- 调用resolveClass(Class)这个方法来生成最终的Class对象。