|
Class Loader API
Modifications for Deadlock Fix
Version .01: November 13, 2008
Authors: Valerie Peng, Jeff
Nisewanger, Karen Kinnear
Audience: Community Review
Goals
Non-Goals
Other Class Loader RFEs that
require API modification will be addressed via individual RFEs,
separately from this fix for the deadlock problem.
This proposal does not address any re-architecture for
performance
Motivation
Technical Design
Constraints
Must allow non-hierarchical class
delegation topologies without deadlocks
Must be backwards compatible
Existing class loaders must
continue to work unchanged
Existing code that invokes a
class loader directly via loadClass(String) or a class loader that
calls loadClass(String, boolean) must continue to work unchanged
Must continue to support the
temporary risky flag combination -XX:+UnlockDiagnosticVMOptions
-XX:+UnsyncloadClass
Common workaround
Must be possible to define
guidance to migrate custom class loaders to take advantage of
asynchronous behavior
Today, with the class loader lock
held around class loader operations, class loaders are not required
to handle asynchronous multi-threaded class loading. A mechanism
which avoids deadlock will require multi-thread safety awareness.
Given that supporting multi-threaded class loading requires careful
thought and planning, the new mechanism must provide a way for a
class loader to explicitly claim that they are multi-thread safe.
Therefore: the fix for these bugs
will require code changes by custom class loader authors if their
class loaders are candidates for deadlock.
Given that each class loader that
chooses to adopt the new mechanism must be explicitly safe for
concurrent class loading by multiple threads, the claim that a class
loader is multi-thread safe must not be automatically inherited.
Class loaders that adopt the new
mechanism must be able to run on older JREs. So, for example, the
design must allow for a way to detect if the mechanism exists, and
can not count on inheritance of classes or interfaces that may not
be present.
Goal: minimal code changes for
custom class loader authors
Goal: If we need to make tradeoffs on how much thought and
changes are needed for custom class loader authors, depending on
whether they override findClass(String) or
loadClass(String)/loadClass(String, boolean), make it simpler for
those who override findClass(String). There are more of those custom
class loaders and generally to override either loadClass(...)
already requires dealing with more class loader complexity.
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 strictly tree 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
New protected static method in
java.lang.ClassLoader, boolean registerAsParallelCapable()
To be called once from class
initialization of a subclass of java.lang.ClassLoader which
supports parallel loading of classes.
In order for a a class loader
class to register itself as parallel capable, all of its
superclasses which are instances of java.lang.ClassLoader must also
already be registered as parallel capable when invoking this
protected static method. Otherwise the request will be ignored.
registerAsParallelCapable() will
return true if the class loader successfully registers as
parallelCapable, even if accidentally called a second time. It will
return false on failure.
java.lang.ClassLoader API changes:
Deprecate private
loadClassInternal(), retain temporarily for backward compatibility
Remove the synchronized keyword
from:
protected synchronized Class<?>
loadClass(String name, boolean resolve) method.
public synchronized void
setDefaultAssertionStatus(boolean enabled)
public synchronized void
setPackageAssertionStatus(String packageName, boolean enabled)
public synchronized void
setClassAssertionStatus(String className, boolean enabled)
public synchronized void clearAssertionStatus()
VM Flags for early
access testing
AlwaysLockClassLoader - default
false. Provided for ParallelClassLoader that has a bug in handling
multiple threads in parallel. Expected to ship in product.
AllowParallelDefineClass - default
false. If two threads try to define the same class/class loader pair
in parallel, throw linkageError, duplicate class definition. If you
change the flag, we would allow parallel defineClass requests, using
the result of the first requester. This would require a JVMS
clarification. Experimental in product for early access feedback
only, not expected to ship.
MustCallLoadClassInternal - default false. In case a customer
actually depended on this call, perhaps expecting to find this on
the stack. Note that this will only enforce calling
loadClassInternal(String). For instances of parallel capable class
loaders, neither the VM nor loadClassInternal(String) will acquire
the class loader lock. Expected to ship in product for one or two
releases until deprecated loadClassInternal(String) can be safely
removed.
Java <-> VM
interface changes
VM calls loadClass(String) NOT
loadClassInternal(String) always (note: backward compatibility
-XX:+MustCallLoadClassInternal flag override)
VM acquires class loader lock only
if class loader instance is not a ParallelCapable class
java.lang.ClassLoader's instance constructor will look up the
class for a new class loader, and if it is registered as parallel
capable, set a private instance field, parallelLockMap to non-null
for the vm to query.
Class Loader changes required
java.lang.ClassLoader:
Invoke
registerAsParallelCapable() during class initialization
Modify the code to ensure
thread-safe concurrency
protected loadClass(String,
boolean) changes:
Remove synchronized keyword
If "this" is not a
parallel capable class loader, synchronize on "this" for
backward compatibility
else synchronize on a
class-name-based-lock
The synchronization in protected
loadClass(String, boolean) ensures that defineClass(...) will not
be called multiple times in parallel for the same class name/class
loader pair.
AssertionStatus related APIs
add new protected static method:
registerAsParallelCapable()
Suggested Model
for Custom Class Loaders
Custom class loaders that have no
history of deadlocks require no changes.
Custom class loaders that support
a non-hierarchical delegation model and so are candidates for
deadlock may choose to adopt the new mechanism. If you adopt this
new mechanism, you need to modify all custom class loaders that
could interact in a deadlock.
Current class loaders frequently rely on the synchronization
on the class loader lock provided by the enclosing methods
loadClassInternal and protected loadClass(String, boolean). To
resolve the current deadlocking on class loader locks, finer grained
locking is needed. For class loader classes that successfully
register themselves as parallel capable, the java.lang.ClassLoader
class will no longer synchronize on the current class loader object
when called to load a class. Instead java.lang.ClassLoader uses its
own private lock which is unique for each class name. This allows
concurrent loading of different classes for the same class loader
instance. This locking logic is in the protected loadClass(String,
boolean) method and will be passed to custom parallel class loaders
through inheritance if not overridden.
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. These include:
java.lang.ClassLoader
java.security.SecureClassLoader
java.net.URLClassLoader
javax.management.loading.MLet
javax.management.loading.PrivateMlet
All of the JRE class loaders which custom class loaders delegate
to in the traditional tree-based hierarchy in theory would not
require changes due to delegation. Given that they are supposed to
only delegate further up the hierarchy, they should not participate
in a deadlock. If at a future time any of these class loaders were to
adopt an alternative delegation policy, that for instance would allow
more direct delegation to a specific class loader, then it would be
necessary to make these class loaders multi-thread safe.
Other class loaders, that are not extended and not delegated to by
custom class loaders do not require any changes. These include:
java.rmi.server.RMIClassLoader
com.sun.jnlp.JNLPClassLoader
All sun.* packages are private and
customers can not extend
sun.plugin._javascript_.JSClassLoader
sun.plugin.security.PluginClassLoader
sun.plugin2.applet.JNLP2ClassLoader
sun.plugin2.applet.Applet2ClassLoader
sun.plugin2.applet.Plugin2ClassLoader
VM Behavior Changes
Part of the 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:
Fixed problems with circularity
detection (JDK5)
Modified the bootstrap class
loader to be a parallel class loader (JDK6)
Modified the VM common class
resolution logic to handle parallel class loading (HotSpot 10)
Details of VM handling of different cl Details of VM handling of
specific class loader cases:
Traditional class loaders
JDK 6: lock class loader object
lock, call loadClassInternal(String)
New: when using traditional class
loaders to load classes, lock class loader object lock, call
loadClass(String)
-XX:+UnlockDiagnosticOptions
-XX:+UnsyncloadClass - DEPRECATED
JDK 6: no class loader object
lock for class loaders, call loadClass(String)
allow parallel overall class
resolution, parallel superclass loading, and parallel
defineClass() calls
parallel defineClass() handling:
if there are two define class requests at the same time for the
same class/class loader pair, the second requestor waits for the
first requestor and returns the first requestor's results, instead
of throwing a linkageError.
New: support for flags is the
same as in JDK6
parallelCapable
JDK 6: same behavior as
traditional class loaders
New: no class loader object lock
for class loaders, call loadClass(String)
allow parallel overall class
resolution, and parallel superclass loading
parallel defineClass requests
will throw LinkageErrors (for early access experimentation: see
AllowParallelDefineClass flag)
bootstrap class loader
breaking the class loader lock
Alternatives Considered and Not Chosen
Open Issues
|