It represents a redundant lock that has been removed in the bytecode.
I always find the source code a good place to start with things like this. A review of hotspot/src/share/vm/opto/callnode.cpp
from the openJDK had the following interesting comments:
// Redundant lock elimination
//
// There are various patterns of locking where we release and
// immediately reacquire a lock in a piece of code where no operations
// occur in between that would be observable. In those cases we can
// skip releasing and reacquiring the lock without violating any
// fairness requirements. Doing this around a loop could cause a lock
// to be held for a very long time so we concentrate on non-looping
// control flow. We also require that the operations are fully
// redundant meaning that we don't introduce new lock operations on
// some paths so to be able to eliminate it on others ala PRE. This
// would probably require some more extensive graph manipulation to
// guarantee that the memory edges were all handled correctly.
//
// Assuming p is a simple predicate which can't trap in any way and s
// is a synchronized method consider this code:
//
// s();
// if (p)
// s();
// else
// s();
// s();
//
// 1. The unlocks of the first call to s can be eliminated if the
// locks inside the then and else branches are eliminated.
//
// 2. The unlocks of the then and else branches can be eliminated if
// the lock of the final call to s is eliminated.
//
// Either of these cases subsumes the simple case of sequential control flow
So from the above, it would seem (at least in openJDK) that eliminated means the lock is maintained by the JVM through one or more set of release/acquire instructions.
Reviewing the javaVFrame::print_lock_info_on()
in hotspot/src/share/vm/runtime/vframe.cpp
shows where the check and output occurs:
// Print out all monitors that we have locked or are trying to lock
GrowableArray<MonitorInfo*>* mons = monitors();
if (!mons->is_empty()) {
bool found_first_monitor = false;
for (int index = (mons->length()-1); index >= 0; index--) {
MonitorInfo* monitor = mons->at(index);
if (monitor->eliminated() && is_compiled_frame()) { // Eliminated in compiled code
if (monitor->owner_is_scalar_replaced()) {
Klass* k = Klass::cast(monitor->owner_klass());
st->print("\t- eliminated <owner is scalar replaced> (a %s)", k->external_name());
} else {
oop obj = monitor->owner();
if (obj != NULL) {
print_locked_object_class_name(st, obj, "eliminated");
}
}
continue;
Further comments including the one above also allude to replacing the lock and unlock instructions with NOP.
I read the document Dirk referred to regarding lock elision and it seems to be Lock Coarsening rather than Elision:
Another optimization that can be used to reduce the cost of locking is lock coarsening. Lock coarsening is the process of merging adjacent synchronized blocks that use the same lock object. If the compiler cannot eliminate the locking using lock elision, it may be able to reduce the overhead by using lock coarsening.
But to be honest the difference is very subtle and the end effect is pretty much the same - you eliminate unnecessary locks and unlocks.