oracle/graal · error · PermanentBailoutException
unbalanced monitors - monitors do not match
Error message
unbalanced monitors - monitors do not match
What it means
Companion of the locked-object-count check: both predecessors hold the same number of monitors, but at some index the monitor identities cannot be reconciled (different monitorIds and MonitorIdNode.monitorIdentityEquals fails). Even matching counts are not enough — the same locks, identified by their locking objects, must be held at the merge, otherwise Graal permanently bails out.
Source
Thrown at compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/FrameStateBuilder.java:518
}
for (int i = 0; i < stackSize(); i++) {
ValueNode x = stack[i];
ValueNode y = other.stack[i];
assert x != null;
assert y != null;
if (x != y && (x == TWO_SLOT_MARKER || x.isDeleted() || y == TWO_SLOT_MARKER || y.isDeleted() || x.getStackKind() != y.getStackKind())) {
throw new GraalError(incompatibilityErrorMessage("mismatch in stack types", other));
}
}
if (lockedObjects.length != other.lockedObjects.length) {
throw new PermanentBailoutException(incompatibilityErrorMessage("unbalanced monitors - locked objects do not match", other));
}
for (int i = 0; i < lockedObjects.length; i++) {
if (monitorIds[i] != other.monitorIds[i]) {
if (MonitorIdNode.monitorIdentityEquals(monitorIds[i], other.monitorIds[i])) {
continue;
}
throw new PermanentBailoutException(incompatibilityErrorMessage("unbalanced monitors - monitors do not match", other));
}
}
}
public boolean areLocksMergeableWith(FrameStateBuilder other) {
if (lockedObjects.length != other.lockedObjects.length) {
return false;
}
for (int i = 0; i < lockedObjects.length; i++) {
if (!MonitorIdNode.monitorIdentityEquals(monitorIds[i], other.monitorIds[i])) {
return false;
}
}
return true;
}
public void merge(AbstractMergeNode block, FrameStateBuilder other) {
checkCompatibleWith(other);View on GitHub (pinned to a66e9ccd1d)
Solutions
- Strictly verify the class to locate the offending merge and method.
- Restructure the code so the same lock object(s) are held on every path reaching the merge (lock before branching, or unlock before the join).
- Regenerate the bytecode without the tool transformation that introduced the divergent locks.
Defensive patterns
Strategy: validation
Validate before calling
// ensure the same lock object is held on all paths into a merge; // strict verification catches mismatched monitor identities early
Prevention
- Lock before branching or unlock before joining, never conditionally inside branches.
- Verify instrumented classes after obfuscation/coverage transformations.
When it happens
Trigger: The per-index loop over lockedObjects at a merge finding monitorIds[i] != other.monitorIds[i] with different underlying lock identity — e.g. two branches each did monitorenter on different objects and then flow together.
Common situations: Generated/obfuscated bytecode locking different objects on different branches before a join; instrumentation inserting conditional monitors; malformed monitorenter sequences that pass naive count checks but lock distinct objects.
Related errors
- unbalanced monitors - locked objects do not match
- At %s, bci %s: opcode %s (%s) has a stack effect of %s, the
- OSR compilation without OSR entry loop.
- OSR with locks disabled.
- Unbalanced monitor enter-exit in OSR compilation with locks:
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/8624ebf293d1f5e9.
Report an issue: GitHub.