Platform ClassLoader 到底加载了哪些类?

Yah*_*ski 5 java jvm classloader

假设我们使用的是 OpenJDK 20。官方文档说

平台类加载器负责加载平台类。平台类包括 Java SE 平台 API、它们的实现类以及由平台类加载器或其祖先定义的特定于 JDK 的运行时类。平台类加载器可以用作ClassLoader实例的父类。

同时,我从Oracle规范中了解到,Bootstrap ClassLoader加载启动JVM所需的核心运行时类。我还了解到 Platform ClassLoader 不会将任何内容加载到空程序中:

jshell> ClassLoader.getPlatformClassLoader().getDefinedPackages();
$1 ==> Package[0] { }
Run Code Online (Sandbox Code Playgroud)

但它从 Java SE加载一些java.sql包,例如:

jshell> java.sql.Connection.class.getClassLoader()
$2 ==> jdk.internal.loader.ClassLoaders$PlatformClassLoader@27fa135a

jshell> ClassLoader.getPlatformClassLoader().getDefinedPackages()
$3 ==> Package[1] { package java.sql }
Run Code Online (Sandbox Code Playgroud)

有些则不然(例如,作为java-se 模块java.util.logging的同一个子模块):

jshell> java.util.logging.ConsoleHandler.class.getClassLoader();
$4 ==> null
Run Code Online (Sandbox Code Playgroud)

我是否正确地指出 Platform ClassLoader 加载开发人员可能需要的公共类型的系统模块?如果是这样,哪些特定的包(或者更好地问,模块?)属于这个“可能”?

提前致谢。

Hol*_*ger 5

该决定是按模块做出的。您可以使用以下代码对每个类加载器的模块进行分组:

\n
ModuleLayer.boot().modules().stream()\n  .collect(Collectors.groupingBy(\n    m -> Optional.ofNullable(m.getClassLoader())\n                 .map(ClassLoader::getName).orElse("boot"),\n    Collectors.mapping(Module::getName,\n                       Collectors.toCollection(() -> new TreeSet<>()))))\n  .entrySet().stream()\n  .sorted(Comparator.comparingInt(\n                     e -> List.of("boot", "platform", "app").indexOf(e.getKey())))\n  .map(e -> e.getKey() + "\\n\\t" + String.join("\\n\\t", e.getValue()))\n  .forEach(System.out::println);\n
Run Code Online (Sandbox Code Playgroud)\n

这将打印类似的内容:

\n
boot\n    java.base\n    java.datatransfer\n    java.desktop\n    java.instrument\n    java.logging\n    java.management\n    java.management.rmi\n    java.naming\n    java.prefs\n    java.rmi\n    java.security.sasl\n    java.xml\n    jdk.jfr\n    jdk.management\n    jdk.management.agent\n    jdk.management.jfr\n    jdk.naming.rmi\n    jdk.net\n    jdk.sctp\n    jdk.unsupported\nplatform\n    java.compiler\n    java.net.http\n    java.scripting\n    java.security.jgss\n    java.smartcardio\n    java.sql\n    java.sql.rowset\n    java.transaction.xa\n    java.xml.crypto\n    jdk.accessibility\n    jdk.charsets\n    jdk.crypto.cryptoki\n    jdk.crypto.ec\n    jdk.dynalink\n    jdk.httpserver\n    jdk.jsobject\n    jdk.localedata\n    jdk.naming.dns\n    jdk.scripting.nashorn\n    jdk.security.auth\n    jdk.security.jgss\n    jdk.xml.dom\n    jdk.zipfs\napp\n    jdk.attach\n    jdk.compiler\n    jdk.editpad\n    jdk.internal.ed\n    jdk.internal.jvmstat\n    jdk.internal.le\n    jdk.internal.opt\n    jdk.jartool\n    jdk.javadoc\n    jdk.jconsole\n    jdk.jdeps\n    jdk.jdi\n    jdk.jdwp.agent\n    jdk.jlink\n    jdk.jshell\n    jdk.jstatd\n    jdk.unsupported.desktop\n
Run Code Online (Sandbox Code Playgroud)\n

tio.run 上的演示

\n

请注意,引导类加载器加载的类并不是运行 JVM 所必需的。java.baseJVM 真正需要模块中的少数类。您可以使用该jlink命令创建一个仅包含应用程序所需的模块的环境,在最极端的示例中,这可能是一个仅包含java.base(以及您的应用程序模块,有用)的环境。

\n

您可能会在有关模块到类加载器映射的决策中认识到一种模式,但与人类做出的任何决策一样,可能存在异常值。

\n

无论如何,它\xe2\x80\x99s并不重要;当您要求支持模块的类加载器在同一模块层中加载属于命名模块的类(并且所有 JDK 类都属于命名模块)时,它无论如何都会委托给 module\xe2\x80\x99s 类加载器。

\n

例如下面的代码

\n
Class<?> c = Class.forName("com.sun.source.doctree.VersionTree",\n    false, ClassLoader.getPlatformClassLoader());\nSystem.out.println(c + "\\n\\t" + c.getClassLoader() + "\\n\\t" + c.getModule());\n
Run Code Online (Sandbox Code Playgroud)\n

印刷

\n
interface com.sun.source.doctree.VersionTree\n    jdk.internal.loader.ClassLoaders$AppClassLoader@5bc2b487\n    module jdk.compiler\n
Run Code Online (Sandbox Code Playgroud)\n

证明向平台类加载器请求属于与应用程序类加载器关联的模块的类将会成功。

\n

tio.run 上的演示

\n