JDK-6508296 : (cl) java.lang.ClassCircularityError occurs when program overrides ClassLoader.loadClass
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang:class_loading
  • Affected Version: 6
  • Priority: P3
  • Status: Closed
  • Resolution: Won't Fix
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2006-12-28
  • Updated: 2013-10-10
  • Resolved: 2013-10-10
Related Reports
Relates :  
Relates :  
Description
java.lang.ClassCircularityError occurs when program overrides ClassLoader.loadClass and
call Class.getSimpleName().

CONFIGURATION:
 OS : windowsXP(SP2, Japanese)
 CPU : Pentium IV 1.4GHz
 Men : 384 MB
 JRE : 5.0u10 and 6.0

REPRODUCE :
 1. unfold the attached archive, test-program.zip
 2. Edit compile.bat and run.bat (set JAVA_HOME)
 3. Invoke compile.bat and then run.bat 

The following messages will appear.(6.0)
K:\shares2\hitachi\classcircularityerro-occurs\TP>java -version
java version "1.6.0"
Java(TM) SE Runtime Environment (build 1.6.0-b105)
Java HotSpot(TM) Client VM (build 1.6.0-b105, mixed mode)

K:\shares2\TP>java -classpath . server.DummyServer
Exception in thread "main" java.lang.ClassCircularityError: app/Class1$ChildClass
        at java.lang.Class.getDeclaringClass(Native Method)
        at java.lang.Class.getEnclosingClass(Class.java:1085)
        at java.lang.Class.getSimpleBinaryName(Class.java:1220)
        at java.lang.Class.getSimpleName(Class.java:1112)
        at server.ClassLoaderImpl.loadClass(ClassLoaderImpl.java:16)
        at server.DummyServer.<init>(DummyServer.java:15)
        at server.DummyServer.main(DummyServer.java:23)

Comments
EVALUATION First, some background: A "circularity error" is intended to be thrown by the virtual machine when "A class or interface could not be loaded because it would be its own superclass or superinterface" according the the Java Virtual Machine Specification (JVMS 2.17.2). The implementation of the virtual machine generally detects this by noting the beginning of an attempt to load a class and then noticing when the same task thread attempts to load that same class again before the original attempt has completed (is still in progress). This is because an attempt to define a class via a call to ClassLoader.defineClass() recursively causes the virtual machine to load all of that class's superclasses and superinterfaces before defineClass() returns a java.lang.Class representing the newly defined class. In the current bug report, a ClassCircularityError is being thrown even though the above description does not match what is actually causing the error to be thrown. The error is being thrown due to the call to Class.getSimpleName() in the overridden loadClass() method. The getSimpleName() method was introduced in J2SE 1.5. In it's implementation, getSimpleName() calls Class.getDeclaringClass() which is was introduced in JDK 1.1. The testcase can be simplified to call getDeclaringClass() instead with the same resulting behavior. In order to reproduce the issue on JDK 1.2.2 and perhaps other releases it may be necessary to change Class1.java in the testcase so that the inner class "ChildClass" is declared first inside Class1 due to minor differences in the behavior of the javac compiler between releases. The test can also be further simplified by having the constructor of DummyServer.java directly call loader.loadClass("app.Class1$ChildClass"). The underlying issue is that getDeclaringClass() inspects the "inner_classes" constant pool attribute in both the inner class and the enclosing outer class to verify that the nesting relationship is consistent. The description of this attribute can be found in the JVMS (in English) at: http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#79996 Unfortunately, the implementation of this cross-checking of the constant pool attributes is non-optimal and causes entries other than the named inner and enclosing classes in the attribute to be resolved in the constant pool. In the original testcase, this means that Class1.ChildClass is loaded due to its entry in the inner_classes attribute of the enclosing class (Class1) even though it's presence in the attribute is not relevant to the question of which class is the declaring class of Class1.Class2. The unnecessary loading of Class1.ChildClass then causes the loading of it's superclass Class1.ParentClass. The resulting call in the overloaded loadClass() method to getDeclaringClass() on Class1.ParentClass then likewise causes a recursive attempt to load Class.ChildClass from the inner_classes attribute of Class1.ParentClass's declaring class (Class1) even though it is not relevant to the question of whether the inner_class attributes of Class1 and Class1.ParentClass agree on their nesting relationship. Two things should be noted: #1 Because of the inherent way that ClassLoader.loadClass() in involved in the virtual machine's detection of circularity errors, it is inherently risky for implementations of loadClass() to call non-trivial methods on Class instances which report relationships between classes. #2 Class.getSimpleName() has the outward appearance of being a relatively trivial method although it's implementation actually involves determining relationships between classes. The documentation for the method should be enhanced to more fully describe the class relationship determinations which are part of its behavior. In addition, we should determine if it is reasonable to enhance the implementation of Class.getDeclaringClass() to avoid resolving irrelevant constant pool entries both for performance reasons and to avoid triggering unnecessary and incorrect ClassCircularityErrors.
22-02-2007