JDK-8295673 : Deprecate and disable legacy parallel class loading workaround for non-parallel-capable class loaders
  • Type: Enhancement
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 20
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2022-10-19
  • Updated: 2022-12-09
  • Resolved: 2022-11-07
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 20
20 b23Fixed
Related Reports
CSR :  
Relates :  
Relates :  
Relates :  
Sub Tasks
JDK-8296446 :  
Description
double_lock_wait was added in JDK 6 to workaround the custom class loader deadlock problem [1] before parallel capable class loaders were added. 

It's expected that custom class loaders using non-hierarchical class delegation model should migrate to parallel-capable class loaders to avoid the deadlock problem as described in [1]. 

This double_lock_wait logic should be removed.

[1] https://openjdk.org/groups/core-libs/ClassLoaderProposal.html

double_lock_wait does the following:

// 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.

Comments
Changeset: 76790ad2 Author: Coleen Phillimore <coleenp@openjdk.org> Date: 2022-11-07 23:31:21 +0000 URL: https://git.openjdk.org/jdk/commit/76790ad2427b777b470ef3e5474fa8df9f3bf875
07-11-2022

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/10832 Date: 2022-10-24 12:16:54 +0000
03-11-2022

This is going to need to go through the Deprecation Process with an option to reenable it and a CSR, and release note.
20-10-2022

Here is my understanding of the double-lock-wait usage ... First, it applies to non-parallel-capable loaders for which we lock the classloader instance when loading a class. We attempt to resolve a class C with loader L in thread T2. We find that C is not yet loaded so we lock L We grab the SD lock and check to see if loading of C is in progress - by seeing if there is a placeholder entry - and more particularly we check if the loading of C's superclass SC, is in progress. We then release the SD lock. Given another thread, T1, has initiated loading of C and is in the process of loading SC, and given that we must have the lock of L during loading, something is not quite right: T1 must have relinquished the lock on L - and the only way to do that is by a call to L.wait(). The assumption here is that the code in L for loading C and SC has determined there is some kind of deadlock potential and so released the lock on L by doing a wait(), thus breaking the deadlock potential. T1 expects to be notified by some other logic in L and then continue loading SC (and ancestors) and eventually C. Meanwhile T2 is stuck: it has the lock for L that T1 will need to complete loading of SC and C, and so it can't directly wait for T1 to be done whilst holding that lock. There is where double_lock_wait comes into play: - T2 does L.notify** and then uses the VM to unlock L - T2 does a wait on the SD_lock, knowing it will be notified when some other thread completes a classloading action (which may or may not be the load of SC and/or C) - T2 then releases the SD_lock, reacquires the lock of L, then reacquires the SD_lock - this repeats until C or SC (as applicable) has been loaded ** This notification seems odd to me. We don't know what condition the Java code in ClassLoader L is waiting for, so the most likely scenario here is that T1 will see this as a spurious wakeup, and go back to waiting. I can't see how this actually helps T1 to make progress and so could be elided. Basically double_lock_wait sets up a polling loop waiting for C/SC to be loaded, but it can't tell when that happens directly so it just waits on the SD_lock and wakes up after every class load (and for good measure reissues that notification to T1) and eventually SC will be loaded and T2 proceeds. The expectation today is that any non-parallel-capable non-hierarchical-delegation classloaders that needed this support back in the JDK 5/6 timeframe will by now have migrated to being parallel-capable classloaders. This was mainly an issue in the Java Enterprise Environment (EE) setting and the use of App servers back in the day.
20-10-2022

The common workaround described at https://openjdk.org/groups/core-libs/ClassLoaderProposal.html: Right now some customers work around the deadlock by explicitly issuing a wait() on the class loader lock. While this has not been sufficient to solve many customers problems, we need to continue to support this until those customers have an opportunity to migrate to the new mechanism.
20-10-2022