Class Loader API Modifications for Deadlock Fix

Goals

Non-Goals

Motivation

Technical Design Constraints

Class loader Deadlock Problem

Brief Overview of Class Loading Interactions Between Class loader and the VM

Class loading requires cooperation between the VM and user level class loaders. Specifically, when the VM is performing constant pool resolution, among other things, the VM needs to call out to the user level class loader in order to find and define the requested class. On the other side, the class loader responsible for loading a class needs to call into the VM to determine if the class has already been loaded (findLoadedClass), and to define the class based on a bytecode stream (defineClass).

The current recommended logic in detail is:

                 User Level Class Loader                               VM
constant pool resolution
first acquire class loader lock
-->private synchronized loadClassInternal(String) <--- calls out to loadClassInternal(String)
| public loadClass(String)
| protected synchronized loadClass(String,boolean)
| protected final findLoadedClass(...) ---> VM SystemDictionary cache lookup
| (can not trigger further class loading)
| delegate (e.g. parent.loadClass(String))
| protected findClass(String)
| reads in bytes
| protected final defineClass(...) ---> resolves superclasses and superinterfaces
| which recursively calls out to
-------------------------------------------------- <--- loadClassInternal(String)

Custom class loader authors are encouraged to override findClass(String) to allow them to determine where or how the byte code stream is obtained. Some custom class loader authors override loadClass (String) or loadClass(String, boolean) to be able to change the delegation strategy.

Currently many class loading interactions are synchronized on the class loader lock. This works well for class loader delegation that assumes a DAG-based delegation hierarchy.

Customers have requested the ability to delegate to arbitrary class loaders. Currently this can cause deadlocks if class loaders delegate to each other without a fixed ordering.

Sample Deadlock Scenario: non-tree based delegation hierarchy

Class Hierarchy:
class A extends B
class C extends D

ClassLoader Delegation Hierarchy:

Custom Classloader CL1:
directly loads class A
delegates to custom ClassLoader CL2 for class B

Custom Classloader CL2:
directly loads class C
delegates to custom ClassLoader CL1 for class D

Thread 1:
Use CL1 to load class A (locks CL1)
defineClass A triggers
loadClass B (try to lock CL2)

Thread 2:
Use CL2 to load class C (locks CL2)
defineClass C triggers
loadClass D (try to lock CL1)

Proposed Solution

                 User Level Class Loader                                   VM
constant pool resolution
If ParallelCapable:lock class/class loader pair
else acquire class loader lock
*DEPRECATE*: private synchronized loadClassInternal
|-->public loadClass(String) <--- calls out to loadClass(String)
| call loadClass(String,boolean) (*no longer synchronized*)
| *if ParallelCapable:*
| *synchronize on a class-name-based-lock*
| *else synchronize on "this" (backward compatibility)*
| protected final findLoadedClass(...) ---> VM SystemDictionary cache lookup
| (can not trigger further class loading)
| delegate (e.g. parent.loadClass(String))
| protected findClass(String)
| reads in by
| protected final defineClass(...) ---> resolves superclasses and superinterfaces
| which recursively calls out to
-------------------------------------------------- <--- loadClass(String)

API Modifications

Java APIs
VM Flags for early access testing
Java <-> VM interface changes

 Class Loader changes required

Suggested Model for Custom Class Loaders

Internal Implementation Details

The most important point to make is that all class loaders that want the deadlock fix, must be cleaned up to ensure that they are multi-thread safe, i.e. that they allow the class loader to load multiple classes at the same time. The basic approach is to remove synchronization on the class loader itself, and provide smaller granularity locking for critical sections. It is critical that there be no synchronization on the class loader lock for parallel capable class loaders. Given that class loading is frequently triggered implicitly, e.g. by newInstance, and given the interactions between the VM and class loaders, acquiring the class loader lock holds the risk of causing a deadlock.

All of the JRE class loaders which customers extend to create custom class loaders need to invoke registerAsParallelCapable(). They all must be made multi-thread safe for concurrent class loading of different class name/class loader pairs, so methods inherited by parallel capable custom class loaders are thread-safe. Sun will need to commit to these changes for customers to count on, and will need to add this to the documentation of these classes. In addition, in some cases the JRE class loaders need to delegate in other than the traditional tree-based hierarchy. These include:

Other class loaders do not require any changes. These include:

VM Behavior Changes

Sincere apologies on how long it has taken us to fix this problem. Part of the reason this fix has taken so long is that we first needed to modify the VM in the following ways:

Details of VM handling of  specific class loader cases:

Alternatives Considered and Not Chosen

Open Issues