Search code examples
javajvmclassloader

Which exact classes are loaded by Platform ClassLoader?


Let's assume, we're using OpenJDK 20. The official documentation says:

The platform class loader is responsible for loading the platform classes. Platform classes include Java SE platform APIs, their implementation classes, and JDK-specific run-time classes that are defined by the platform class loader or its ancestors. The platform class loader can be used as the parent of a ClassLoader instance.

At the same time, I understood from the Oracle specification that Bootstrap ClassLoader loads the core runtime classes required to start the JVM. I also understood that Platform ClassLoader does not load anything into an empty program:

jshell> ClassLoader.getPlatformClassLoader().getDefinedPackages();
$1 ==> Package[0] { }

But it loads some packages from Java SE, like 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 }

And some not (like java.util.logging, as the same child of java-se module):

jshell> java.util.logging.ConsoleHandler.class.getClassLoader();
$4 ==> null

Am I correct in stating that Platform ClassLoader loads the public types of system modules that a developer might need? If so, which specific packages (or better ask, modules?) fall under this "might"?

Thanks in advance.


Solution

  • The decision is made per module. You can use the following code to group the modules per class loader:

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

    which will print something alike:

    boot
        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.jfr
        jdk.management
        jdk.management.agent
        jdk.management.jfr
        jdk.naming.rmi
        jdk.net
        jdk.sctp
        jdk.unsupported
    platform
        java.compiler
        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
    app
        jdk.attach
        jdk.compiler
        jdk.editpad
        jdk.internal.ed
        jdk.internal.jvmstat
        jdk.internal.le
        jdk.internal.opt
        jdk.jartool
        jdk.javadoc
        jdk.jconsole
        jdk.jdeps
        jdk.jdi
        jdk.jdwp.agent
        jdk.jlink
        jdk.jshell
        jdk.jstatd
        jdk.unsupported.desktop
    

    Demonstration on tio.run

    Note that the classes loaded by the bootstrap class loader are not strictly necessary to run the JVM. Only a few classes within the java.base module are really required by the JVM. You can use the jlink command to create an environment containing only the modules required by your application and in the most extreme example, that could be an environment only containing java.base (and your application module, to be useful).

    You may recognize a pattern in the decision about the module to class loader mapping, but as with any decision made by humans, there can be outliers.

    It’s not really important anyway; when you ask a module-capable class loader to load a class belonging to a named module (and all JDK classes belong to a named module) within the same module layer, it will delegate to the module’s class loader anyway.

    For example, the following code

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

    prints

    interface com.sun.source.doctree.VersionTree
        jdk.internal.loader.ClassLoaders$AppClassLoader@5bc2b487
        module jdk.compiler
    

    demonstrating that asking the platform class loader for a class belonging to a module associated with the application class loader will succeed.

    Demonstration on tio.run