# 4.3 JDK的类加载器

JDK自身的jar包如rt.jar和tools.jar(或者JDK9以上的模块)等中的类也需要使用类加载器来加载,下面的代码用来获取JDK内置的类加载器。

public class JdkClassloader {
    public static void main(String[] args) {
        // 获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);
        
        // 获取系统类加载器的父类加载器 --> 扩展类加载器或者平台类加载器
        ClassLoader platformClassLoader = systemClassLoader.getParent();
        System.out.println(platformClassLoader);
        
        // 获取扩展类加载器的父类加载器 --> 启动类加载器(C/C++)
        ClassLoader bootstrapClassLoader = platformClassLoader.getParent();
        System.out.println(bootstrapClassLoader);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

在JDK8上运行:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@4a574795
null
1
2
3

在JDK11上运行:

jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17
jdk.internal.loader.ClassLoaders$PlatformClassLoader@3cda1055
null
1
2
3

可以看到JDK8和JDK11类加载器的类名称存在差异,下面分别说明其实现。

# 4.3.1 JDK8的类加载器

# 4.3.1.1 AppClassloader

AppClassloader也称为System ClassLoader,继承了URLClassLoader, 是Java虚拟机默认的类加载器之一,主要用来加载用户类和第三方依赖包, 在JVM启动命令行中设置-Djava.class.path参数来指定加载路径。

代码位置:src/share/classes/sun/misc/Launcher$AppClassLoader.java

// AppClassLoader继承URLClassLoader
static class AppClassLoader extends URLClassLoader {
    
    public static ClassLoader getAppClassLoader(final ClassLoader extcl)
            throws IOException {
        // 搜索路径java.class.path
        final String s = System.getProperty("java.class.path");
        final File[] path = (s == null) ? new File[0] : getClassPath(s);

        URL[] urls = (s == null) ? new URL[0] : pathToURLs(path);
        return new AppClassLoader(urls, extcl);
    }

    /*
     * Creates a new AppClassLoader
     */
    AppClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent, factory);
    }

    /**
     * 重写了loadClass,支持类的包权限检查
     */
    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));
            }
        }
        // 调用父类URLClassLoader完成类加载
        return (super.loadClass(name, resolve));
    }
    
    // 其他方法省略...
}    
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

# 4.3.1.2 ExtClassLoader

ExtClassLoader称为扩展类加载器,继承了URLClassLoader,主要负责加载Java的扩展类库,默认加载${JAVA_HOME}/jre/lib/ext/ 目录下的所有jar包,也可以用参数-Djava.ext.dirs来设置它的搜索路径。

代码位置:src/share/classes/sun/misc/Launcher$ExtClassLoader.java

// ExtClassLoader继承URLClassLoader
static class ExtClassLoader extends URLClassLoader {

    public static ExtClassLoader getExtClassLoader() throws IOException {
        final File[] dirs = getExtDirs();

        try {
            return new ExtClassLoader(dirs);
        } catch (java.security.PrivilegedActionException e) {
            throw (IOException) e.getException();
        }
    }
    
    public ExtClassLoader(File[] dirs) throws IOException {
        super(getExtURLs(dirs), null, factory);
    }

    private static File[] getExtDirs() {
        // 通过系统变量指定加载路径
        String s = System.getProperty("java.ext.dirs");
        File[] dirs;
        if (s != null) {
            StringTokenizer st =
                    new StringTokenizer(s, File.pathSeparator);
            int count = st.countTokens();
            dirs = new File[count];
            for (int i = 0; i < count; i++) {
                dirs[i] = new File(st.nextToken());
            }
        } else {
            dirs = new File[0];
        }
        return dirs;
    }
}    
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

JDK8的类加载器的继承关系如下图4-3所示:

图4-3 JDK8的类加载器的继承关系

图4-3 JDK8的类加载器的继承关系

# 4.3.1.3 JDK8的类加载器的初始化

JDK的类加载器的初始化在Launcher类中。

源码位置:src/share/classes/sun/misc/Launcher.java

public class Launcher {

    public Launcher() {
        // 创建ExtClassLoader
        ClassLoader extcl = ExtClassLoader.getExtClassLoader();
        // 创建AppClassLoader
        ClassLoader loader = AppClassLoader.getAppClassLoader(extcl);
        // 设置当前线程的ContextClassLoader
        Thread.currentThread().setContextClassLoader(loader);
        // 异常处理的代码省略
    }
    // ...
}    
1
2
3
4
5
6
7
8
9
10
11
12
13

