Demystifying class loading problems, Part 4: Deadlocks and constraints

An in-depth look at two of the most complex class loading problems

This four-part article series examines Java™ class loading, in an effort to help application developers understand and debug problems they may encounter. In this final installment, authors Lakshmi Shankar and Simon Burns from the IBM Hursley Labs build on the first three articles and look at two of the most interesting and complex problems you will encounter in this arena: deadlocks and constraints.

Share:

Simon Burns (simon_burns@uk.ibm.com), Java Technology Center Development Team, IBM Hursley Labs

Simon BurnsSimon Burns was the component owner and team lead for the Persistant Reusable JVM in the Java Technology Centre in IBM Hursley Labs. He worked in JVM development for over three years, specializing in the Persistant Reusable JVM technology and the z/OS platform. He has also worked closely with CICS, helping them to exploit this technology. Simon worked on the OSGi framework as part of the open-source Eclipse Equinox project, which has now been integrated into Eclipse 3.1. He is now working on componentization.



Lakshmi Shankar, Java Technology Center Development Team, IBM Hursley Labs

Lakshmi ShankarLakshmi Shankar is a Software Engineer in IBM Hursley Labs, UK. He has worked for IBM for more than three years and has a broad range of experience, having worked in Java performance, test, and development within Hursley Labs. Until recently he was the Class Loading component owner for IBM's Java technology. He is now a developer working as part of the Information Management team.



20 December 2005

Also available in

This article, the final installment in this four-part series, examines class loader deadlocks and constraint violations. These two types of problems can be difficult simply to understand, much less resolve. Just as we did in the previous articles in this series, we provide examples to illustrate the problems and then follow on with discussion of various resolution techniques.

Before you begin this article, you should be familiar with the class loader delegation model, along with the phases and stages of class linking. We recommend that you start by reading the first article in this series.

Class loader deadlocks

A class loader deadlock occurs when two threads each own a lock on two different class loaders, and both threads are waiting for the lock owned by the other thread. Both threads will wait indefinitely for the lock on the other class loader, and so they become deadlocked. These deadlocks can occur in multi-threaded environments when the usual delegation model is bypassed. Consider the situation outlined in Figure 1:

Figure 1. Class loader deadlock example
Class loader deadlock example

Here we have two user-defined class loaders, mcl1 and mcl2. mcl1 is a child of the system class loader, and mcl2 is a child of mcl1. Classes A and B are on the classpath of mcl1, and class C is on the classpath of mcl2. Class A extends C, and class C extends B.

Normally in this situation, a NoClassDefFoundError would be thrown by mcl1 when trying to load C, the superclass of A, because mcl1 cannot see down and C can only be loaded by a class loader below mcl1. However, in this particular situation, mcl1 delegates down to its child class loader for classes in a certain package, package2, and class C is in that package.

The test case in Listings 1 through 6 implements this scenario:

Listing 1. ClassLoaderDeadlockTest.java
import java.net.URL;

public class ClassLoaderDeadlockTest {

    MyClassLoader1 mycl1;

    MyClassLoader2 mycl2;

    public static void main(String[] args) {
        new ClassLoaderDeadlockTest().test();

    }

