JDK-8284777 : Tests fail with java.lang.ClassCircularityError: java/util/concurrent/locks/AbstractQueuedSynchronizer$ExclusiveNode
  • Type: Bug
  • Component: core-svc
  • Sub-Component: java.lang.instrument
  • Affected Version: 19,repo-loom
  • Priority: P4
  • Status: Resolved
  • Resolution: Cannot Reproduce
  • Submitted: 2022-04-12
  • Updated: 2022-09-08
  • Resolved: 2022-09-08
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.
JDK 20
20Resolved
Related Reports
Relates :  
Relates :  
Description
In the loom repo, the following tests have failed with "java.lang.ClassCircularityError: java/util/concurrent/locks/AbstractQueuedSynchronizer$ExclusiveNode".

java/lang/instrument/NativeMethodPrefixAgent.java
runtime/cds/appcds/jigsaw/RedefineClassesInModuleGraph.java

Both have the following stack trace in common:

java.lang.ClassCircularityError: java/util/concurrent/locks/AbstractQueuedSynchronizer$ExclusiveNode
	at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:695)
	at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:938)
	at java.base/java.util.concurrent.locks.ReentrantLock$Sync.lock(ReentrantLock.java:153)
	at java.base/java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:322)
	at java.base/jdk.internal.misc.InternalLock.lock(InternalLock.java:64)
	at java.base/java.io.PrintStream.writeln(PrintStream.java:823)
	at java.base/java.io.PrintStream.println(PrintStream.java:1167)
	at <test app main method>

The root cause is covered by JDK-8164165, which is basically about the issue with the transform() method triggering loading of the class currently being transformed.

AbstractQueuedSynchronizer.acquire(), which is the topmost frame above, has triggered the loading of AbstractQueuedSynchronizer$ExclusiveNode. The test's transform() method is called for AbstractQueuedSynchronizer$ExclusiveNode. The tranform() method does a println(), which result in the same execution path you see above, once again triggering the loading of AbstractQueuedSynchronizer$ExclusiveNode. This results in the CCE.

These failures are new in Loom due to the changes made in the java.io classes to use j.u.concurrent locks instead of synchronized to avoid pinning carrier threads. So now calls to println() end up in j.u.concurrent.
Comments
I'm unable to reproduce this in the current jdk repo or the loom repo. Also tried with JTREG_VTHREAD_WRAPPER=Virtual. Something must have changed in the libs so now the AbstractQueuedSynchronizer.acquire() path is no longer triggered, or it no longer triggers any class loading while the test is running.
08-09-2022

The Java code you use to implement synchronization can not lead to code that recursively requires synchronization. By changing println to use ReentrantLock you have introduced a constraint that no code executed while ReentrantLock, and all classes used thereby, are being loaded and initialized (or used!), can in turn call println. Aggressive pre-loading/linking/initialization can workaround this in many cases but there are still potential issues e.g. if the clinit code itself can lead to the recursion (either directly or via some other transformed code). Also you cannot do something "trivial" like using println to add logging/tracing to the lock code whilst debugging.
13-04-2022