可以看到,初始化过程较为简单,先初始化ExtClassLoader,然后在初始化AppClassLoader,并且设置AppClassLoader的父加载器为ExtClassLoader。

# 4.3.2 JDK11的类加载器

JDK9实现模块化之后,对Classloader有所改造,其中一点就是将ExtClassLoader改为PlatformClassLoader, 模块化之后不同的Classloader加载各自对应的模块。因为JDK11是一个长期支持的稳定版本,这里以JDK11的源代码来说明类加载器的变化。JDK11的类加载器的继承关系如下图4-4所示:

图4-4 JDK11的类加载器的继承关系

图4-4 JDK11的类加载器的继承关系

# 4.3.2.1 BuiltinClassLoader

BuiltinClassLoader是PlatformClassLoader、BootClassLoader和AppClassloader的父类,功能上与URLClassLoader相似,都是基于UrlClassPath来实现类的查找,但BuiltinClassLoader还支持从模块中加载类。

BuiltinClassLoader的属性与构造函数如下:

代码位置:src/java.base/share/classes/jdk/internal/loader/BuiltinClassLoader.java

// 类加载器路径
private final URLClassPath ucp;

BuiltinClassLoader(String name, BuiltinClassLoader parent, URLClassPath ucp) {
    // 确保当父加载器是bootloader时返回null
    // name 是类加载器的名称
    super(name, parent == null || parent == ClassLoaders.bootLoader() ? null : parent);

    this.parent = parent;
    this.ucp = ucp;
    
    this.nameToModule = new ConcurrentHashMap<>();
    this.moduleToReader = new ConcurrentHashMap<>();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

BuiltinClassLoader也重写了loadClass方法,loadClass实际调用loadClassOrNull方法,来看下loadClassOrNull方法的实现。

源码位置:src/java.base/share/classes/jdk/internal/loader/BuiltinClassLoader.java

protected Class<?> loadClassOrNull(String cn, boolean resolve) {
    // 加锁,保证线程安全
    synchronized (getClassLoadingLock(cn)) {
        // 先去找一次class是否已经被加载了,此方法是ClassLoader中的native方法
        Class<?> c = findLoadedClass(cn);
        if (c == null) {
            // 这里会需要去先加载模块信息
            LoadedModule loadedModule = findLoadedModule(cn);
            if (loadedModule != null) {
                BuiltinClassLoader loader = loadedModule.loader();
                if (loader == this) {
                    if (VM.isModuleSystemInited()) {
                        c = findClassInModuleOrNull(loadedModule, cn);
                    }
                } else {
                    // 委托其他类加载器加载
                    c = loader.loadClassOrNull(cn);
                }
            } else {
                // 先调用父加载器的相关方法去加载一次
                if (parent != null) {
                    c = parent.loadClassOrNull(cn);
                }

                // 如果没加载到,则用当前加载器去加载
                if (c == null && hasClassPath() && VM.isModuleSystemInited()) {
                    // 此方法内会调用到defineClas方法完成类的定义
                    c = findClassOnClassPathOrNull(cn);
                }
            }

        }

        if (resolve && c != null)
            resolveClass(c);

        return c;
    }
}
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

和通常的双亲委派稍有差异,如果一个class属于某个module那么会直接调用该module的类加载器去加载, 而不是说直接用当前类加载器的双亲委派模型去加载。 但是找到这个class对应的类加载器后,还是会按照双亲委派去加载。

BuiltinClassLoader也重写了ClassLoader的findClass方法。

源码位置:src/java.base/share/classes/jdk/internal/loader/BuiltinClassLoader.java

@Override
protected Class<?> findClass(String cn) throws ClassNotFoundException {
    
    // 在模块中尝试查找
    LoadedModule loadedModule = findLoadedModule(cn);

    Class<?> c = null;
    if (loadedModule != null) {
        //  加载任务委派给模块的加载器
        if (loadedModule.loader() == this) {
            c = findClassInModuleOrNull(loadedModule, cn);
        }
    } else {
        // 类路径下查找
        if (hasClassPath()) {
            c = findClassOnClassPathOrNull(cn);
        }
    }

    // 都没有找到,抛出异常
    if (c == null)
        throw new ClassNotFoundException(cn);

    return c;
}
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

其中findClassOnClassPathOrNull是在类路径下查找类。

源码位置:src/java.base/share/classes/jdk/internal/loader/BuiltinClassLoader.java

private Class<?> findClassOnClassPathOrNull(String cn) {
    String path = cn.replace('.', '/').concat(".class");
    // 权限检查代码省去...
    Resource res = ucp.getResource(path, false);
    if (res != null) {
        try {
          return defineClass(cn, res);
       } catch (IOException ioe) {
        // TBD on how I/O errors should be propagated
       }
    }
    return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
# 4.3.2.2 BuiltinClassLoader的子类以及初始化

ClassLoaders类中分别初始化BootClassLoader、PlatformClassLoader和AppClassLoader类加载器。

源码位置:src/java.base/share/classes/jdk/internal/loader/ClassLoaders.java

public class ClassLoaders {

    // JDK内置类加载器
    private static final BootClassLoader BOOT_LOADER;
    private static final PlatformClassLoader PLATFORM_LOADER;
    private static final AppClassLoader APP_LOADER;

    // 初始化类加载器对象
    static {
        // 可以使用 -Xbootclasspath/a 或者 -javaagent 中的Boot-Class-Path属性指定
        String append = VM.getSavedProperty("jdk.boot.class.path.append");
        // 初始化BOOT_LOADER
        BOOT_LOADER =
            new BootClassLoader((append != null && append.length() > 0)
                ? new URLClassPath(append, true)
                : null);
        
        // 初始化PLATFORM_LOADER并指定AppClassLoader的父加载器BOOT_LOADER        
        PLATFORM_LOADER = new PlatformClassLoader(BOOT_LOADER);

        // 获取classpath路径
        String cp = System.getProperty("java.class.path");
        if (cp == null || cp.length() == 0) {
            String initialModuleName = System.getProperty("jdk.module.main");
            cp = (initialModuleName == null) ? "" : null;
        }
        URLClassPath ucp = new URLClassPath(cp, false);
        // 初始化APP_LOADER并指定AppClassLoader的父加载器为PLATFORM_LOADER
        APP_LOADER = new AppClassLoader(PLATFORM_LOADER, ucp);
    }
  
    // ...
 }   
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

从类加载器实例的初始化代码可以看出,BootClassLoader用来加载jdk.boot.class.path.append参数指定的类,在初始化PLATFORM_LOADER是指定BOOT_LOADER为其父类,在初始化AppClassLoader是指定PLATFORM_LOADER为其父类,构成了类加载器的三层结构。

再来看下JDK9以上特有的PlatformClassLoader类:

private static class PlatformClassLoader extends BuiltinClassLoader {
    
    PlatformClassLoader(BootClassLoader parent) {
        // 类加载器名称为platform
        super("platform", parent, null);
    }
    
    // ...
}
1
2
3
4
5
6
7
8
9

不同类加载器负责加载对应的模块,在编译JDK时指定。

代码来源:jdk11-1ddf9a99e4ad/make/common/Modules.gmk

  • BOOT_MODULES是由引导加载程序定义的模块:
java.base               java.datatransfer
java.desktop            java.instrument
java.logging            java.management
java.management.rmi     java.naming
java.prefs              java.rmi
java.security.sasl      java.xml
jdk.internal.vm.ci      jdk.jfr
jdk.management          jdk.management.jfr
jdk.management.agent    jdk.net
jdk.sctp                jdk.unsupported
jdk.naming.rmi
1
2
3
4
5
6
7
8
9
10
11
  • PLATFORM_MODULES是由平台加载程序定义的模块:
java.net.http           java.scripting  
java.security.jgss      java.smartcardio    
java.sql                java.sql.rowset
java.transaction.xa     java.xml.crypto
jdk.accessibility       jdk.charsets
jdk.crypto.cryptoki     jdk.crypto.ec
jdk.dynalink            jdk.httpserver
jdk.jsobject            jdk.localedata
jdk.naming.dns          jdk.scripting.nashorn
jdk.security.auth       jdk.security.jgss
jdk.xml.dom             jdk.zipfs
jdk.crypto.mscapi       jdk.crypto.ucrypto
java.compiler           jdk.aot
jdk.internal.vm.compiler
jdk.internal.vm.compiler.management
java.se
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • JRE_TOOL_MODULES是JRE中包含的工具,由AppClassLoader加载:
jdk.jdwp.agent
jdk.pack
jdk.scripting.nashorn.shell
1
2
3

未列出的其他模块由AppClassLoader加载。