    public void test() {
        try {
            mycl1 = new MyClassLoader1(new URL[] { new URL(
                "file://C:/CL_Article/ClassloaderDeadlocks/cp1/") });

            mycl2 = new MyClassLoader2(new URL[] { new URL(
                "file://C:/CL_Article/ClassloaderDeadlocks/cp2/") }, mycl1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                try {
                    System.out.println("About to load class A with mycl1");
                    mycl1.loadClass("package1.A");
                    System.out.println("Loaded Class A with mycl1");
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        });

        t1.start();

        try {
            System.out.println("About to load class C with mycl2");
            mycl2.loadClass("package2.C");
            System.out.println("Loaded Class C with mycl2");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
Listing 2. MyClassLoader1.java
import java.net.URL;
import java.net.URLClassLoader;

public class MyClassLoader1 extends URLClassLoader {

    MyClassLoader1(URL[] urls) {
        super(urls);
    }

    public Class loadClass(String name) throws ClassNotFoundException{
        if (name.startsWith("package2."))
            return MyClassLoader2.getClassLoader().loadClass(name);
        else
            return findClass(name);
    }
}
Listing 3. MyClassLoader2.java
import java.net.URL;
import java.net.URLClassLoader;

public class MyClassLoader2 extends URLClassLoader{

    static ClassLoader loader;

    MyClassLoader2(URL[] urls, ClassLoader parent) {
        super(urls, parent);
        loader = this;
    }

    public static ClassLoader getClassLoader() {
        return loader;
    }
}
Listing 4. package1/A.java
package package1;

public class A extends package2.C {

}
Listing 5. package1/B.java
package package1;

public class B {

}
Listing 6. package2/C.java
package package2;

public class C extends package1.B {

}

When the above test case is run, the following output is produced, and then the application hangs:

About to load class C with mycl2
About to load class A with mycl1

The application hangs because each thread owns a lock on one of the class loaders and wants the lock on the other class loader, as illustrated in the timeline diagram in Figure 2:

Figure 2. Class loader deadlock timeline
Class Loader Deadlock Timeline

Thread 2 (t2) first calls the synchronized loadClass() method on mcl2 to load package2.C. This causes t2 to take a lock on mcl2. Then Thread 1 (t1) starts and calls loadClass() on mcl1 to load package1.A. This causes t1 to take a lock on mcl1. Because package1.A extends package2.C, mcl1 initiates the loading of the superclass. Because C is in package2, mcl1 delegates down to mcl2, as described earlier. This causes t1 to request a lock on mcl2 and wait until that lock can be obtained. Now t2 tries to load the superclass of package2.C (which is package1.B) using mcl1, and in turn tries to take the lock on mcl1.

Because each thread is waiting for a lock held by the other, a deadlock occurs.

Deadlocks of this nature can be resolved using some of the debugging features described in the first article of this series. Setting the IBM Verbose Class Loading option (-Dibm.cl.verbose) when you're running this program will help you understand the class loading sequence that leads to this deadlock. Here's the output.

IBM Verbose Class Loading option output
About to load class C with mycl2
ExtClassLoader attempting to find package2.C
...
About to load class A with mycl1
MyClassLoader1 attempting to find package1.A
MyClassLoader1 using classpath \C:\CL_Article\ClassloaderDeadlocks\cp1
...
ExtClassLoader could not find package2.C
...
AppClassLoader attempting to find package2.C
...
AppClassLoader could not find package2.C
...
MyClassLoader1 attempting to find package2.C
MyClassLoader1 using classpath \C:\CL_Article\ClassloaderDeadlocks\cp1
MyClassLoader1 found package1/A.class in \C:\CL_Article\ClassloaderDeadlocks\cp1
MyClassLoader1 could not find package2/C.class in \C:\CL_Article\ClassloaderDeadlocks\cp1
MyClassLoader1 could not find package2.C
...
MyClassLoader2 attempting to find package2.C
MyClassLoader2 using classpath \C:\CL_Article\ClassloaderDeadlocks\cp2
MyClassLoader2 found package2/C.class in \C:\CL_Article\ClassloaderDeadlocks\cp2

To make this listing easier to read, the output from t2 is shown in bold text, and the output from t1 is normal text. As you can see, t2 has reached the point where it has loaded class C, and t1 has loaded class A.

The most valuable information for this problem can be found in a Javadump, obtained using the mechanisms described in Part 1 of this series. The JVM can usually detect that a deadlock has occurred and reports this in the Javadump, as shown below. (Here, t2 is identified as main, t1 as Thread-0):

...
Deadlock detected!!!
---------------------

 Thread "Thread-0" (0x44DFE1E8)
   is waiting for:
     sys_mon_t:0x002A26D0 infl_mon_t: 0x00000000:
     MyClassLoader2@ADB658/ADB660:
   which is owned by:
 Thread "main" (0x2A1750)
   which is waiting for
     sys_mon_t:0x002A2718 infl_mon_t: 0x00000000:
     MyClassLoader1@ADB6D8/ADB6E0:
   which is owned by:
 Thread "Thread-0" (0x44DFE1E8)
...

This section shows us the threads that are involved in the deadlock and the locks that they're holding and waiting for. Just below this section, the Javadump shows the stack traces of these threads at the time of the deadlock. As expected, both class loaders are trying to load a class:

"Thread-0" (TID:0xADB600, sys_thread_t:0x44DC72D0, state:CW, native ID:0x9DC) prio=5
    at java.lang.ClassLoader.loadClass(ClassLoader.java:577)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
    at MyClassLoader1.loadClass(MyClassLoader1.java:12)
    at java.lang.ClassLoader.defineClass0(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:810)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:147)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:475)
    at java.net.URLClassLoader.access$500(URLClassLoader.java:109)
    at java.net.URLClassLoader$ClassFinder.run(URLClassLoader.java:848)
    at java.security.AccessController.doPrivileged1(Native Method)
    at java.security.AccessController.doPrivileged(AccessController.java:389)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:371)
    at MyClassLoader1.loadClass(MyClassLoader1.java:13)
    at ClassLoaderDeadlockTest$1.run(ClassLoaderDeadlockTest.java:29)
    at java.lang.Thread.run(Thread.java:568)

"main" (TID:0xADB9B8, sys_thread_t:0x2A2028, state:CW, native ID:0x18C) prio=5
    at java.lang.ClassLoader.loadClass(ClassLoader.java:577)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:563)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
    at java.lang.ClassLoader.defineClass0(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:810)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:147)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:475)
    at java.net.URLClassLoader.access$500(URLClassLoader.java:109)
    at java.net.URLClassLoader$ClassFinder.run(URLClassLoader.java:848)
    at java.security.AccessController.doPrivileged1(Native Method)
    at java.security.AccessController.doPrivileged(AccessController.java:389)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:371)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:572)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
    at ClassLoaderDeadlockTest.test(ClassLoaderDeadlockTest.java:42)
    at ClassLoaderDeadlockTest.main(ClassLoaderDeadlockTest.java:10)

