RFR: 8357782: JVM JIT Causes Static Initialization Order Issue
Manuel Hässig
mhaessig at openjdk.org
Wed Jun 11 07:12:15 UTC 2025
# Issue Summary
When C1 compiles a method that allocates a new instance of a class that is not fully initialized at compile time, it does not take into account that the `<clinit>` might run a static initializer that might have side effects. Consider the following example:
class A {
static class B {
static String field;
static void test() {
String tmp = field;
new C(field);
}
}
static class C {
static {
B.field = "Hello";
}
C(String val) {
if (val == null) {
throw new RuntimeException("Should not reach here");
}
}
}
}
Here, `B.field` gets assigned in `C`'s static initializer. Since C1 believes that the `newinstance` does not have memory side effects, local value numbering eliminates the field access for the argument in `C.<init>` because it believes that `B.field` is still the same as `tmp`. Hence, the assignment in `C.<clinit>` gets effectively ignored and the code triggers the runtime exception. Because this only happens if `C` is not fully initialized when it is compiled, we need `-Xcomp` to reproduce this issue.
# Changes
To fix the illustrated issue, this PR ensures that `newinstance` kills the memory state in C1's LVN if the class might not be fully initialized. Since we can not reliably detect if a class has a static initializer, we kill memory whenever a class is not yet loaded or, if it has already been loaded, when it has not been fully initialized, which is conservative and might kill memory when it is not necessary for correctness and have an impact on performance in the form of some additional field accesses.
# Benchmark Results
Since this might have an effect on startup, I ran some benchmarks. The results mostly did not show effects outside the run-to-run variance.
# Testing
- [x] [GHA](https://github.com/mhaessig/jdk/actions/runs/15560262225)
- [x] tier1 through tier3 plus Oracle internal testing on Oracle supported platforms and OSes
# Acknowledgements
Shout out to @TobiHartmann who wrote the reproducer that became the regression test and helped me find my way around C1 and narrow down the problem.
-------------
Commit messages:
- C1: kill memory for new instance if class has not been initialized
- Add ciInstanceKlass::has_class_initializer
- Add regression test
Changes: https://git.openjdk.org/jdk/pull/25725/files
Webrev: https://webrevs.openjdk.org/?repo=jdk&pr=25725&range=00
Issue: https://bugs.openjdk.org/browse/JDK-8357782
Stats: 84 lines in 4 files changed: 82 ins; 0 del; 2 mod
Patch: https://git.openjdk.org/jdk/pull/25725.diff
Fetch: git fetch https://git.openjdk.org/jdk.git pull/25725/head:pull/25725
PR: https://git.openjdk.org/jdk/pull/25725
More information about the hotspot-compiler-dev
mailing list