RFR: 8374307: Fix deoptimization storm caused by Action_none in GraphKit::uncommon_trap
Boris Ulasevich
bulasevich at openjdk.org
Tue Dec 23 18:24:27 UTC 2025
We observed a deoptimization storm caused by GraphKit::uncommon_trap generator logic. GraphKit::uncommon_trap considers the too_many_recompiles metric. If the threshold is overflowed, it replaces Deoptimization::Action_reinterpret with Deoptimization::Action_none (see code snippet below).
This replacement changes the uncommon_trap logic: once execution hits a trap, the VM performs deoptimization but does not recompile the method anymore. In an "unlucky" case, when the code part calling this uncommon_trap becomes frequent, a deoptimization storm occurs (thousands of deoptimizations per second) causing a significant performance drop.
The original problematic method, which triggered repeated recompilations, is a high-performance compressed binary serialization algorithm with heavy use of conditional branches driven by bitmasks. See a standalone [synthetic benchmark](https://bugs.openjdk.org/secure/attachment/118045/UnstableIf.java) to reproduce the issue.
The issue arises when the method overcomes a global recompilation threshold before stabilizing specific trap counters.
Current thresholds:
- Recompilation Limit (too_many_recompiles):
Condition: decompile_count() >= (PerMethodRecompilationCutoff / 2) + 1
Default: 201 (derived from default PerMethodRecompilationCutoff = 400).
- Specific Trap Limits (too_many_traps):
Checks if the trap count for a specific reason exceeds:
PerMethodTrapLimit (Default: 100) - for Reason_unstable_if, Reason_unstable_fused_if, etc.
PerMethodSpecTrapLimit (Default: 5000) - for Reason_speculate_class_check, Reason_speculate_null_check, etc.
With the gived defaults, if the only reason for the method recompilation is unstable_if, the system stabilizes after 100 traps (PerMethodTrapLimit). However, if the method experiences traps and recompilations for different reasons, the total number of recompilations can exceed 200 before hitting the limit for unstable_if traps. This triggers Action_none and causes the deopt storm.
The proposal is a minimal change in GraphKit::uncommon_trap: apply the same `too_many_recompiles` threshold inside `Parse::path_is_suitable_for_uncommon_trap` - this ensures that on the final recompilation C2 gets a hint not to speculate on untaken branches anymore.
As an alternative solution, we can revisit GraphKit::uncommon_trap. This "Temporary fix" has persisted in the codebase for 17 years, so it is probably time to change it as well. Any comments are welcome
case Deoptimization::Action_reinterpret:
// Temporary fix for 6529811 to allow virtual calls to be sure they
// get the chance to go from mono->bi->mega
if (!keep_exact_action &&
Deoptimization::trap_request_index(trap_request) < 0 &&
too_many_recompiles(reason)) {
// This BCI is causing too many recompilations.
if (C->log() != nullptr) {
C->log()->elem("observe that='trap_action_change' reason='%s' from='%s' to='none'",
Deoptimization::trap_reason_name(reason),
Deoptimization::trap_action_name(action));
}
action = Deoptimization::Action_none;
trap_request = Deoptimization::make_trap_request(reason, action);
} else {
C->set_trap_can_recompile(true);
}
-------------
Commit messages:
- 8374307: Fix deoptimization storm caused by Action_none in GraphKit::uncommon_trap
Changes: https://git.openjdk.org/jdk/pull/28966/files
Webrev: https://webrevs.openjdk.org/?repo=jdk&pr=28966&range=00
Issue: https://bugs.openjdk.org/browse/JDK-8374307
Stats: 2 lines in 1 file changed: 2 ins; 0 del; 0 mod
Patch: https://git.openjdk.org/jdk/pull/28966.diff
Fetch: git fetch https://git.openjdk.org/jdk.git pull/28966/head:pull/28966
PR: https://git.openjdk.org/jdk/pull/28966
More information about the hotspot-compiler-dev
mailing list