The Javadump also shows the classes loaded by these class loaders:

ClassLoader loaded classes
        Loader MyClassLoader2(0x452DA0F0)
                package2/C(0x00ACEAF0)
        Loader MyClassLoader1(0x452DA7F8)
                package1/A(0x00ACEC20)
        Loader sun/misc/Launcher$AppClassLoader(0x44D7C7B8)
                ClassLoaderDeadlockTest$1(0x00ACED50)
                MyClassLoader1(0x00ACEFB0)
                ClassLoaderDeadlockTest(0x00ACF0E0)
                MyClassLoader2(0x00ACEE80)
        Loader sun/misc/Launcher$ExtClassLoader(0x44D73D78)
        Loader *System*(0x00352A08)
                sun/reflect/UnsafeFieldAccessorFactory(0x44D40998)
                java/lang/Class$1(0x002CF128)
                java/io/InputStream(0x002C9818)
                java/lang/Integer$1(0x002C83E8)
                ...

With all of this information, it should be possible to resolve the deadlock. Because the identities of the deadlocked class loaders are known, it is possible to examine the delegation model that these class loaders are using. In this case, the delegation model is a proper graph (with cycles), and so a deadlock can occur as a result of particular class relationships and thread usage. Here, the class relationship of Class A extending Class C triggers the cyclic delegation model.


Class loader constraint violations

Class loader constraints ensure the consistency of the class space in the JVM. In other words, when two class loaders load different classes (that is, different bytecodes) with the same name, class loader constraints guarantee that there will be no type mismatch between them.

According to the JVM specification, a class loader constraint is violated when the following four conditions hold:
  • There exists a loader L such that L has been recorded by the Java Virtual Machine as an initiating loader of a class C named N
  • There exists a loader L' such that L' has been recorded by the Java Virtual Machine as an initiating loader of a class C' named N
  • The equivalence relation defined by the (transitive closure of the) set of imposed constraints implies N L = N L'
  • C != C'

The easiest way to explain these conditions is with an example. Consider the scenario in Figure 3:

Figure 3. Class loader constraint
Class Loader Constraint

Class A has a static method, methodA(), that takes an instance of class C as a parameter. Class B has a static method, methodB(), that calls methodA() in class A, passing in an instance of C. The main program invokes methodB() in class B.

To relate this back to the four conditions defined in the JVM specification:

  • L = mycl1. C = the class C loaded by mycl1. N = C.
  • L' = mycl2. C' = the class C loaded by mycl2. N = C.
  • The equivalence relation is established by the constraint implied by passing an instance of C in the method call from B to A.
  • Class C != class C'

Because all four conditions hold, this situation results in a class loader constraint violation.

The test case in Listings 7 through 12 implements this scenario:

Listing 7. ConstraintViolationTest.java
import java.lang.reflect.Method;
import java.net.URL;

public class ConstraintViolationTest {

    MyClassLoader1 mycl1;

    MyClassLoader2 mycl2;

    public static void main(String[] args) {
        new ConstraintViolationTest().test();

    }

