1.Why

在Java Bug System中,我们可以看到Java9与ClassLoader相关的一系列issue:

JDK-8170294:java.lang: Define behaviour of null resource name

JDK-8161230:java.lang: Add ClassLoader.resources()

JDK-8165346:java.lang: Specify exceptional condition for get*Package()

JDK-8165793:java.lang: Add ClassLoader.isRegisteredAsParallelCapable()

JDK-6516909:java.lang: Enhanced ClassLoader with respect to loading array classes

JDK-8165563:java.lang: Update ClassLoader.getSystemClassLoader()

JDK-7180225:java.lang: Declare SecurityException consistently in class loader methods

JDK-8155770:Networking: Document support of jar: scheme URLs in URLClassLoader

这些Issue表明了两个信息:

  • 变化:在Java9,JVM的类加载机制发生了变化。
  • 变化不大:JVM类加载机制的变化不大。如果有较大的变化,就不会只通过几个issue来实施,应该通过JEP来落地。

进一步思考一下,不难理解为什么JVM会出现类加载机制的变化?

Ext ClassLoader为例:

在Java9之前,JDK为了具有扩展性,会将扩展的jar包放到<JAVA_HOME>\lib\extjava.ext.dirts下,Ext ClassLoader负责加载这些扩展包

而从Java9开始,Java9引入了JPMS(Java Platform Module System),模块化机制帮助Java类库天然地具备扩展性。

因此,原来的扩展机制可以废弃掉,用来加载这些路径类库的类加载器——Ext ClassLoader也就完成了它的历史使命。

说明:Java9中,基于JPMS机制,tools.jar、rt.jar从物理层面上被拆分成了10+个jmod文件。

至于,类加载机制的变化不大,说明Java9不会因为JPMS而推翻JVM原有的双亲委派模型类加载机制,只是为了在引入JPMS的同时,保证Java9的前向兼容性。

image-20210909131341238

2.变化点1:新的双亲委派模型

《【类加载机制】-1-类加载器剖析-类加载器速览》中,笔者对Java8的双亲委派模型评价为"妈宝模型"

即,任何一个待加载的类,都要从Application ClassLoader一路向上,问上级类加载器能否加载。

在Java9,因为有了JPMS机制,任何一个类一定都有归属的module

如果事先就定义好系统级的类加载器能够加载哪些系统级的module,那么这些module下的类就不必在加载的时候,都要走一圈向上级类加载器询问能否加载的过程了。

新的双亲委派模型,本质上就是在原有的双亲委派模型的基础上,给各种类加载器约定了责任田——即,谁的孩子谁直接带走。

下图对比了Java9前后的双亲委派模型差异。

比如:已知类A归属于系统moduleB,JVM已经约定系统moduleBBootStrap ClassLoader加载,

因此,当加载类A时,如下图蓝色线,类A直接交给BootStrap ClassLoader加载。

image-20210909135001699

附:Java9中各个类加载器默认加载的module

摘自《深入理解JVM》,周志明

  • BootStrap ClassLoader负责加载的模块:

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.httpserver

jdk.internal.vm.ci

jdk.management

jdk.management.agent

jdk.naming.rmi

jdk.net

jdk.sctp

jdk.unsupported

  • Platform ClassLoader负责的module

java.activation*

java.compiler*

java.corba*

java.scripting

java.se

java.se.ee

java.security.jgss

java.smartcardio

java.sql

java.sql.rowset

java.transaction*

java.xml.bind*

java.xml.crypto

java.xml.ws*

java.xml.ws.annotation*

jdk.accessibility

jdk.charsets

jdk.crypto.cryptoki

jdk.crypto.ec

jdk.dynalink

jdk.incubator.httpclient

jdk.internal.vm.compiler*

jdk.jsobject

jdk.localedata

jdk.naming.dns

jdk.scripting.nashorn

jdk.security.auth

jdk.security.jgss

jdk.xml.dom

jdk.zipfs

  • App ClassLoader负责加载的module

jdk.aot

jdk.attach

jdk.compiler

jdk.editpad

jdk.hotspot.agent

jdk.internal.ed

jdk.internal.jvmstat

jdk.internal.le

jdk.internal.opt

jdk.jartool

jdk.javadoc

jdk.jcmd

jdk.jconsole

jdk.jdeps

jdk.jdi

jdk.jdwp.agent

jdk.jlink

jdk.jshell

jdk.jstatd

jdk.pack

jdk.policytool

jdk.rmic

jdk.scripting.nashorn.shell

jdk.xml.bind*

jdk.xml.ws*

3.变化点2:ClassLoader的类层次关系

  • 在Java9之前,JDK没有提供BootStrapClassLoader类,ExtClassLoader类和AppClassLoader类继承于URLClassLoader。
  • 在Java9之后,JDK提供了BootClassLoader类,同时BootClassLoaderPlatformClassLoaderAppClassLoader类继承于BuiltinClassLoader

image-20210909145749490

查看Java9的BuiltinClassLoader::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
protected Class<?> loadClassOrNull(String cn, boolean resolve) {
    synchronized (getClassLoadingLock(cn)) {
        // check if already loaded
        Class<?> c = findLoadedClass(cn);

        if (c == null) {
            // find the candidate module for this class
            LoadedModule loadedModule = findLoadedModule(cn);
            if (loadedModule != null) {
                // package is in a module
                BuiltinClassLoader loader = loadedModule.loader();
                if (loader == this) {
                    if (VM.isModuleSystemInited()) {
                        c = findClassInModuleOrNull(loadedModule, cn);
                    }
                } else {
                    // delegate to the other loader
                    c = loader.loadClassOrNull(cn);
                }
            } else {
                // check parent
                if (parent != null) {
                    c = parent.loadClassOrNull(cn);
                }

                // check class path
                if (c == null && hasClassPath() && VM.isModuleSystemInited()) {
                    c = findClassOnClassPathOrNull(cn);
                }
            }

        }

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

        return c;
    }
}

可以看到:JVM优先从module中查找是否加载了待加载的类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// find the candidate module for this class
if (loadedModule != null) {
    // package is in a module
    BuiltinClassLoader loader = loadedModule.loader();
    if (loader == this) {
        if (VM.isModuleSystemInited()) {
            c = findClassInModuleOrNull(loadedModule, cn);
        }
    } else {
        // delegate to the other loader
        c = loader.loadClassOrNull(cn);
    }
}

当待加载类不归属于系统级module,则还是沿用原来的双亲委派模型去加载:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
else {
    // check parent
    if (parent != null) {
    	c = parent.loadClassOrNull(cn);
    }

    // check class path
    if (c == null && hasClassPath() && VM.isModuleSystemInited()) {
    	c = findClassOnClassPathOrNull(cn);
    }
}

4.参考

1
深入理解JVM——周志明