Multithreaded Custom Class Loaders in Java SE 7

The Java SE 7 release contains an important enhancement for multithreaded custom class loaders. In previous releases, certain types of custom class loaders were prone to deadlock. The Java SE 7 release modifies the locking mechanism to avoid deadlock.

Background

The function of a java.lang.ClassLoader is to locate the bytecode for a particular class, then transform that bytecode into a usable class in the runtime system. The runtime system provides class loaders that can locate bootstrap classes, extension classes, and user classes. The CLASSPATH environment variable is one way to indicate to the runtime system where the bytecode is located.

Knowing about the CLASSPATH environment variable is all you may need to know about class loading. In some specific situations, however, customizing the behavior of a class loader by creating your own subclass may be necessary.

Custom class loaders will not run into deadlocks if they adhere to an acyclic class loader delegation model. Acyclic delegation is what the architects of ClassLoader envisioned. In this model, every class loader has a parent (delegate). When a class is requested, the class loader first checks if the class was loaded previously. If the class is not found, the class loader asks its parent to locate the class. If the parent cannot find the class, the class loader attempts to locate the class itself.

Deadlock Scenario

In earlier releases of the Java platform, multithreaded custom class loaders could deadlock when they did not have an acyclic delegation model. Here is one example:

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)

Synchronization in the ClassLoader class was previously heavy-handed, or in technical terms, not sufficiently granular. A request to load a class synchronized on the entire ClassLoader object, which made it prone to deadlock.

Class Loader Synchronization in the Java SE 7 Release

The Java SE 7 release includes the concept of a parallel capable class loader. Loading a class by a parallel capable class loader now synchronizes on the pair consisting of the class loader and the class name.

In the previous scenario, using the Java SE 7 release, the threads are no longer deadlocked, and all classes are loaded successfully:

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

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

Recommendations for Multithreaded Custom Class Loaders

Custom class loaders that do not have a history of deadlocks do not require any changes. In particular, you do not need to change custom class loaders that follow the recommended acyclic hierarchical delegation model, that is, delegating first to their parent class. For backward compatibility, the Java SE 7 release continues to lock a class loader object unless it registers as parallel capable.

To create new custom class loaders, the process is similar in the Java SE 7 release as in previous releases. Create a subclass of ClassLoader, then override the findClass() method and possibly loadClass(). Overriding loadClass() makes your life more difficult, but it is the only way to use a different delegation model.

If you have a custom class loader with a risk of deadlocking, with the Java SE 7 release, you can avoid deadlocks by following these rules:

  1. Ensure that your custom class loader is multithread safe for concurrent class loading.
    1. Decide upon an internal locking scheme. For example, java.lang.ClassLoader uses a locking scheme based on the requested class name.
    2. Remove all synchronization on the class loader object lock alone.
    3. Ensure that critical sections are safe for multiple threads loading different classes.
  2. In your custom class loader's static initializer, invoke java.lang.ClassLoader's static method registerAsParallelCapable(). This registration indicates that all instances of your custom class loader are multithread safe.
  3. Check that all class loader classes that this custom class loader extends also invoke the registerAsParallelCapable() method in their class initializers. Ensure that they are multithread safe for concurrent class loading.

If your custom class loader overrides only findClass(String), you do not need further changes. This is the recommended mechanism to create a custom class loader.

If your custom class loader overrides either the protected loadClass(String, boolean) method or the public loadClass(String) method, you must also ensure that the protected defineClass() method is called only once for each class loader and class name pair.

Troubleshooting

If your product ships and appears to have problems due to incomplete handling of critical sections, you can use a new VM flag -XX:+AlwaysLockClassLoader. This flag reverts to locking the class loader lock before invoking your custom class loader's findClass() or loadClass() method, even for class loaders that register as parallel capable.

References

Class Loader API Modifications for Deadlock Fix at OpenJDK

Bug Database Bug 4670071 Detail

Internals of Java Class Loading, Binildas Christudas, O'Reilly Media onJava.com

Demystifying class loading problems, Part 4: Deadlocks and constraints, Simon Burns and Lakshmi Shankar, IBM developerWorks


Copyright © 1993, 2020, Oracle and/or its affiliates. All rights reserved.