    public void test() {
        try {
            mycl1 = new MyClassLoader1(new URL[] { new URL(
                "file://C:/CL_Article/ConstraintViolation/cp1/") });

            mycl2 = new MyClassLoader2(new URL[] { new URL(
                "file://C:/CL_Article/ConstraintViolation/cp2/") }, mycl1);

            System.out.println("About to load class A with mycl1");
            mycl2.loadClass("A");
            System.out.println("Loaded Class A with mycl1");

            System.out.println("About to load class B with mycl2");
            Class myB = mycl2.loadClass("B");

            Method aMethod = myB.getMethod("methodB", new Class[] {});
            aMethod.invoke(null, new Object[] {});
            System.out.println("Loaded Class B with mycl2");

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
Listing 8. MyClassLoader1.java
import java.net.URL;
import java.net.URLClassLoader;

public class MyClassLoader1 extends URLClassLoader {

    MyClassLoader1(URL[] urls) {
        super(urls);
    }
}
Listing 9. MyClassLoader2.java
import java.net.URL;
import java.net.URLClassLoader;

public class MyClassLoader2 extends URLClassLoader {

    static ClassLoader loader;

    MyClassLoader2(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    public Class loadClass(String name) throws ClassNotFoundException {
        Class aClass = findLoadedClass(name);
        if (aClass != null)
            return aClass;
        if (name.startsWith("C"))
            return findClass(name);
        else
            return super.loadClass(name);

    }
}
Listing 10. A.java
public class A extends C {

    public static void methodA(C c){

    }
}
Listing 11. B.java
public class B extends C {
    static A a = new A();

    public static void methodB() {
        A.methodA(new C());
    }
}
Listing 12. C.java
public class C {

}

A copy of class C must be placed on the classpaths of both mcl1 and mcl2. This test case produces the following output.

Test case output

Click to see code listing

Test case output

About to load class A with mycl1
Loaded Class A with mycl1
About to load class B with mycl2
java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:85)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:58)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:60)
        at java.lang.reflect.Method.invoke(Method.java:391)
        at ConstraintViolationTest.test(ConstraintViolationTest.java:31)
        at ConstraintViolationTest.main(ConstraintViolationTest.java:11)
Caused by: java.lang.LinkageError: Class C violates loader constraints:  definition mismatch between parent and child loaders
        at B.methodB(B.java:7)
        ... 7 more

Resolving class loader constraint violations

Many developers find constraint violations to be a difficult type of class loading problem to resolve. This is mainly because the exception messages can seem cryptic to developers who are encountering this problem for the first time.

A good starting point to resolving this problem is to examine the classes involved. These can be ascertained either from the IBM verbose output or from a Javadump.

From the output above, you can see that the class that is violating the loader constraints is C. If you want to use IBM verbose output to examine the classes involved, you should use the command line option -Dibm.cl.verbose=C. The output will show the two different class loaders loading C.

A clearer way to see the problem is to generate a Javadump. The class loading section of the Javadump for this scenario should look like something like this:

ClassLoader loaded classes
        Loader MyClassLoader2(0x44DC93A8)
                C(0x00ACE9C0)
                B(0x00ACEAF0)                                     
        Loader MyClassLoader1(0x44DC64B8) 
                C(0x00ACEC20)
                A(0x00ACED50)                                     
        Loader sun/misc/Launcher$AppClassLoader(0x44D7C7B8) 
                MyClassLoader1(0x00ACEFB0)
                ConstraintViolationTest(0x00ACF0E0)
                MyClassLoader2(0x00ACEE80) 
        Loader sun/misc/Launcher$ExtClassLoader(0x44D73D78)  
        Loader *System*(0x00352A08)                               
                sun/net/TransferProtocolClient(0x44D4AB18)
                sun/reflect/UnsafeFieldAccessorFactory(0x44D40998)
                java/lang/Class$1(0x002CF128)                     
                java/io/InputStream(0x002C9818)                   
                ...

As you can see, class C has been loaded by an instance of MyClassLoader1 (mcl1) and also by an instance of MyClassLoader2 (mcl2). Importantly, the addresses (shown in brackets) of the two classes are different. This means that the bytecodes are from different files.

The easiest way to resolve this problem is to ensure that there is only one copy of a class in the system -- that is, that the class only appears in the classpath of one loader. However, if it is necessary to have two copies of the same class, then it is important to ensure that there is no interaction between any classes that refer to them.

Avoiding class loader constraint violations

While the simplest way to avoid class loader constraint violations is to only have one copy of a class in the system, it is sometimes necessary to have multiple versions.

One possible way to avoid constraint violations, while still deploying multiple versions of a class, is to use a peer class loading model, illustrated in Figure 4. Peer class loading does not follow the traditional hierarchical delegation structure for class loaders. Instead, it has a set of class loaders that are unrelated except that they have the same parent (usually the system class loader). These class loaders can delegate not only to their parent, but also to their peers.

Figure 4. Peer class loading
Peer class loading

This kind of class loader structure allows discrete class spaces to exist within one JVM; thus, it's very useful for running componentized products. A good example of this class loading structure is an OSGi framework, such as the one Eclipse is built on.


Conclusion

This series has provided a general overview of potential problems that you might encounter when using Java class loaders. We've shown you the different kinds of exceptions that can occur and how to go about resolving them. We've also examined some of the other issues that may arise in the implicit and explicit use of class loaders. In addition, we introduced the various debugging features of the IBM JVM and shown how to apply them to the various problems.

We hope that the insights provided by these articles will enable you to better understand class loading and to make better use of class loaders in your applications.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=100167
ArticleTitle=Demystifying class loading problems, Part 4: Deadlocks and constraints
publish-date=12202005