< prev index next > src/hotspot/share/runtime/synchronizer.cpp
Print this page
/*
- * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
#include "memory/universe.hpp"
#include "oops/markWord.hpp"
#include "oops/oop.inline.hpp"
#include "runtime/atomic.hpp"
#include "runtime/frame.inline.hpp"
+ #include "runtime/globals.hpp"
#include "runtime/handles.inline.hpp"
#include "runtime/handshake.hpp"
#include "runtime/interfaceSupport.inline.hpp"
#include "runtime/javaThread.hpp"
#include "runtime/lockStack.inline.hpp"
#include "runtime/vframe.hpp"
#include "runtime/vmThread.hpp"
#include "utilities/align.hpp"
#include "utilities/dtrace.hpp"
#include "utilities/events.hpp"
+ #include "utilities/globalDefinitions.hpp"
#include "utilities/linkedlist.hpp"
#include "utilities/preserveException.hpp"
void MonitorList::add(ObjectMonitor* m) {
ObjectMonitor* head;
if (obj->klass()->is_value_based()) {
return false;
}
+ if (LockingMode == LM_LIGHTWEIGHT) {
+ LockStack& lock_stack = current->lock_stack();
+ if (lock_stack.is_full()) {
+ // Always go into runtime if the lock stack is full.
+ return false;
+ }
+ if (lock_stack.try_recursive_enter(obj)) {
+ // Recursive lock successful.
+ current->inc_held_monitor_count();
+ return true;
+ }
+ }
+
const markWord mark = obj->mark();
if (mark.has_monitor()) {
ObjectMonitor* const m = mark.monitor();
// An async deflation or GC can race us before we manage to make
return false; // revert to slow-path
}
// Handle notifications when synchronizing on value based classes
- void ObjectSynchronizer::handle_sync_on_value_based_class(Handle obj, JavaThread* current) {
- frame last_frame = current->last_frame();
+ void ObjectSynchronizer::handle_sync_on_value_based_class(Handle obj, JavaThread* locking_thread) {
+ assert(locking_thread == Thread::current() || locking_thread->is_obj_deopt_suspend(), "must be");
+ frame last_frame = locking_thread->last_frame();
bool bcp_was_adjusted = false;
// Don't decrement bcp if it points to the frame's first instruction. This happens when
// handle_sync_on_value_based_class() is called because of a synchronized method. There
// is no actual monitorenter instruction in the byte code in this case.
if (last_frame.is_interpreted_frame() &&
last_frame.interpreter_frame_set_bcp(last_frame.interpreter_frame_bcp() - 1);
bcp_was_adjusted = true;
}
if (DiagnoseSyncOnValueBasedClasses == FATAL_EXIT) {
- ResourceMark rm(current);
+ ResourceMark rm;
stringStream ss;
- current->print_active_stack_on(&ss);
+ locking_thread->print_active_stack_on(&ss);
char* base = (char*)strstr(ss.base(), "at");
char* newline = (char*)strchr(ss.base(), '\n');
if (newline != nullptr) {
*newline = '\0';
}
fatal("Synchronizing on object " INTPTR_FORMAT " of klass %s %s", p2i(obj()), obj->klass()->external_name(), base);
} else {
assert(DiagnoseSyncOnValueBasedClasses == LOG_WARNING, "invalid value for DiagnoseSyncOnValueBasedClasses");
- ResourceMark rm(current);
+ ResourceMark rm;
Log(valuebasedclasses) vblog;
vblog.info("Synchronizing on object " INTPTR_FORMAT " of klass %s", p2i(obj()), obj->klass()->external_name());
- if (current->has_last_Java_frame()) {
+ if (locking_thread->has_last_Java_frame()) {
LogStream info_stream(vblog.info());
- current->print_active_stack_on(&info_stream);
+ locking_thread->print_active_stack_on(&info_stream);
} else {
vblog.info("Cannot find the last Java frame");
}
EventSyncOnValueBasedClass event;
#endif
}
// -----------------------------------------------------------------------------
// Monitor Enter/Exit
+
+ void ObjectSynchronizer::enter_for(Handle obj, BasicLock* lock, JavaThread* locking_thread) {
+ // When called with locking_thread != Thread::current() some mechanism must synchronize
+ // the locking_thread with respect to the current thread. Currently only used when
+ // deoptimizing and re-locking locks. See Deoptimization::relock_objects
+ assert(locking_thread == Thread::current() || locking_thread->is_obj_deopt_suspend(), "must be");
+ if (!enter_fast_impl(obj, lock, locking_thread)) {
+ // Inflated ObjectMonitor::enter_for is required
+
+ // An async deflation can race after the inflate_for() call and before
+ // enter_for() can make the ObjectMonitor busy. enter_for() returns false
+ // if we have lost the race to async deflation and we simply try again.
+ while (true) {
+ ObjectMonitor* monitor = inflate_for(locking_thread, obj(), inflate_cause_monitor_enter);
+ if (monitor->enter_for(locking_thread)) {
+ return;
+ }
+ assert(monitor->is_being_async_deflated(), "must be");
+ }
+ }
+ }
+
+ void ObjectSynchronizer::enter(Handle obj, BasicLock* lock, JavaThread* current) {
+ assert(current == Thread::current(), "must be");
+ if (!enter_fast_impl(obj, lock, current)) {
+ // Inflated ObjectMonitor::enter is required
+
+ // An async deflation can race after the inflate() call and before
+ // enter() can make the ObjectMonitor busy. enter() returns false if
+ // we have lost the race to async deflation and we simply try again.
+ while (true) {
+ ObjectMonitor* monitor = inflate(current, obj(), inflate_cause_monitor_enter);
+ if (monitor->enter(current)) {
+ return;
+ }
+ }
+ }
+ }
+
// The interpreter and compiler assembly code tries to lock using the fast path
// of this algorithm. Make sure to update that code if the following function is
// changed. The implementation is extremely sensitive to race condition. Be careful.
+ bool ObjectSynchronizer::enter_fast_impl(Handle obj, BasicLock* lock, JavaThread* locking_thread) {
- void ObjectSynchronizer::enter(Handle obj, BasicLock* lock, JavaThread* current) {
if (obj->klass()->is_value_based()) {
- handle_sync_on_value_based_class(obj, current);
+ handle_sync_on_value_based_class(obj, locking_thread);
}
- current->inc_held_monitor_count();
+ locking_thread->inc_held_monitor_count();
if (!useHeavyMonitors()) {
if (LockingMode == LM_LIGHTWEIGHT) {
// Fast-locking does not use the 'lock' argument.
- LockStack& lock_stack = current->lock_stack();
- if (lock_stack.can_push()) {
- markWord mark = obj()->mark_acquire();
- while (mark.is_neutral()) {
- // Retry until a lock state change has been observed. cas_set_mark() may collide with non lock bits modifications.
- // Try to swing into 'fast-locked' state.
- assert(!lock_stack.contains(obj()), "thread must not already hold the lock");
- const markWord locked_mark = mark.set_fast_locked();
- const markWord old_mark = obj()->cas_set_mark(locked_mark, mark);
- if (old_mark == mark) {
- // Successfully fast-locked, push object to lock-stack and return.
- lock_stack.push(obj());
- return;
- }
- mark = old_mark;
+ LockStack& lock_stack = locking_thread->lock_stack();
+ if (lock_stack.is_full()) {
+ // We unconditionally make room on the lock stack by inflating
+ // the least recently locked object on the lock stack.
+
+ // About the choice to inflate least recently locked object.
+ // First we must chose to inflate a lock, either some lock on
+ // the lock-stack or the lock that is currently being entered
+ // (which may or may not be on the lock-stack).
+ // Second the best lock to inflate is a lock which is entered
+ // in a control flow where there are only a very few locks being
+ // used, as the costly part of inflated locking is inflation,
+ // not locking. But this property is entirely program dependent.
+ // Third inflating the lock currently being entered on when it
+ // is not present on the lock-stack will result in a still full
+ // lock-stack. This creates a scenario where every deeper nested
+ // monitorenter must call into the runtime.
+ // The rational here is as follows:
+ // Because we cannot (currently) figure out the second, and want
+ // to avoid the third, we inflate a lock on the lock-stack.
+ // The least recently locked lock is chosen as it is the lock
+ // with the longest critical section.
+
+ log_info(monitorinflation)("LockStack capacity exceeded, inflating.");
+ ObjectMonitor* monitor = inflate_for(locking_thread, lock_stack.bottom(), inflate_cause_vm_internal);
+ assert(monitor->owner() == Thread::current(), "must be owner=" PTR_FORMAT " current=" PTR_FORMAT " mark=" PTR_FORMAT,
+ p2i(monitor->owner()), p2i(Thread::current()), monitor->object()->mark_acquire().value());
+ assert(!lock_stack.is_full(), "must have made room here");
+ }
+
+ markWord mark = obj()->mark_acquire();
+ while (mark.is_neutral()) {
+ // Retry until a lock state change has been observed. cas_set_mark() may collide with non lock bits modifications.
+ // Try to swing into 'fast-locked' state.
+ assert(!lock_stack.contains(obj()), "thread must not already hold the lock");
+ const markWord locked_mark = mark.set_fast_locked();
+ const markWord old_mark = obj()->cas_set_mark(locked_mark, mark);
+ if (old_mark == mark) {
+ // Successfully fast-locked, push object to lock-stack and return.
+ lock_stack.push(obj());
+ return true;
}
+ mark = old_mark;
}
- // All other paths fall-through to inflate-enter.
+
+ if (mark.is_fast_locked() && lock_stack.try_recursive_enter(obj())) {
+ // Recursive lock successful.
+ return true;
+ }
+
+ // Failed to fast lock.
+ return false;
} else if (LockingMode == LM_LEGACY) {
markWord mark = obj->mark();
if (mark.is_neutral()) {
// Anticipate successful CAS -- the ST of the displaced mark must
// be visible <= the ST performed by the CAS.
lock->set_displaced_header(mark);
if (mark == obj()->cas_set_mark(markWord::from_pointer(lock), mark)) {
- return;
+ return true;
}
- // Fall through to inflate() ...
} else if (mark.has_locker() &&
- current->is_lock_owned((address) mark.locker())) {
+ locking_thread->is_lock_owned((address) mark.locker())) {
assert(lock != mark.locker(), "must not re-lock the same lock");
assert(lock != (BasicLock*) obj->mark().value(), "don't relock with same BasicLock");
lock->set_displaced_header(markWord::from_pointer(nullptr));
- return;
+ return true;
}
// The object header will never be displaced to this lock,
// so it does not matter what the value is, except that it
// must be non-zero to avoid looking like a re-entrant lock,
// and must not look locked either.
lock->set_displaced_header(markWord::unused_mark());
+
+ // Failed to fast lock.
+ return false;
}
} else if (VerifyHeavyMonitors) {
guarantee((obj->mark().value() & markWord::lock_mask_in_place) != markWord::locked_value, "must not be lightweight/stack-locked");
}
- // An async deflation can race after the inflate() call and before
- // enter() can make the ObjectMonitor busy. enter() returns false if
- // we have lost the race to async deflation and we simply try again.
- while (true) {
- ObjectMonitor* monitor = inflate(current, obj(), inflate_cause_monitor_enter);
- if (monitor->enter(current)) {
- return;
- }
- }
+ return false;
}
void ObjectSynchronizer::exit(oop object, BasicLock* lock, JavaThread* current) {
current->dec_held_monitor_count();
if (!useHeavyMonitors()) {
markWord mark = object->mark();
if (LockingMode == LM_LIGHTWEIGHT) {
// Fast-locking does not use the 'lock' argument.
- while (mark.is_fast_locked()) {
- // Retry until a lock state change has been observed. cas_set_mark() may collide with non lock bits modifications.
- const markWord unlocked_mark = mark.set_unlocked();
- const markWord old_mark = object->cas_set_mark(unlocked_mark, mark);
- if (old_mark == mark) {
- current->lock_stack().remove(object);
- return;
+ LockStack& lock_stack = current->lock_stack();
+ if (mark.is_fast_locked() && lock_stack.try_recursive_exit(object)) {
+ // Recursively unlocked.
+ return;
+ }
+
+ if (mark.is_fast_locked() && lock_stack.is_recursive(object)) {
+ // This lock is recursive but is not at the top of the lock stack so we're
+ // doing an unbalanced exit. We have to fall thru to inflation below and
+ // let ObjectMonitor::exit() do the unlock.
+ } else {
+ while (mark.is_fast_locked()) {
+ // Retry until a lock state change has been observed. cas_set_mark() may collide with non lock bits modifications.
+ const markWord unlocked_mark = mark.set_unlocked();
+ const markWord old_mark = object->cas_set_mark(unlocked_mark, mark);
+ if (old_mark == mark) {
+ size_t recursions = lock_stack.remove(object) - 1;
+ assert(recursions == 0, "must not be recursive here");
+ return;
+ }
+ mark = old_mark;
}
- mark = old_mark;
}
} else if (LockingMode == LM_LEGACY) {
markWord dhw = lock->displaced_header();
if (dhw.value() == 0) {
// If the displaced header is null, then this exit matches up with
// We have to take the slow-path of possible inflation and then exit.
// The ObjectMonitor* can't be async deflated until ownership is
// dropped inside exit() and the ObjectMonitor* must be !is_busy().
ObjectMonitor* monitor = inflate(current, object, inflate_cause_vm_internal);
- if (LockingMode == LM_LIGHTWEIGHT && monitor->is_owner_anonymous()) {
- // It must be owned by us. Pop lock object from lock stack.
- LockStack& lock_stack = current->lock_stack();
- oop popped = lock_stack.pop();
- assert(popped == object, "must be owned by this thread");
- monitor->set_owner_from_anonymous(current);
- }
+ assert(!monitor->is_owner_anonymous(), "must not be");
monitor->exit(current);
}
// -----------------------------------------------------------------------------
// JNI locks on java objects
v = (v ^ (v >> 19)) ^ (t ^ (t >> 8));
current->_hashStateW = v;
value = v;
}
- value &= markWord::hash_mask;
+ value &= UseCompactObjectHeaders ? markWord::hash_mask_compact : markWord::hash_mask;
if (value == 0) value = 0xBAD;
assert(value != markWord::no_hash, "invariant");
return value;
}
return;
}
(void)inflate(Thread::current(), obj, inflate_cause_vm_internal);
}
- // Can be called from non JavaThreads (e.g., VMThread) for FastHashCode
- // calculations as part of JVM/TI tagging.
- static bool is_lock_owned(Thread* thread, oop obj) {
- assert(LockingMode == LM_LIGHTWEIGHT, "only call this with new lightweight locking enabled");
- return thread->is_Java_thread() ? JavaThread::cast(thread)->lock_stack().contains(obj) : false;
+ ObjectMonitor* ObjectSynchronizer::inflate(Thread* current, oop obj, const InflateCause cause) {
+ assert(current == Thread::current(), "must be");
+ if (LockingMode == LM_LIGHTWEIGHT && current->is_Java_thread()) {
+ return inflate_impl(JavaThread::cast(current), obj, cause);
+ }
+ return inflate_impl(nullptr, obj, cause);
+ }
+
+ ObjectMonitor* ObjectSynchronizer::inflate_for(JavaThread* thread, oop obj, const InflateCause cause) {
+ assert(thread == Thread::current() || thread->is_obj_deopt_suspend(), "must be");
+ return inflate_impl(thread, obj, cause);
}
- ObjectMonitor* ObjectSynchronizer::inflate(Thread* current, oop object,
- const InflateCause cause) {
+ ObjectMonitor* ObjectSynchronizer::inflate_impl(JavaThread* inflating_thread, oop object, const InflateCause cause) {
+ // The JavaThread* inflating_thread parameter is only used by LM_LIGHTWEIGHT and requires
+ // that the inflating_thread == Thread::current() or is suspended throughout the call by
+ // some other mechanism.
+ // Even with LM_LIGHTWEIGHT the thread might be nullptr when called from a non
+ // JavaThread. (As may still be the case from FastHashCode). However it is only
+ // important for the correctness of the LM_LIGHTWEIGHT algorithm that the thread
+ // is set when called from ObjectSynchronizer::enter from the owning thread,
+ // ObjectSynchronizer::enter_for from any thread, or ObjectSynchronizer::exit.
EventJavaMonitorInflate event;
for (;;) {
const markWord mark = object->mark_acquire();
// The mark can be in one of the following states:
// * inflated - Just return if using stack-locking.
// If using fast-locking and the ObjectMonitor owner
- // is anonymous and the current thread owns the
- // object lock, then we make the current thread the
- // ObjectMonitor owner and remove the lock from the
- // current thread's lock stack.
+ // is anonymous and the inflating_thread owns the
+ // object lock, then we make the inflating_thread
+ // the ObjectMonitor owner and remove the lock from
+ // the inflating_thread's lock stack.
// * fast-locked - Coerce it to inflated from fast-locked.
// * stack-locked - Coerce it to inflated from stack-locked.
// * INFLATING - Busy wait for conversion from stack-locked to
// inflated.
// * neutral - Aggressively inflate the object.
// CASE: inflated
if (mark.has_monitor()) {
ObjectMonitor* inf = mark.monitor();
markWord dmw = inf->header();
assert(dmw.is_neutral(), "invariant: header=" INTPTR_FORMAT, dmw.value());
- if (LockingMode == LM_LIGHTWEIGHT && inf->is_owner_anonymous() && is_lock_owned(current, object)) {
- inf->set_owner_from_anonymous(current);
- JavaThread::cast(current)->lock_stack().remove(object);
+ if (LockingMode == LM_LIGHTWEIGHT && inf->is_owner_anonymous() &&
+ inflating_thread != nullptr && inflating_thread->lock_stack().contains(object)) {
+ inf->set_owner_from_anonymous(inflating_thread);
+ size_t removed = inflating_thread->lock_stack().remove(object);
+ inf->set_recursions(removed - 1);
}
return inf;
}
if (LockingMode != LM_LIGHTWEIGHT) {
continue;
}
}
// CASE: fast-locked
- // Could be fast-locked either by current or by some other thread.
+ // Could be fast-locked either by the inflating_thread or by some other thread.
//
// Note that we allocate the ObjectMonitor speculatively, _before_
// attempting to set the object's mark to the new ObjectMonitor. If
- // this thread owns the monitor, then we set the ObjectMonitor's
- // owner to this thread. Otherwise, we set the ObjectMonitor's owner
+ // the inflating_thread owns the monitor, then we set the ObjectMonitor's
+ // owner to the inflating_thread. Otherwise, we set the ObjectMonitor's owner
// to anonymous. If we lose the race to set the object's mark to the
// new ObjectMonitor, then we just delete it and loop around again.
//
LogStreamHandle(Trace, monitorinflation) lsh;
if (LockingMode == LM_LIGHTWEIGHT && mark.is_fast_locked()) {
ObjectMonitor* monitor = new ObjectMonitor(object);
monitor->set_header(mark.set_unlocked());
- bool own = is_lock_owned(current, object);
+ bool own = inflating_thread != nullptr && inflating_thread->lock_stack().contains(object);
if (own) {
- // Owned by us.
- monitor->set_owner_from(nullptr, current);
+ // Owned by inflating_thread.
+ monitor->set_owner_from(nullptr, inflating_thread);
} else {
// Owned by somebody else.
monitor->set_owner_anonymous();
}
markWord monitor_mark = markWord::encode(monitor);
markWord old_mark = object->cas_set_mark(monitor_mark, mark);
if (old_mark == mark) {
// Success! Return inflated monitor.
if (own) {
- JavaThread::cast(current)->lock_stack().remove(object);
+ size_t removed = inflating_thread->lock_stack().remove(object);
+ monitor->set_recursions(removed - 1);
}
// Once the ObjectMonitor is configured and object is associated
// with the ObjectMonitor, it is safe to allow async deflation:
_in_use_list.add(monitor);
// Hopefully the performance counters are allocated on distinct
// cache lines to avoid false sharing on MP systems ...
OM_PERFDATA_OP(Inflations, inc());
if (log_is_enabled(Trace, monitorinflation)) {
- ResourceMark rm(current);
+ ResourceMark rm;
lsh.print_cr("inflate(has_locker): object=" INTPTR_FORMAT ", mark="
INTPTR_FORMAT ", type='%s'", p2i(object),
object->mark().value(), object->klass()->external_name());
}
if (event.should_commit()) {
// Hopefully the performance counters are allocated on distinct cache lines
// to avoid false sharing on MP systems ...
OM_PERFDATA_OP(Inflations, inc());
if (log_is_enabled(Trace, monitorinflation)) {
- ResourceMark rm(current);
+ ResourceMark rm;
lsh.print_cr("inflate(has_locker): object=" INTPTR_FORMAT ", mark="
INTPTR_FORMAT ", type='%s'", p2i(object),
object->mark().value(), object->klass()->external_name());
}
if (event.should_commit()) {
// Hopefully the performance counters are allocated on distinct
// cache lines to avoid false sharing on MP systems ...
OM_PERFDATA_OP(Inflations, inc());
if (log_is_enabled(Trace, monitorinflation)) {
- ResourceMark rm(current);
+ ResourceMark rm;
lsh.print_cr("inflate(neutral): object=" INTPTR_FORMAT ", mark="
INTPTR_FORMAT ", type='%s'", p2i(object),
object->mark().value(), object->klass()->external_name());
}
if (event.should_commit()) {
< prev index next >