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