JDK-8345969 : AOTCache should always include internal modules required for optional JVM features
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.lang.module
  • Priority: P3
  • Status: New
  • Resolution: Unresolved
  • Submitted: 2024-12-11
  • Updated: 2025-01-03
Related Reports
Relates :  
Description
Background:

Several optional JVM features require internal modules:

  + EnableJVMCI requires jdk.internal.vm.ci
  + JFR requires jdk.jfr
  + -javaagent requires java.instrument
  + -Dcom.sun.management requires jdk.management.agent

Currently, these modules are added to the module graph only when the JVM feature is enabled by the command-line. E.g. (arguments.cpp),

  if (status && (FlightRecorderOptions || StartFlightRecording)) {
    if (!create_numbered_module_property("jdk.module.addmods", "jdk.jfr", _addmods_count++)) {
      return false;
    }
  }

However, this cannot be efficiently supported by the AOTCache (CDS).

When the AOTCache is assembled, we store a copy of the boot layer (java.lang.System::bootLayer). When the AOTCache is loaded in a production run, we can reuse the cached boot layer if the production run uses the same set of modules as the AOTCache assembly phase.

By default, none of the above optional JVM features are enabled in the assembly phase. Therefore, the AOTCache does not include modules such as jdk.jfr.

In the production run, if the user decides to use JFR, the jdk.jfr module will be added. As a result we cannot use the cached boot layer. The boot layer must be created from scratch (by reading information about all required modules, etc), resulting in slower start-up time.

Further, if an AOTCache is created with -XX:+AOTClassLinking, the entire AOTCache will be rejected if the cached boot layer cannot be used

- The cached boot layer is used to ensure that the same set of classes are visible between assembly phase and production run, so that we can perform ahead-of-time resolution of symbolic references between the cached classes.
- Other cached heap objects may reference objects in the cached boot layer (e.g., java.lang.Class -> java.lang.Module). If we create the boot layer from scratch, such references will no longer be valid.

================================================
Proposal:

When an AOTCache is created, or when an AOTCache is loaded into a production run, the JVM automatically add the following four internal modules  (if available from the module image) into the boot layer:

  + jdk.internal.vm.ci
  + jdk.jfr
  + java.instrument
  + jdk.management.agent

As a result, the AOTCache can be used whether optional JVM features (such as JFR) are enabled or not.

================================================
The size impact is minimal, as we store only meta information for the additional modules (not their classes) into the AOTCache. 

$ java -XX:AOTMode=create -XX:AOTCache=baseline.aot -XX:AOTConfiguration=$JAVA_HOME/lib/classlist \
    -p mlib/com.foo.jar -m com.foo/com.foo.Test
$ java -XX:AOTMode=create -XX:AOTCache=extra.aot -XX:AOTConfiguration=$JAVA_HOME/lib/classlist \
    --add-modules=jdk.internal.vm.ci,jdk.jfr,java.instrument,jdk.management.agent \
    -p mlib/com.foo.jar -m com.foo/com.foo.Test
$ ls -l *aot
-r--r--r-- 1 iklam iklam 15130624 Dec 10 23:37 baseline.aot
-r--r--r-- 1 iklam iklam 15138816 Dec 10 23:38 extra.aot
$ expr 15138816 - 15130624
8192
Comments
> For JVMCI, always including jdk.internal.vm.ci in the AOTCache will greatly simplify testing. In addition to simplifying testing, it will also most likely simplify efforts to add Graal support for future features such as Ahead-of-Time Code Compilation (https://openjdk.org/jeps/8335368).
03-01-2025

An alternative to this RFE would be the ability to dynamically add extra modules into the archivedBootLayer: https://github.com/openjdk/jdk/blob/31ceec7cd55b455cddf0953cc23aaa64612bd6e7/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java#L161
13-12-2024

I started a discussion on https://mail.openjdk.org/pipermail/jigsaw-dev/2024-December/015080.html To answer question from about: AOTCache is a cache that contains "that the app might need". If the app happens to use them, they will be available immediately without runtime processing. In the case of jdk.internal.vm.ci, the proposed change will unconditionally resolve it as part of the root set. My assumption is that it shouldn't cause any compatibility issue (the fact that this module is in the root set shouldn't cause applications to break). For Alan's remark about custom images, we can check if the image contains these modules before adding them. My main focus is JFR and JVMCI for now. It's quite common for people to switch JFR on and off in production environments, so we wat both cases to benefit from AOTCache. For JVMCI, always including jdk.internal.vm.ci in the AOTCache will greatly simplify testing. See JDK-8345826 and bugs linked therein for the headaches we are having with testing.
13-12-2024

Does adding extra modules to the AOTCache change semantics wrt default root sets? The term "cache" seems to imply that AOTCache is purely a performance optimization. That is, if jdk.internal.vm.ci is added to the AOTCache, does it mean it will unconditionally be resolved as part of the root set?
12-12-2024

This is going to require discussion as it's proposing changing policies and default root sets that have implications. Note that some of these CLI options add root modules that export APIs or require modules that export APIs. EnableJVMCI is a discussion in itself as there different environments depending on build. Custom run-time images that include some or none of these modules will be part the discussion, as will the existing ability to load these modules into a child layer (java.instrument and jdk.management.agent can be loaded at runtime).
11-12-2024

Note: when using the standard JDK 24 image, the jdk.jfr modules is always loaded even if the user doesn't explicitly JFR. This is true even if you run with a module that doesn't require anything other than java.base: $ java -Xlog:module+load=debug -p mlib/com.foo.jar -m com.foo/com.foo.Test | grep module,load | grep jfr [0.026s][info][module,load] jdk.management.jfr location: jrt:/jdk.management.jfr [0.026s][info][module,load] jdk.jfr location: jrt:/jdk.jfr If running a classpath application, jdk.jfr, java.instrument and jdk.management.agent are always included $ java -Xshare:off -Xlog:module+load=debug -cp . HelloWorld | egrep '(jdk.jfr)|(java.instrument)|(jdk.management.agent)' [0.026s][info][module,load] java.instrument location: jrt:/java.instrument [0.026s][info][module,load] jdk.jfr location: jrt:/jdk.jfr [0.026s][info][module,load] jdk.management.agent location: jrt:/jdk.management.agent
11-12-2024