Java 类加载器的双亲委派模型
ClassLoader
ClassLoader 就是类加载器,它的作用就是根据指定全限定名称将 class 文件加载到 JVM 内存中,转为 Class 对象。
ClassLoader 的分类
从 JVM 角度来说,ClassLoader 可以分为两类:
Bootstrap ClassLoader(启动类加载器)
这个类加载器由 C++ 实现,负责将存放在
\lib 目录或 -Xbootclasspath 参数指定的路径中的类库加载到内存中,与 JVM 是一体的。 其他类加载器
其他类加载器又可以分为以下三类
Extension ClassLoader(扩展类加载器)
负责加载
\lib\ext 目录或 java.ext.dirs 系统变量指定的路径中的所有类库。 Application ClassLoader(应用程序类加载器)
负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果没有自定义类加载器默认就是用这个加载器。
User ClassLoader(用户自定义类加载器)
如果需要自己控制类加载过程,可以自己实现类加载器,一般很少用到。
双亲委派模型
工作过程
双亲委派模型的工作过程是这样的:
- 如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成,每个类加载器都是如此,直到最上层的 Bootstrap ClassLoader。(由下到上)
- 当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),会往下传递至子加载器,子加载器才会尝试自己去加载。(由上到下)
为什么要使用双亲委派模型
确保一个类只会被加载一次,防止重复加载
因为每个类需要加载的时候都会先由子加载器委派给父加载器加载,如果某一层级的加载器查找到了该类已经加载,那么类加载器就会返回该类,而不会重新加载一个新类。
建立信任层级,防止恶意代码加载
一个例子说明:用户自己编写了一个类 java.lang.Object,与系统的 java.lang.Object “同名同姓”,当需要加载一个全限定类名为 java.lang.Object 的类时候,由于使用了双亲委派模型,会由最上层的 Bootstrap ClassLoader 先加载系统的 java.lang.Object 类,并且子加载器不会再加载任何全限定名为 java.lang.Object 的类,也就是说用户自己的编写的 Object 类就不会被加载进 JVM,而如果没有双亲委派模型,那么通过自定义类加载器可以将用户编写的 java.lang.Object 加载进 JVM,而 JVM 就可能误以为用户自定义的 java.lang.Object 就是系统的 java.lang.Object,导致“恶意代码”被执行。这个例子可以理解为通过双亲委派模型建立起了多层信任层级,优先级最高的当然是 Bootstrap ClassLoader,接着是 Extension ClassLoader,再往下是 Application ClassLoader,最后才是用户自定义的类加载器。
类加载器不同,相同全限定类名的两个类并不是同一个类型
在自定义的类加载器中,可以强制加载某个类,例如上文提到的用户自己编写的 java.lang.Object 类,但是即使这样,“恶意代码”也不会被执行,因为在JVM 中,判断一个对象是否是某个类型时,如果该对象的实际类型与待比较的类型的类加载器不同,那么会返回false。也就是说通过自定义类加载器强制加载的java.lang.Object 与 Bootstrap ClassLoader 加载的 java.lang.Object 并不是同一个类型,所以不会导致“恶意代码”被执行。
实现双亲委派模型
父子加载器之间并不是通过继承实现的,而是而是使用组合包含关系,通过 ClassLoader 源码可以得知
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class ClassLoader {
private static native void registerNatives();
static {
registerNatives();
}
// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
// 父类加载器
private final ClassLoader parent;
...
}
ClassLoader 实现双亲委派模型的几个重要方法
loadClass()
loadClass()的默认实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,调用 findLoadedClass() 方法检查类是否已经被加载
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 如果父加载器不为 null,委派给父加载器进行加载,否则寻找启动类加载器进行加载
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);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
从上面的源码可以看到双亲委派模型就是通过 loadClass(String name, boolean resolve) 方法实现的,实现过程大致如下:
- 首先,调用 findLoadedClass() 方法检查类是否已经被加载,如果已经被加载,直接返回。
- 如果没有被加载过并且父加载器不为 null,则委派给父加载器进行加载,否则寻找启动类加载器进行加载。
- 如果父加载器及启动类加载器都没有找到指定的类,那么调用当前类加载器的 findClass() 方法来完成类加载。
通过对 loadClass() 方法的分析,我们可以知道,如果要自定义类加载器,那么就必须重写 findClass() 方法。
findClass()
findClass() 的默认实现就是抛出一个 ClassNotFoundException
1
2
3
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
如果要自定义类加载器,那么我们需要做的工作就是重写 findClass() 方法,在这个方法里面实现将一个指定类名称转换为 Class 对象。ClassLoader 类中提供了一个 defineClass() 方法,通过这个方法,就可以把一个字节数组转为Class对象,十分简单。
defineClass()
这个方法的作用是将一个字节数组转为 Class 对象,这个字节数组是 class 文件读取后最终的字节数组。
实现自定义类加载器
通过上面对 ClassLoader 源码中实现双亲委派模型的几个重要方法的分析,我们可以知道实现自定义类加载器大致需要以下三个步骤:
- 自定义一个类(MyClassLoader)继承 ClassLoader 抽象类;
- 重写 ClassLoader 的 findClass(String name) 方法;
- 在 findClass(String name) 中实现将一个指定类名称转换为 Class 对象,即调用 defineClass() 方法。
示例
自定义一个 ClassLoader 加载某个 class 文件
创建类 ClassLoaderTest 编译生成 ClassLoaderTest.class 文件,删除 ClassLoaderTest.java 文件
1 2 3 4 5 6 7
package cc.white.classloaderdemo; public class ClassLoaderTest { public void helloWorld() { System.out.println("hello classloader"); } }
创建自定义类加载器 MyClassLoader 继承 ClassLoader,重写 findClass() 方法,在 findClass() 中调用 defineClass() 方法实现类加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
package cc.white.classloaderdemo; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; /** * 自定义类加载器加载 myclass/ClassLoaderTest.class */ public class MyClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { FileInputStream fileInputStream = null; ByteArrayOutputStream byteArrayOutputStream = null; try { fileInputStream = new FileInputStream(new File("myclass/ClassLoaderTest.class")); byteArrayOutputStream = new ByteArrayOutputStream(); int len; try { while ((len = fileInputStream.read()) != -1) { byteArrayOutputStream.write(len); } } catch (IOException e) { e.printStackTrace(); } byte[] data = byteArrayOutputStream.toByteArray(); return defineClass(name, data, 0, data.length); } catch (IOException e) { e.printStackTrace(); } finally { if (fileInputStream != null) { try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (byteArrayOutputStream != null) { try { byteArrayOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } return super.findClass(name); } }
测试 MyClassLoader 能否使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
package cc.white.classloaderdemo; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class Main { public static void main(String[] args) { MyClassLoader myClassLoader = new MyClassLoader(); try { // 加载全限定类名为 cc.white.classloaderdemo.ClassLoaderTest 的 class 文件 Class c = myClassLoader.loadClass("cc.white.classloaderdemo.ClassLoaderTest"); if (c != null) { try { Object obj = c.newInstance(); Method method = c.getDeclaredMethod("helloWorld", null); //通过反射调用 Test 类的 helloWorld 方法 method.invoke(obj, null); } catch (IllegalAccessException | InvocationTargetException | InstantiationException | NoSuchMethodException e) { e.printStackTrace(); } } } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
测试代码执行结果如下:
1
hello classloader
输出 hello classloader
说明正确加载了 cc.white.classloaderdemo.ClassLoaderTest 类,并执行了 helloWorld 方法。
总结
Java 的类加载器是用来加载 class 文件到 JVM 内存中的,可以分为两大类:Bootstrap ClassLoader(启动类加载器)和其他类加载器,而其他类加载器大致有以下三种:Extension ClassLoader(扩展类加载器)、Application ClassLoader(应用程序类加载器)、User ClassLoader(用户自定义类加载器)。类加载器之间存在父子关系,加载一个类的时候会使用双亲委派模型进行加载,每个子加载器都会先将加载需求委托给上级父加载器,父加载器搜索不到该类的时候再往下传递由子加载器进行加载。使用双亲委派模型可以有效的防止“恶意代码”被注入执行。
通过分析 ClassLoader 源码,了解了双亲委派模型的实现方式,以及实现自定义 ClassLoader 的基本步骤,主要依赖几个方法:loadClass()
findClass()
defineClass()