学习目标
通过源码级的分析,理解三种加载器直接的关系,以及双亲委派机制的理解
类加载器的介绍
Java虚拟机的设计团队有意的将通过一个类的全限类名来获得描述该类的二进制字节流
放入jvm的外部实现,以便引用程序知道如何去获取所需要的类。实现这个动作的代码
称为类加载器。这个代码的类为ClassLoader
,它是一个抽象类,其实现类确定了具体的加载过程。
ClassLoader
对类进行加载,ClassLoader只是负责类的加载中的第一个阶段–加载,它是通过其子类的实现将Class以某一种方式以二进制的方式加载到内存中,转换为一个目标类对应的java.lang.Class
的实例,并将该Class对象实例和其子类的类加载器关联起来
。类、类加载器、类 实例之间的关系大致如下:
类加载的类型
类的加载分为显示加载和隐式加载:
- 显示加载:在代码中通过调用ClassLoader来加载的Class对象,如直接使用Class.forName(name)或 this.getClass().getClassLoader().loadClass()加载class对象。
- 隐式加载:没有调用ClassLoader来加载的对象,由jvm自动将类加载到内存中。加载的类仍会和jvm使用的加 载器相关联起来
加载器的类型
加载器的类型分为两种
- 系统类加载器(BootstrapClassLoader),让jvm能够运行起来
- 用户自定义加载器(User-Defined ClassLoader),用Java语言实现,开发人员自己开发的加载器
其中jdk自带了两个用户自定义加载器
:扩展类加载器、应用程序类加载器
jdk中的三种加载器
这三种加载器中,扩展类加载器
和应用程序类加载器
都是在Launcher
(启动器)的类中,且皆是该类的静态类部类。
逻辑关系
这里的关系是它们逻辑上的关系:逻辑上是继承的关系
代码结构关系
对应类的代码层面上的关系
BootstrapClassLoader
叫引导类加载器,又名启动类加载器,其具有如下的特点:
- C/C++语言编写的类加载器
- 加载的是java中的最核心的代码,用于jvm自身运行的需求
- 为了安全的考虑,其只加载包名为java、javax、sun开头的包名
- 逻辑上是另外两种加载器的父类
- 使用-XX:+TraceClassLoading参数得到该加载器加载的类
测试:命名一个类,包名为:java.lang,类名为:String,看看是否能运行
ExtClassLoader
叫扩展类加载器,其具有如下特点
- 由java语言编写,为Launcher的内部类
- 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。
AppClassLoader
又叫应用程序加载器,其具有如下特点:
- 由java语言编写,为Launcher的内部类
- 它负责加载环境变量classpath或系统属性 java.class.path 指定路径下的类库
- 应用程序中的类加载器默认是系统类加载器。
- 它是
用户自定义类加载器
的默认父加载器 - 通过ClassLoader的getSystemClassLoader()方法可以获取到该类加载器
用户自定义加载器的源码分析
用户自定义加载器包括:扩展类加载器、引用程序类加载器。但是因为是源码分析,所以我们需要从ClassLoader
这个类开始逐步的向下分析。
ClassLoader的源码
关键代码:
public abstract class ClassLoader {
// 父类加载器
private final ClassLoader parent;
// name:要加载类全限类名
// resolve:若为true,则要解析该类
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 检查类是否被加载
Class<?> c = findLoadedClass(name);
// 该类没有被加载,则准备加载
if (c == null) {
long t0 = System.nanoTime();
try {
// 如果加载器的父加载器为null,则代表父加载器是系统类加载器
// 直接将类名传给系统类加载器,其底层调用C++进行加载
// 否则就调用父类加载器进行加载
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
}
// 如果父加载之后仍然为null,则代表父加载器不能加载,只能本加载器尝试加载
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// 调用findClass尝试对类进行加载
c = findClass(name);
// 统计这个类加载器加载了哪些类的信息
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
// 是否需要解析,若为true,则对类进行
if (resolve) {
resolveClass(c);
}
return c;
}
}
// 让子类重写这个方法,子类重写这个方法来实现类的加载
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
// 私有构造器但是子类传入父加载器的时候,会一直向上调用方法,知道调用到了此方法
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
流程图如图所示:
URLClassLoader的源码
URLClassLoader
继承SecureClassLoader
,但是SecureClassLoader
类中基本没有关于加载的重写,所以对应加载的重写方法findClass
还是要看URLClassLoader
的。
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
// 加载类的结果
final Class<?> result;
try {
result = 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 {
// 从指定资源获得一个由二进制字节流定义的类,这个类只有解析后才可以使用
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
Launcher的源码
两位两个加载器是该类中的内部类,所以分析另外两个加载器之前,需要分析Launcher类的源码,才能更好的分析其他的加载器。Launcher类在下载的jdk中没有源文件,只有class文件,需要去下载openJdk的源码,才可以找到对应的Launcher类文件。文章后面会分享文件
关键代码:
package sun.misc;
public class Launcher {
// static修饰的变量,初始化的时候便会执行new Launcher();,从而调用构造方法
private static Launcher launcher = new Launcher();
// 应用程序加载器加载器
private ClassLoader loader;
public Launcher() {
// 创建一个扩展类加载器
ClassLoader extcl;
try {
// 创造一个扩展类加载器
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader", e);
}
try {
// 创建一个应用类加载器,传入扩展类加载器,传入的加载器就是父类加载器
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader", e);
}
// 后面还有一系列的代码,和源码分析无关,暂时省去
}
}
Launcher的包名是以sun.misc
开始的,所以该加载器由系统类加载器进行加载,系统类加载器是jvm启动的时候便会加载该类。并且因为有static修饰的对象创建,所以也会进行初始化,执行构造方法。
在构造方法中,关于加载器执行了两个事情:
- 创建扩展类加载器
- 创建应用程序类加载器
ExtClassLoader的源码
static class ExtClassLoader extends URLClassLoader {
// 创建一个扩展类加载器,关键代码:return new ExtClassLoader(dirs);
public static ExtClassLoader getExtClassLoader() throws IOException
{
final File[] dirs = getExtDirs();
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction<ExtClassLoader>() {
public ExtClassLoader run() throws IOException {
int len = dirs.length;
for (int i = 0; i < len; i++) {
MetaIndex.registerDirectory(dirs[i]);
}
return new ExtClassLoader(dirs);
}
});
} catch (java.security.PrivilegedActionException e) {
throw (IOException) e.getException();
}
}
// 没有重写ClassLoader的loadClass方法,所以用ClassLoader的方法
// 没有重写URLClassLoader中的findClass方法,所以用父类的方法
}
AppClassLoader的 源码解析
static class AppClassLoader extends URLClassLoader {
public Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
int i = name.lastIndexOf('.');
if (i != -1) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPackageAccess(name.substring(0, i));
}
}
if (ucp.knownToNotExist(name)) {
// 在父类加载器及其本地 URLClassPath 中找不到给定名称的类。
// 检查这个类是否已经被动态定义;
// 如果是,则返回加载的类; 否则,跳过父委托并 findClass。
Class<?> c = findLoadedClass(name);
if (c != null) {
if (resolve) {
resolveClass(c);
}
return c;
}
throw new ClassNotFoundException(name);
}
return (super.loadClass(name, resolve));
}
// 没有重写URLClassLoader中的findClass方法,所以用父类的方法
}
可以看到,虽然重写了loadClass,但是最后还是会调用父加载器的loadClass方法。
还有两个关于构造的方法,截图如下:

源码总结
- ClassLoader中由一个
loadClass
的方法,这个方法被AppClassLoader重写,但是ExtClassLoader没重写该方法 - SecureClassLoader只是写了一些安全的实现,没对加载进行实现
- URL 中重写了ClassLoader中的
findClass
方法,此方法没有被另外两个加载器重写过 - Launcher是创建两个加载器,其中创建AppClassLoader加载器的时候会将第一步创建的ExtClassLoader作为父加载器传入到其创建方法中。
- ExtClassLoader没有对
loadClass
和findClass
方法进行重写,都是用的父类的 - AppClassLoader只对
loadClass
进行了重写,但是还是可能会调用父类的loadClass
方法
所以:
两个加载器都会调用ClassLoader中的loadClass方法,而loadClass方法又是自己加载先不加载,让父加载器进行加载,若父加载器加载不了,自己再尝试加载。这种现象即为双亲委派机制
双亲委派机制
参考用户自定义加载器的源码分析
中的ClassLoader的源码
,其中的loadClass
方法即为双亲委派机制的实现。也可以看上面的源码总结,是双亲委派机制的总结
参考资料和相关资源
相关资源:https://download.youkuaiyun.com/download/xing_S/82487214
参考资料:《深入理解java虚拟机》、尚硅谷宋红康老师的jvm