Skip to content

Commit

Permalink
8298469: Obsolete legacy parallel class loading workaround for non-pa…
Browse files Browse the repository at this point in the history
…rallel-capable class loaders

Reviewed-by: dholmes, fparain
  • Loading branch information
coleenp committed Mar 17, 2023
1 parent 02a4ee2 commit 932be35
Show file tree
Hide file tree
Showing 10 changed files with 17 additions and 163 deletions.
3 changes: 0 additions & 3 deletions src/hotspot/share/classfile/placeholders.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,6 @@ void PlaceholderEntry::add_seen_thread(JavaThread* thread, PlaceholderTable::cla
SeenThread* threadEntry = new SeenThread(thread);
SeenThread* seen = actionToQueue(action);

assert(action != PlaceholderTable::LOAD_INSTANCE || !EnableWaitForParallelLoad || seen == nullptr,
"Only one LOAD_INSTANCE allowed at a time");

if (seen == nullptr) {
set_threadQ(threadEntry, action);
return;
Expand Down
102 changes: 16 additions & 86 deletions src/hotspot/share/classfile/systemDictionary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -489,50 +489,6 @@ InstanceKlass* SystemDictionary::resolve_super_or_fail(Symbol* class_name,
return superk;
}

// We only get here if this thread finds that another thread
// has already claimed the placeholder token for the current operation,
// but that other thread either never owned or gave up the
// object lock
// Waits on SystemDictionary_lock to indicate placeholder table updated
// On return, caller must recheck placeholder table state
//
// We only get here if
// 1) custom classLoader, i.e. not bootstrap classloader
// 2) custom classLoader has broken the class loader objectLock
// so another thread got here in parallel
//
// lockObject must be held.
// Complicated dance due to lock ordering:
// Must first release the classloader object lock to
// allow initial definer to complete the class definition
// and to avoid deadlock
// Reclaim classloader lock object with same original recursion count
// Must release SystemDictionary_lock after notify, since
// class loader lock must be claimed before SystemDictionary_lock
// to prevent deadlocks
//
// The notify allows applications that did an untimed wait() on
// the classloader object lock to not hang.
static void double_lock_wait(JavaThread* thread, Handle lockObject) {
assert_lock_strong(SystemDictionary_lock);

assert(EnableWaitForParallelLoad,
"Only called when enabling legacy parallel class loading logic "
"for non-parallel capable class loaders");
assert(lockObject() != nullptr, "lockObject must be non-null");
bool calledholdinglock
= ObjectSynchronizer::current_thread_holds_lock(thread, lockObject);
assert(calledholdinglock, "must hold lock for notify");
assert(!is_parallelCapable(lockObject), "lockObject must not be parallelCapable");
// These don't throw exceptions.
ObjectSynchronizer::notifyall(lockObject, thread);
intx recursions = ObjectSynchronizer::complete_exit(lockObject, thread);
SystemDictionary_lock->wait();
SystemDictionary_lock->unlock();
ObjectSynchronizer::reenter(lockObject, recursions, thread);
SystemDictionary_lock->lock();
}

// If the class in is in the placeholder table, class loading is in progress.
// For cases where the application changes threads to load classes, it
// is critical to ClassCircularity detection that we try loading
Expand All @@ -554,20 +510,17 @@ static void handle_parallel_super_load(Symbol* name,
}

// Bootstrap and non-parallel capable class loaders use the LOAD_INSTANCE placeholder to
// wait for parallel class loading and to check for circularity error for Xcomp when loading
// signature classes.
// parallelCapable class loaders do NOT wait for parallel loads to complete
// wait for parallel class loading and/or to check for circularity error for Xcomp when loading.
static bool needs_load_placeholder(Handle class_loader) {
return class_loader.is_null() || !is_parallelCapable(class_loader);
}

// For bootstrap and non-parallelCapable class loaders, check and wait for
// another thread to complete loading this class.
InstanceKlass* SystemDictionary::handle_parallel_loading(JavaThread* current,
Symbol* name,
ClassLoaderData* loader_data,
Handle lockObject,
bool* throw_circularity_error) {
// Check for other threads loading this class either to throw CCE or wait in the case of the boot loader.
static InstanceKlass* handle_parallel_loading(JavaThread* current,
Symbol* name,
ClassLoaderData* loader_data,
bool must_wait_for_class_loading,
bool* throw_circularity_error) {
PlaceholderEntry* oldprobe = PlaceholderTable::get_entry(name, loader_data);
if (oldprobe != nullptr) {
// -Xcomp calls load_signature_classes which might result in loading
Expand All @@ -577,32 +530,15 @@ InstanceKlass* SystemDictionary::handle_parallel_loading(JavaThread* current,
log_circularity_error(name, oldprobe);
*throw_circularity_error = true;
return nullptr;
} else {
} else if (must_wait_for_class_loading) {
// Wait until the first thread has finished loading this class. Also wait until all the
// threads trying to load its superclass have removed their placeholders.
while (oldprobe != nullptr &&
(oldprobe->instance_load_in_progress() || oldprobe->super_load_in_progress())) {

// We only get here if the application has released the
// classloader lock when another thread was in the middle of loading a
// superclass/superinterface for this class, and now
// this thread is also trying to load this class.
// To minimize surprises, the first thread that started to
// load a class should be the one to complete the loading
// with the classfile it initially expected.
// This logic has the current thread wait once it has done
// all the superclass/superinterface loading it can, until
// the original thread completes the class loading or fails
// If it completes we will use the resulting InstanceKlass
// which we will find below in the systemDictionary.

if (lockObject.is_null()) {
SystemDictionary_lock->wait();
} else if (EnableWaitForParallelLoad) {
double_lock_wait(current, lockObject);
} else {
return nullptr;
}
// LOAD_INSTANCE placeholders are used to implement parallel capable class loading
// for the bootclass loader.
SystemDictionary_lock->wait();

// Check if classloading completed while we were waiting
InstanceKlass* check = loader_data->dictionary()->find_class(current, name);
Expand Down Expand Up @@ -709,7 +645,6 @@ InstanceKlass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
bool load_placeholder_added = false;

// Add placeholder entry to record loading instance class
// Four cases:
// case 1. Bootstrap classloader
// This classloader supports parallelism at the classloader level
// but only allows a single thread to load a class/classloader pair.
Expand All @@ -718,20 +653,16 @@ InstanceKlass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
// These class loaders lock a per-class object lock when ClassLoader.loadClass()
// is called. A LOAD_INSTANCE placeholder isn't used for mutual exclusion.
// case 3. traditional classloaders that rely on the classloader object lock
// There should be no need for need for LOAD_INSTANCE, except:
// case 4. traditional class loaders that break the classloader object lock
// as a legacy deadlock workaround. Detection of this case requires that
// this check is done while holding the classloader object lock,
// and that lock is still held when calling classloader's loadClass.
// For these classloaders, we ensure that the first requestor
// completes the load and other requestors wait for completion.
// There should be no need for need for LOAD_INSTANCE for mutual exclusion,
// except the LOAD_INSTANCE placeholder is used to detect CCE for -Xcomp.
// TODO: should also be used to detect CCE for parallel capable class loaders but it's not.
{
MutexLocker mu(THREAD, SystemDictionary_lock);
if (needs_load_placeholder(class_loader)) {
loaded_class = handle_parallel_loading(THREAD,
name,
loader_data,
lockObject,
class_loader.is_null(),
&throw_circularity_error);
}

Expand All @@ -742,8 +673,7 @@ InstanceKlass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
if (check != nullptr) {
loaded_class = check;
} else if (needs_load_placeholder(class_loader)) {
// Add the LOAD_INSTANCE token. Threads will wait on loading to complete for this thread,
// and check for ClassCircularityError with -Xcomp.
// Add the LOAD_INSTANCE token. Threads will wait on loading to complete for this thread.
PlaceholderEntry* newprobe = PlaceholderTable::find_and_add(name, loader_data,
PlaceholderTable::LOAD_INSTANCE,
nullptr,
Expand Down
6 changes: 0 additions & 6 deletions src/hotspot/share/classfile/systemDictionary.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -302,12 +302,6 @@ class SystemDictionary : AllStatic {
static Klass* resolve_array_class_or_null(Symbol* class_name,
Handle class_loader,
Handle protection_domain, TRAPS);
static InstanceKlass* handle_parallel_loading(JavaThread* current,
Symbol* name,
ClassLoaderData* loader_data,
Handle lockObject,
bool* throw_circularity_error);

static void define_instance_class(InstanceKlass* k, Handle class_loader, TRAPS);
static InstanceKlass* find_or_define_helper(Symbol* class_name,
Handle class_loader,
Expand Down
2 changes: 1 addition & 1 deletion src/hotspot/share/runtime/arguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,6 @@ static SpecialFlag const special_jvm_flags[] = {
{ "DynamicDumpSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() },
{ "RequireSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() },
{ "UseSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() },
{ "EnableWaitForParallelLoad", JDK_Version::jdk(20), JDK_Version::jdk(21), JDK_Version::jdk(22) },

// --- Deprecated alias flags (see also aliased_jvm_flags) - sorted by obsolete_in then expired_in:
{ "DefaultMaxRAMFraction", JDK_Version::jdk(8), JDK_Version::undefined(), JDK_Version::undefined() },
Expand All @@ -551,6 +550,7 @@ static SpecialFlag const special_jvm_flags[] = {

// -------------- Obsolete Flags - sorted by expired_in --------------

{ "EnableWaitForParallelLoad", JDK_Version::jdk(20), JDK_Version::jdk(21), JDK_Version::jdk(22) },
{ "G1ConcRefinementGreenZone", JDK_Version::undefined(), JDK_Version::jdk(20), JDK_Version::undefined() },
{ "G1ConcRefinementYellowZone", JDK_Version::undefined(), JDK_Version::jdk(20), JDK_Version::undefined() },
{ "G1ConcRefinementRedZone", JDK_Version::undefined(), JDK_Version::jdk(20), JDK_Version::undefined() },
Expand Down
4 changes: 0 additions & 4 deletions src/hotspot/share/runtime/globals.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -688,10 +688,6 @@ const int ObjectAlignmentInBytes = 8;
"Allow parallel defineClass requests for class loaders " \
"registering as parallel capable") \
\
product(bool, EnableWaitForParallelLoad, false, \
"(Deprecated) Enable legacy parallel classloading logic for " \
"class loaders not registered as parallel capable") \
\
product_pd(bool, DontYieldALot, \
"Throw away obvious excess yield calls") \
\
Expand Down
19 changes: 0 additions & 19 deletions src/hotspot/share/runtime/objectMonitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1340,12 +1340,7 @@ void ObjectMonitor::ExitEpilog(JavaThread* current, ObjectWaiter* Wakee) {
OM_PERFDATA_OP(Parks, inc());
}


// -----------------------------------------------------------------------------
// Class Loader deadlock handling.
//
// complete_exit exits a lock returning recursion count
// complete_exit/reenter operate as a wait without waiting
// complete_exit requires an inflated monitor
// The _owner field is not always the Thread addr even with an
// inflated monitor, e.g. the monitor can be inflated by a non-owning
Expand All @@ -1370,20 +1365,6 @@ intx ObjectMonitor::complete_exit(JavaThread* current) {
return save;
}

// reenter() enters a lock and sets recursion count
// complete_exit/reenter operate as a wait without waiting
bool ObjectMonitor::reenter(intx recursions, JavaThread* current) {

guarantee(owner_raw() != current, "reenter already owner");
if (!enter(current)) {
return false;
}
// Entered the monitor.
guarantee(_recursions == 0, "reenter recursion");
_recursions = recursions;
return true;
}

// Checks that the current THREAD owns this monitor and causes an
// immediate return if it doesn't. We don't use the CHECK macro
// because we want the IMSE to be the only exception that is thrown
Expand Down
1 change: 0 additions & 1 deletion src/hotspot/share/runtime/objectMonitor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,6 @@ class ObjectMonitor : public CHeapObj<mtObjectMonitor> {

// Use the following at your own risk
intx complete_exit(JavaThread* current);
bool reenter(intx recursions, JavaThread* current);

private:
void AddWaiter(ObjectWaiter* waiter);
Expand Down
36 changes: 0 additions & 36 deletions src/hotspot/share/runtime/synchronizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -575,42 +575,6 @@ void ObjectSynchronizer::exit(oop object, BasicLock* lock, JavaThread* current)
monitor->exit(current);
}

// -----------------------------------------------------------------------------
// Class Loader support to workaround deadlocks on the class loader lock objects
// Also used by GC
// complete_exit()/reenter() are used to wait on a nested lock
// i.e. to give up an outer lock completely and then re-enter
// Used when holding nested locks - lock acquisition order: lock1 then lock2
// 1) complete_exit lock1 - saving recursion count
// 2) wait on lock2
// 3) when notified on lock2, unlock lock2
// 4) reenter lock1 with original recursion count
// 5) lock lock2
// NOTE: must use heavy weight monitor to handle complete_exit/reenter()
intx ObjectSynchronizer::complete_exit(Handle obj, JavaThread* current) {
// The ObjectMonitor* can't be async deflated until ownership is
// dropped inside exit() and the ObjectMonitor* must be !is_busy().
ObjectMonitor* monitor = inflate(current, obj(), inflate_cause_vm_internal);
intx recur_count = monitor->complete_exit(current);
current->dec_held_monitor_count(recur_count + 1);
return recur_count;
}

// NOTE: must use heavy weight monitor to handle complete_exit/reenter()
void ObjectSynchronizer::reenter(Handle obj, intx recursions, JavaThread* current) {
// An async deflation can race after the inflate() call and before
// reenter() -> enter() can make the ObjectMonitor busy. reenter() ->
// 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_vm_internal);
if (monitor->reenter(recursions, current)) {
current->inc_held_monitor_count(recursions + 1);
return;
}
}
}

// -----------------------------------------------------------------------------
// JNI locks on java objects
// NOTE: must use heavy weight monitor to handle jni monitor enter
Expand Down
6 changes: 0 additions & 6 deletions src/hotspot/share/runtime/synchronizer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,6 @@ class ObjectSynchronizer : AllStatic {
static bool quick_notify(oopDesc* obj, JavaThread* current, bool All);
static bool quick_enter(oop obj, JavaThread* current, BasicLock* Lock);

// used by classloading to free classloader object lock,
// wait on an internal lock, and reclaim original lock
// with original recursion count
static intx complete_exit(Handle obj, JavaThread* current);
static void reenter (Handle obj, intx recursions, JavaThread* current);

// Inflate light weight monitor to heavy weight monitor
static ObjectMonitor* inflate(Thread* current, oop obj, const InflateCause cause);
// This version is only for internal use
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
* @compile -XDignore.symbol.file AsmClasses.java
* @compile test-classes/ClassInLoader.java test-classes/A.java test-classes/B.java ../share/ThreadPrint.java
* @run main/othervm ParallelSuperTest
* @run main/othervm -XX:+EnableWaitForParallelLoad ParallelSuperTest -parallel
* @run main/othervm ParallelSuperTest -parallel -parallelCapable
*/

Expand Down

1 comment on commit 932be35

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.