When running with CDS archived heap data that includes full module graph (FMG) related subgraphs, we can leave some archived classes unresolved in configurations where the JVM is not using the FMG. If a GC runs very early, it may trace objects/classes in these FMG subgraphs before they have been resolved. At that point the corresponding CLD has not been set up yet, and attempting to access it leads to a SIGSEGV.
This occurs because when FMG is disabled, we clear the roots associated with the FMG, but we leave the subgraphs untouched. In the same configuration we also skip resolving classes for these FMG subgraphs, leaving them in an unresolved state. Historically this has not been observed because GCs typically run later, after the involved classes have been resolved as a side-effect of allocating instances of them via the Interpreter.
With the reproducer below we see this failing in JDK mainline, but we first noticed this in the development of Automatic Heap Sizing (AHS) for ZGC, where GCs run very early "by default":
make -C ../build/mainline test TEST=runtime/logging/RedefineClasses.java JTREG_JAVA_OPTIONS="-XX:+UseZGC -XX:+ZCollectionIntervalOnly -XX:ZCollectionIntervalMajor=0.001"
The bailout path that skips resolving FMG subgraphs when FMG is disabled is:
SystemDictionary::initialize
HeapShared::resolve_classes
HeapShared::resolve_classes_for_subgraphs
HeapShared::resolve_classes_for_subgraph_of(JavaThread* current, Klass* k)
HeapShared::resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAPS)
...
if (record->is_full_module_graph() && !CDSConfig::is_using_full_module_graph()) {
if (log_is_enabled(Info, aot, heap)) {
ResourceMark rm(THREAD);
log_info(aot, heap)("subgraph %s cannot be used because full module graph is disabled",
k->external_name());
}
return nullptr;
}
...
This occurs because when FMG is disabled, we clear the roots associated with the FMG, but we leave the subgraphs untouched. In the same configuration we also skip resolving classes for these FMG subgraphs, leaving them in an unresolved state. Historically this has not been observed because GCs typically run later, after the involved classes have been resolved as a side-effect of allocating instances of them via the Interpreter.
With the reproducer below we see this failing in JDK mainline, but we first noticed this in the development of Automatic Heap Sizing (AHS) for ZGC, where GCs run very early "by default":
make -C ../build/mainline test TEST=runtime/logging/RedefineClasses.java JTREG_JAVA_OPTIONS="-XX:+UseZGC -XX:+ZCollectionIntervalOnly -XX:ZCollectionIntervalMajor=0.001"
The bailout path that skips resolving FMG subgraphs when FMG is disabled is:
SystemDictionary::initialize
HeapShared::resolve_classes
HeapShared::resolve_classes_for_subgraphs
HeapShared::resolve_classes_for_subgraph_of(JavaThread* current, Klass* k)
HeapShared::resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAPS)
...
if (record->is_full_module_graph() && !CDSConfig::is_using_full_module_graph()) {
if (log_is_enabled(Info, aot, heap)) {
ResourceMark rm(THREAD);
log_info(aot, heap)("subgraph %s cannot be used because full module graph is disabled",
k->external_name());
}
return nullptr;
}
...
- links to
-
Review(master)
openjdk/jdk/29938