学习 JVM 之双亲委派模型
一、什么是双亲委派
意思是类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。
加载的时候,首先会把该请求委派该父类加载器的 loadClass()
处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader
中。当父类加载器无法处理时,才由自己来处理。当父类加载器为 null 时,会使用启动类加载器 BootstrapClassLoader
作为父类加载器。( ExtClassLoader
的父类加载器为 null)
双亲委派机制:双亲委派机制是 Java 虚拟机加载一个类时为该类确定类加载器的一种机制 (类加载器顺序在下面)
**自底向上检查类是否被加载 **: 加载的大多数都是用户自己写的类,如果每次都要从上往下遍历完 jre/lib 目录,又遍历 jre/lib/ext 目录,最后又不是,以至于浪费大量时间。
自顶向下尝试加载类 :
双亲只是从官方文档翻译成双亲,实际这里的双亲其实就指的是父类,没有mother。加载器之间的“父子关系”也不是我们平日所说的那种继承关系,然而只是一种优先级的层次关系。
官方 API 文档对这部分的描述如下:
The Java platform uses a delegation model for loading classes. The basic idea is that every class loader has a "parent" class loader. When loading a class, a class loader first "delegates" the search for the class to its parent class loader before attempting to find the class itself.
二、介绍其中的类加载器
三个类加载器 以及 用户自定义的加载器,以下顺序描述:
- Bootstrap ClassLoader(启动类加载器) :最顶层的加载类,由 C++实现,负责加载
%JAVA_HOME%/lib
目录下的 jar 包和类或者或被-Xbootclasspath
参数指定的路径中的所有类。 - Extension ClassLoader(扩展类加载器) : 主要负责加载目录
%JRE_HOME%/lib/ext
目录下的 jar 包和类,或被java.ext.dirs
系统变量所指定的路径下的 jar 包。 - Application ClassLoader(应用程序类加载器) :面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。
- User Defined ClassLoader (用户自定义加载类):用户自定义加载器继承
ClassLoader
,重写ClassLoader
类中的findClass()
方法
何时被创建?
启动类加载器,在调用 Java 程序前就被创建。扩展类加载器和应用程序类加载器都是
Launcher
的静态内部类,所以是创建 启动器实例sun.misc.Launcher
的时候就会被创建。
三、双亲委派的源码
private final ClassLoader parent;
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查请求的类是否已经被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {//父加载器不为空,调用父加载器loadClass()方法处理
c = parent.loadClass(name, false);
} else {//父加载器为空,使用启动类加载器 BootstrapClassLoader 加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//抛出异常说明父类加载器无法完成加载请求
}
if (c == null) {
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;
}
}
四、双亲委派的好处
-
双亲委派模型保证了 Java 程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类)
-
防止核心API库被随意篡改,例如哪些 String 等等