凡事莫当前,看戏何如听戏好;
做人须顾后,上台终有下台时;
一:类加载器(ClassLoader)
JDK1.9以前,我们所熟知的JVM内置的三种类加载器:
- Bootstrap ClassLoader
- Extension ClassLoader
- Application ClassLoader
其中Extension ClassLoader 和 Application ClassLoader 都是URLClassLoader的子类。
1. Bootstrap ClassLoader
Bootstrap ClassLoader : 是最顶层的加载类,主要加载核心类库,主要用于加载lib/rt.jar,resources.jar,charsets.jar等模块。
我们可以通过启动指定JVM时通过-Xbootclasspath来改变Bootstrap ClassLoader的加载目录。
BootstrapClassLoader是由C代码实现的,所以我们获取不到它的对象,即每次获取都为null
2. Extension ClassLoader
Extension ClassLoader : 负责加载JVM的扩展类,加载lib/ext目录下的内容。
在JDK1.9之后,更名为:『PlatformClassLoader』
3. Application ClassLoader
Application ClassLoader : 直接面向用户的加载器,加载我们设置的classpath环境变量中的内容。我们自己所写的代码以及第三方jar包通常
都是由它来加载的。
通过ClassLoader.getSystemClassLoader() 获取到『系统类加载器』就是:AppClassLoader.我们平时所写的代码就是由它加载的。
public class ClassLoaderDemo {
public static void main(String[] args) {
// 1. 获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println("系统类加载器:"+systemClassLoader); // ClassLoaders$AppClassLoader
System.out.println("----------------");
// 2. 获取用户自定义类的类加载器:默认使用系统类加载器
ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
System.out.println("用户类加载器:"+classLoader); // ClassLoaders$AppClassLoader
// 3. java内部定义的类的加载器:null (Bootstrap ClassLoader)
System.out.println("基本类型类的加载器:" + int.class.getClassLoader()); // null 即 Bootstrap ClassLoader
}
}
运行上面的代码,返回结果是:AppClassLoader
4. 自定义类加载器
我们可以通过继承java.lang.ClassLoader并重写其findClass(),其内部调用defineClass().
5. 这些加载器之间的关系
包含关系
首先需要强调一点,讨论『加载器』的概念,所涉及到的继承关系,并不是类之间的继承关系。确切地将,更应该称为:包含关系。
5. 总结
- 每个Class对象都有一个ClassLoader的引用(如果classLoader == null,说明其类加载器是Bootstrap ClassLoader)
public final class Class<T> {
private final ClassLoader classLoader;
}
- 每个ClassLoader都有一个parent属性(父类加载器),如果parent == null,说明它的父类加载器是Bootstrap ClassLoader
public abstract class ClassLoader {
private final ClassLoader parent;
}
二:双亲委派机制
2.1 ClassLoader加载类的原理
当一个ClassLoader要加载某个类时(loadClass()),先把这个任务委托给它的父类加载器。即首先由最顶层的类加载器Bootstrap ClassLoader尝试去加载,
如果BootstrapClassLoader 可以加载,则任务完成,反之,则把任务转交给次一级的类加载器(Extension ClassLoader )去加载,如果还没
加载到,则转交给AppClassLoader进行加载。到这里如果还没加载到,则返回给任务的发起者,由它到指定的文件系统或者网络等URL中尝试
加载该类。此时如果还是没有加载到该类,则将会抛出ClassNotFoundException异常。如果加载到该类,则将其生成一个类的定义(defineClass()),并将其
加载到内存中,最后返回该类在内存中的Class实例对象。
这个过程可能描述起来有点绕,这里可以通过看源码java.lang.ClassLoader中的loadClass()方法来,结合其注释,来更好地理解该过程。
其实我们只需要关注ClassLoader里面的三个重要方法即可:
- loadClass() :定义了『双亲委派』机制
- findClass() : 双亲都加载不了的情况下,就会调用自定义类加载器中的findClass()来加载目标类(需要子类加载器自己实现内部逻辑)
- defineClass() : 组装Class对象
下面给出JVM内置的ClassLoader的简化源码:
public abstract class ClassLoader {
// 很清晰的看出来内部的『双亲委派』模型
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
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) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
// 空实现,需要我们自己来实现其加载逻辑
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
// 将字节流转化为Class对象
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
}
PS:一定要注意看源码的注释
2.2 双亲委派模型
再次强调一下,这里的『双亲』就是指加载系统类库的BootStrap ClassLoader 和 ExtensionClassLoader.
2.2.1 Why使用双亲委托模型
通过前面我们的了解,JVM的加载模型是『坑爹』一族啊。但是为什么要这么设计呢?
仔细思考后不难发现,这样可以避免重复加载,因为一旦父加载器加载过该类,子加载器就不需要再动手啦。
另一方面也是出于安全方面的考虑,因为如果不采用这种设置,我们可以随心所欲的覆写自定义的JDK内部定义的类,这样做就存在很大的安全问题。
想象一下,我们自己定义一个String类型,那JDK内部的String还能被加载吗?
2.2.2 JVM判断class是否相同
JVM判断class相同,需要同时满足两个条件:
- 类名相同;
- 由同一个类加载器(ClassLoader)加载;
2.2.3 确定是『双亲』吗?
如果我们自定义类加载器,则『双亲』就可能变成『多亲』,这取决于我们的类加载器是谁,它会一直递归委派到Bootstrap ClassLoader.
如果我们没有给一个ClassLoader指定其parent,则它的parent默认是AppClassLoader.
PS:『坑爹』的属性不会变。
2.2.4 自定义类类加载器
由于JVM已经帮我们把类加载的搜索算法实现,我们只需要按照规定把类的路径给出来即可。所以自定义类加载器就变得简单起来。
- 继承ClassLoader;
- 实现findClass(),最后内部调用defineClass()即可。
三. ClassLoader在JDK 9的变化
在JDK 9, 不存在ExtensionClassLoader,取而代之的是PlatformClassLoader。另外,AppClassLoader 和 PlatformClassLoader
都不再继承URLClassLoader,而是JDK内部定义的类。更多详细的内容,可以参考: migration guide for Java 9