The following stack trace gives you an idea of what's going on when the CCE is thrown. I got this be forcing an assert when the CCE is detected: V [libjvm.dylib+0x11e7774] VMError::report_and_die(int, char const*, char const*, char*, Thread*, unsigned char*, void*, void*, char const*, int, unsigned long)+0x5d8 V [libjvm.dylib+0x11e7eac] VMError::report_and_die(Thread*, void*, char const*, int, char const*, char const*, char*)+0x40 V [libjvm.dylib+0x61ec14] report_vm_error(char const*, int, char const*, char const*, ...)+0x80 V [libjvm.dylib+0x10f7c18] SystemDictionary::resolve_instance_class_or_null(Symbol*, Handle, Handle, JavaThread*)+0x780 V [libjvm.dylib+0x10f6d7c] SystemDictionary::resolve_or_fail(Symbol*, Handle, Handle, bool, JavaThread*)+0x80 V [libjvm.dylib+0x5b3564] ConstantPool::klass_at_impl(constantPoolHandle const&, int, JavaThread*)+0x67c V [libjvm.dylib+0x93de48] InterpreterRuntime::_new(JavaThread*, ConstantPool*, int)+0x1cc j java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;IZZZJ)I+218 java.base@19-internal j java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(I)V+15 java.base@19-internal j java.util.concurrent.locks.ReentrantLock$Sync.lock()V+9 java.base@19-internal j java.util.concurrent.locks.ReentrantLock.lock()V+4 java.base@19-internal j jdk.internal.misc.InternalLock.lock()V+4 java.base@19-internal j java.io.PrintStream.writeln(Ljava/lang/String;)V+11 java.base@19-internal j java.io.PrintStream.println(Ljava/lang/String;)V+14 java.base@19-internal j RedefineClassesInModuleGraphTransformer.transform(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/Class;Ljava/security/ProtectionDomain;[B)[B+9 j java.lang.instrument.ClassFileTransformer.transform(Ljava/lang/Module;Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/Class;Ljava/security/ProtectionDomain;[B)[B+9 java.instrument@19-internal j sun.instrument.TransformerManager.transform(Ljava/lang/Module;Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/Class;Ljava/security/ProtectionDomain;[B)[B+52 java.instrument@19-internal j sun.instrument.InstrumentationImpl.transform(Ljava/lang/Module;Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/Class;Ljava/security/ProtectionDomain;[BZ)[B+69 java.instrument@19-internal v ~StubRoutines::call_stub 0x00000001173641bc V [libjvm.dylib+0x9524fc] JavaCalls::call_helper(JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*)+0x4cc V [libjvm.dylib+0xa1e610] jni_invoke_nonstatic(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, JavaThread*)+0x5d4 V [libjvm.dylib+0xa20a7c] jni_CallObjectMethod+0x148 C [libinstrument.dylib+0x753c] transformClassFile+0x2f0 C [libinstrument.dylib+0x6038] eventHandlerClassFileLoadHook+0x88 V [libjvm.dylib+0xc4eec0] JvmtiClassFileLoadHookPoster::post_to_env(JvmtiEnv*, bool)+0xa0 V [libjvm.dylib+0xc4edb4] JvmtiClassFileLoadHookPoster::post_all_envs()+0x2c0 V [libjvm.dylib+0xc3eab8] JvmtiExport::post_class_file_load_hook(Symbol*, Handle, Handle, unsigned char**, unsigned char**, JvmtiCachedClassFileData**)+0x58 V [libjvm.dylib+0xc7f574] KlassFactory::create_from_stream(ClassFileStream*, Symbol*, ClassLoaderData*, ClassLoadInfo const&, JavaThread*)+0x304 V [libjvm.dylib+0x52cea0] ClassLoader::load_class(Symbol*, bool, JavaThread*)+0x278 V [libjvm.dylib+0x10fad7c] SystemDictionary::load_instance_class_impl(Symbol*, Handle, JavaThread*)+0x444 V [libjvm.dylib+0x10f8924] SystemDictionary::load_instance_class(unsigned int, Symbol*, Handle, JavaThread*)+0x38 V [libjvm.dylib+0x10f7c3c] SystemDictionary::resolve_instance_class_or_null(Symbol*, Handle, Handle, JavaThread*)+0x7a4 V [libjvm.dylib+0x10f6d7c] SystemDictionary::resolve_or_fail(Symbol*, Handle, Handle, bool, JavaThread*)+0x80 V [libjvm.dylib+0x5b3564] ConstantPool::klass_at_impl(constantPoolHandle const&, int, JavaThread*)+0x67c V [libjvm.dylib+0x93de48] InterpreterRuntime::_new(JavaThread*, ConstantPool*, int)+0x1cc j java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;IZZZJ)I+218 java.base@19-internal j java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(I)V+15 java.base@19-internal j java.util.concurrent.locks.ReentrantLock$Sync.lock()V+9 java.base@19-internal j java.util.concurrent.locks.ReentrantLock.lock()V+4 java.base@19-internal j jdk.internal.misc.InternalLock.lock()V+4 java.base@19-internal j java.io.PrintStream.writeln(Ljava/lang/String;)V+11 java.base@19-internal j java.io.PrintStream.println(Ljava/lang/Object;)V+19 java.base@19-internal j RedefineClassesInModuleGraphApp.main([Ljava/lang/String;)V+15 v ~StubRoutines::call_stub 0x00000001173641bc V [libjvm.dylib+0x9524fc] JavaCalls::call_helper(JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*)+0x4cc V [libjvm.dylib+0xa29fcc] jni_invoke_static(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, JavaThread*)+0x1c0 V [libjvm.dylib+0xa2ea88] jni_CallStaticVoidMethod+0x138 C [libjli.dylib+0x5000] JavaMain+0x9dc C [libjli.dylib+0x72f4] ThreadJavaMain+0xc C [libsystem_pthread.dylib+0x74ec] _pthread_start+0x94 When the CCE is thrown, the ConstantPool::klass_at_impl() does the following, making it so the cp entry cannot be resolved: // Failed to resolve class. We must record the errors so that subsequent attempts // to resolve this constant pool entry fail with the same error (JVMS 5.4.3). if (HAS_PENDING_EXCEPTION) { save_and_throw_exception(this_cp, which, constantTag(JVM_CONSTANT_UnresolvedClass), CHECK_NULL); I changed RedefineClassesInModuleGraphTransformer.transform() to swallow the CCE hoping to recover from it, but this wasn't enough. Because of the above save_and_throw_exception(), CCE got triggered again at by the following code, also in ConstantPool::klass_at_impl(), but in this case for the frame nearer the bottom of the stack (the one that triggered the initial class loading of the class): // We need to recheck exceptions from racing thread and return the same. if (old_tag == JVM_CONSTANT_UnresolvedClassInError) { // Remove klass. this_cp->resolved_klasses()->at_put(resolved_klass_index, NULL); throw_resolution_error(this_cp, which, CHECK_NULL); } And future attempts to resolve this cp entry will do the same.
12-04-2022