JDK-4699981 : ClassCircularityError thrown without reason during class loading
  • Type: Bug
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 1.4.0,1.4.2
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: windows,windows_2000
  • CPU: x86,sparc
  • Submitted: 2002-06-10
  • Updated: 2022-10-13
  • Resolved: 2005-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.
Other JDK 6
5.0u8Fixed 6 b51Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Description
Name: nt126004			Date: 06/10/2002


FULL PRODUCT VERSION :
java version "1.3.1"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.1-b24)
Java HotSpot(TM) Client VM (build 1.3.1-b24, mixed mode)

and 

java version "1.4.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-b92)
Java HotSpot(TM) Client VM (build 1.4.0-b92, mixed mode)

FULL OPERATING SYSTEM VERSION :
Microsoft Windows 2000 [Version 5.00.2195]

A DESCRIPTION OF THE PROBLEM :
This problem happens both in JDK 1.3.1 and in 1.4.0,
Windows 2000.

When 2 threads are loading the same class with the same
classloader, and somehow one of the 2 threads releases the
synchronization lock on the classloader, the JVM code
throws ClassCircularityError, mistakenly.

Refer to the JVM class (in systemDictionary.cpp) and method
SystemDictionary::resolve_instance_class_or_null(...) for
where the bug is originating.

Refer also to RFE #4670071. As explained there, in JBoss
3.0 (http://www.jboss.org) a new classloading model is
used, not based on the tree model, that gives important
features such as hot-deploy and modularity. Because of the
synchronization on the classloader, JBoss' classloading
model (or any other classloading mechanism not based on the
tree model) may suffers of deadlocks. In JBoss 3.0 the
deadlock issue has been resolved, releasing the classloader
lock, but this solution showed up the ClassCircularityError
bug, this time in JVM code, and hence with no solution.

Probably the JVM code should throw ClassCircularityError if
a placeholder is found by the same thread that put it, not
if another thread is coming in asking for the same class
with the same classloader.

Note that the problem originates by calls that trigger a
call to the native code in
SystemDictionary::resolve_instance_class_or_null(...); from
investigation I was able to find these methods:

1. Class.defineClass
2. Class.loadClassInternal
3. Class.newInstance (and Constructor.newInstance)
4. Class.forName

There may be other, the code submitted only shows the
problem with 3., but in JBoss the problem is seen it with
all of the 4 cases listed above.

In the code submitted, URLClassLoader has been subclassed
only to make a reproducible test case, but in real code
there is no need to subclass it to get the problem (only
will happen only in certain situation/thread timing).

It would be nice that this bug and RFE #4670071 will go on
in parallell, to give more freedom to the classloading
mechanism.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Make 4 files of the submitted code, namely Base.java,
Derived.java, Support.java and Main.java.
2. Compile the files, for example in directory classes/
3. java -cp classes Main

EXPECTED VERSUS ACTUAL BEHAVIOR :
Expected result: no errors, the 2 threads are able to load
the classes
Actual Result: ClassCircularityError is thrown.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
java.lang.ClassCircularityError: Base
        at Support.<init>(Support.java:7)
        at java.lang.Class.newInstance0(Native Method)
        at java.lang.Class.newInstance(Class.java:237)
        at Main$Run1.run(Main.java:97)
        at java.lang.Thread.run(Thread.java:484)

This bug can be reproduced always.

---------- BEGIN SOURCE ----------
public class Base {}

public class Derived extends Base {}

public class Support
{
   private Base base = new Base();
}


import java.net.URL;
import java.net.URLClassLoader;

/**
 *
 * @version $Revision$
 */
public class Main
{
   public static void main(String[] args) throws Exception
   {
      new Main();
   }

   private Object lock = new Object();

   public Main() throws Exception
   {
      URL location = getClass().getProtectionDomain().getCodeSource
().getLocation();
      URLLoader loader = new URLLoader(new URL[] {location}, getClass
().getClassLoader().getParent());

      Class cls = loader.loadClass("Support");

      Thread t1 = new Thread(new Run1(cls));
      t1.start();

      Thread.sleep(1000);

      // Load Derived, will trigger a loadClassInternal for Base
      loader.loadClass("Derived");
   }

   public class URLLoader extends URLClassLoader
   {
      private boolean m_firstTime = true;

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

      public Class loadClass(String name) throws ClassNotFoundException
      {
         if (name.equals("Base"))
         {
            if (m_firstTime)
            {
               m_firstTime = false;

               // Notify the other thread
               synchronized (lock)
               {
                  lock.notifyAll();
               }

               // Wait on the classloader to have the JVM throw
ClassCircularityError
               try
               {
                  synchronized (this)
                  {
                     wait(5000);
                  }
               }
               catch (InterruptedException ignored)
               {
               }
            }
         }
         return super.loadClass(name);
      }
   }

   public class Run1 implements Runnable
   {
      private Class cls;

      public Run1(Class cls)
      {
         this.cls = cls;
      }

      public void run()
      {
         synchronized (lock)
         {
            try
            {
               lock.wait();
            }
            catch (InterruptedException ignored) {}
         }

         // Trigger loadClassInternal for Base
         try
         {
            cls.newInstance();
         }
         catch (Throwable x)
         {
            x.printStackTrace();
         }
      }
   }
}
---------- END SOURCE ----------

CUSTOMER WORKAROUND :
None, unfortunately.
(Review ID: 148304) 
======================================================================

Comments
EVALUATION Testcase can be found at: /net/curious-george.east/disk2/Tests/4699981 ###@###.### 2002-06-11 ------------------------------------------------------------------------- ###@###.### 2002-06-17 Sequence of events: + the main thread M runs the method main. + thread M creates class loader L. + thread M uses L to load class Support (and returns). + thread M creates a new thread R passing the Class object for Support, and sleeps, giving thread R a chance to run. + thread R blocks waiting for the Lock object to be notified. + thread M uses L to load class "Derived", + which requires L to load superclass Base, >> A "placeholder" of <Base,L> is added to the system dictionary. + during which thread R is notified via the Lock object. and sleeps, giving thread R a chance to run. + thread R creates an instance of Support, + which requires loader L to load class Base. >> At this point, both thread M and thread R are executing in the loadClass method in L at the same time. >> thread R, attempting to load Base, finds the placeholder for Base, and declares the circularity error. >> thread M: loadClass("Derived") >> -> defineClass("derived") >> -> VM >> -> loadClass("Base") >> thread R: >> newInstance("Support") >> -> Support.<init> >> -> VM [newInstance "Base"] >> -> loadClass("Base") Problem: The purpose of the placeholder is to allow detection of the circularity error during the loading of a sequence of classes by a single thread. Thread M should have the classloader lock until after Base is defined. Thread R should have been held off until Thread M releases the lock. ------------------------------------------------------------------------- ###@###.### 2002-06-20 Since the Main thread gives up its classloader lock when it calls wait(), this is not a locking bug. In fact, this is a significant indication that the classloader should never have been locked in the first place. See bug 4670071 - ClassLoader.loadClassInternal(String) is too restrictive Fix needed - adjust the "placeholder" scheme so that circularity checks are handled by individual threads. ------------------------------ A flaw with the proposal of simply not throwing the Circularity error if the loading wasn't initiated by a particular thread is that true circularity errors could result in infinite looping in the JVM. Consider a ClassLoader which always spawns a new thread before calling defineClass. If we have Class A which extends Class B, which extends Class A, we'd see a sequence something like: Thread1 initiates loading of A, resolves its super classes, calls loadClassInternally for B. LoadClass spawns a new thread Thread2 defines B, resolves its supers, calls loadClassInternally for A, loadClass spawns a new thread Thread3 defines A, resolves its supers... This can be overcome by some internal structuring of how the JVM actually resolves classes and their super classes and super interfaces. As noted in comments above and the problem description, the root cause of this problem is the ClassLoader lock being released during class loading. ###@###.### 2002-10-08 We're working on a fix for this problem within the VM. A fix has been added to Jboss 3.2.3 . ###@###.### 2004-04-09
08-10-2002