JDK-8257534 : misc tests failed with "NoClassDefFoundError: Could not initialize class java.util.concurrent.ThreadLocalRandom"
  • Type: Bug
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 16
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows
  • CPU: x86_64
  • Submitted: 2020-12-01
  • Updated: 2024-09-02
  • Resolved: 2021-10-21
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 11 JDK 17 JDK 18
11.0.25-oracleFixed 17.0.5-oracleFixed 18 b21Fixed
Related Reports
Duplicate :  
Duplicate :  
Duplicate :  
Relates :  
Relates :  
Relates :  
Description
The following test failed in the JDK16 CI:

vmTestbase/gc/lock/jvmti/alloc/jvmtialloclock03/TestDescription.java

Here's a snippet from the log file:

#section:main
----------messages:(4/378)----------
command: main -XX:-UseGCOverheadLimit -agentlib:JVMTIAllocLocker gc.lock.LockerTest -gp1 interned(randomString) -lockers jvmtiAlloc
reason: User specified action: run main/othervm/native -XX:-UseGCOverheadLimit -agentlib:JVMTIAllocLocker gc.lock.LockerTest -gp1 interned(randomString) -lockers jvmtiAlloc 
Mode: othervm [/othervm specified]
elapsed time (seconds): 2420.318
----------configuration:(0/0)----------
----------System.out:(70/4514)----------
Stress time: 30 seconds
Stress iterations factor: 1
Stress threads factor: 1
Stress runs factor: 1
Max memory: 2061500416
Sleep time: 500
Iterations: 0
Number of threads: 32
Run GC thread: false
Run mem diag thread: false
Run forever: false
For random generator using seed: -4034922344479984039
To re-run test with same seed value please add "-Djdk.test.lib.random.seed=-4034922344479984039" to command line.
Starting Thread[gc.lock.LockerTest$Worker@2f86d5ed,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@8332f7e,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@1103ba28,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@6a1f8540,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@6fbdabb6,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@7e6431e0,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@72ba06fe,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@6499a86c,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@66fb4f52,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@39cf995c,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@1b60f56d,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@3293f4a5,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@eaebc7a,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@4b6ec18a,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@57398e17,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@7a226e89,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@11440db,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@3ccbb3e2,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@2719db5a,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@4e26af16,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@6d972c91,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@c22c662,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@65080fb7,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@1e2d521f,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@4a8a16ef,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@5081c80a,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@7551101a,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@1180905,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@1bbf4aa7,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@45a01042,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@388c192c,5,MainThreadGroup]
Starting Thread[gc.lock.LockerTest$Worker@799d6f19,5,MainThreadGroup]
Exception in 
gc.lock.LockerTest$Worker@65080fb7
java.lang.RuntimeException: java.lang.NoClassDefFoundError: Could not initialize class java.util.concurrent.ThreadLocalRandom
	at nsk.share.gc.gp.GarbageUtils.eatMemory(GarbageUtils.java:224)
	at nsk.share.gc.gp.GarbageUtils.eatMemory(GarbageUtils.java:144)
	at gc.lock.LockerTest$Worker.run(LockerTest.java:54)
	at nsk.share.runner.ThreadsRunner$ManagedThread.run(ThreadsRunner.java:106)
Failures summary:
java.lang.RuntimeException: java.lang.NoClassDefFoundError: Could not initialize class java.util.concurrent.ThreadLocalRandom
	at nsk.share.gc.gp.GarbageUtils.eatMemory(GarbageUtils.java:224)
	at nsk.share.gc.gp.GarbageUtils.eatMemory(GarbageUtils.java:144)
	at gc.lock.LockerTest$Worker.run(LockerTest.java:54)
	at nsk.share.runner.ThreadsRunner$ManagedThread.run(ThreadsRunner.java:106)
	at java.base/java.lang.Thread.run(Thread.java:831)
Caused by: java.lang.NoClassDefFoundError: Could not initialize class java.util.concurrent.ThreadLocalRandom
	at java.base/java.util.concurrent.ConcurrentHashMap.fullAddCount(ConcurrentHashMap.java:2584)
	at java.base/java.util.concurrent.ConcurrentHashMap.addCount(ConcurrentHashMap.java:2334)
	at java.base/java.util.concurrent.ConcurrentHashMap.replaceNode(ConcurrentHashMap.java:1173)
	at java.base/java.util.concurrent.ConcurrentHashMap.remove(ConcurrentHashMap.java:1102)
	at java.base/java.lang.invoke.MethodType$ConcurrentWeakInternSet.expungeStaleElements(MethodType.java:1409)
	at java.base/java.lang.invoke.MethodType$ConcurrentWeakInternSet.get(MethodType.java:1368)
	at java.base/java.lang.invoke.MethodType.makeImpl(MethodType.java:343)
	at java.base/java.lang.invoke.MethodType.methodType(MethodType.java:287)
	at nsk.share.gc.gp.GarbageUtils.eatMemory(GarbageUtils.java:211)
	... 4 more
----------System.err:(16/954)----------
nsk.share.TestFailure: Test exit code: 97
	at nsk.share.test.Tests$TestRunner.execute(Tests.java:90)
	at nsk.share.test.Tests$TestRunner.run(Tests.java:96)
	at nsk.share.gc.GC.runTest(GC.java:114)
	at gc.lock.LockerTest.main(LockerTest.java:77)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at com.sun.javatest.regtest.agent.MainWrapper$MainThread.run(MainWrapper.java:127)
	at java.base/java.lang.Thread.run(Thread.java:831)

JavaTest Message: Test threw exception: nsk.share.TestFailure: Test exit code: 97
JavaTest Message: shutting down test

STATUS:Failed.`main' threw exception: nsk.share.TestFailure: Test exit code: 97
----------rerun:(44/6484)*----------
Comments
A pull request was submitted for review. Branch: master URL: https://git.openjdk.org/jdk11u-dev/pull/2901 Date: 2024-08-09 08:21:18 +0000
09-08-2024

Fix request [17u] I backport this for parity with 17.0.5-oracle. No risk, only a test change. Clean backport. SAP nightly testing passed.
29-07-2022

A pull request was submitted for review. URL: https://git.openjdk.org/jdk17u-dev/pull/605 Date: 2022-07-28 15:41:00 +0000
28-07-2022

Changeset: cd07b3ca Author: Coleen Phillimore <coleenp@openjdk.org> Date: 2021-10-21 11:46:24 +0000 URL: https://git.openjdk.java.net/jdk/commit/cd07b3cab00e6656e73a29f82210e2dedf26df8c
21-10-2021

The NCDFE in all the cases linked to this bug are caused by trying to load a class in the eatMemory function because of the MethodType declaration. Simply taking the MethodType out of the eatMemory function and loading it in static scope should make this test more reliable and fix this particular failure. Caused by: java.lang.NoClassDefFoundError: Could not initialize class java.util.concurrent.ThreadLocalRandom at java.base/java.util.concurrent.ConcurrentHashMap.fullAddCount(ConcurrentHashMap.java:2584) at java.base/java.util.concurrent.ConcurrentHashMap.addCount(ConcurrentHashMap.java:2334) at java.base/java.util.concurrent.ConcurrentHashMap.replaceNode(ConcurrentHashMap.java:1173) at java.base/java.util.concurrent.ConcurrentHashMap.remove(ConcurrentHashMap.java:1102) at java.base/java.lang.invoke.MethodType$ConcurrentWeakInternSet.expungeStaleElements(MethodType.java:1407) at java.base/java.lang.invoke.MethodType$ConcurrentWeakInternSet.get(MethodType.java:1366) at java.base/java.lang.invoke.MethodType.makeImpl(MethodType.java:341) at java.base/java.lang.invoke.MethodType.methodType(MethodType.java:281) at nsk.share.gc.gp.GarbageUtils.eatMemory(GarbageUtils.java:211)
14-10-2021

Now that we capture the original exception that caused class initialization to fail we see: Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.OutOfMemoryError: Java heap space [in thread "gc.lock.LockerTest$Worker@1d245116"] Unfortunately there is no stack trace.
24-08-2021

GarbageUtils.eatMemoryImpl is not implemented in a reliable way. If there are multiple threads all trying to consume memory then eatMemoryImpl could encounter OOME at numerous places where it does not really expect it. I can also see that even when it catches OOME internally it tries to do things that can fail because of another OOME eg: } catch (OutOfMemoryError e) { someMemory = null; if (type != OOM_TYPE.ANY) { if (type.accept(e.toString())) { numberOfOOMEs++; } else { // Trying to catch situation when Java generates OOM different type that test trying to catch throw new TestBug("Test throw OOM of unexpected type." + e.toString()); Simply setting someMemory to null does not guarantee that memory will be made available and avoid a future OOME - plus in the multiple threads case another thread could consume the memory even if it were made available.
24-08-2021

The spec is very clear and deliberate - any exception that occurs during execution of <clinit> marks the class as erroneous and thus unusable. The type of exception (Error or non-Error) only affects whether the original exception is propagated or it is wrapped by an ExceptionInInitializerError.
27-07-2021

I almost cut/pasted 7 and 12 as well, but they seemed to be talking about how to propagate errors from initializing super classes. Unfortunately when the OOM or other error is thrown, the class may be in a partially initialized state and you can't just start over and rerun the initializer. I think that's why you have to set the class in an error state and throw NCDFE. There's a linked bug that has a long discussion about how useful it would be to know why NCDFE is being thrown. We'd find this useful for these transient test failures also!
26-07-2021

Also in JVMS 5.5: [about the handling of the supers of C] 7. ... If the initialization of S completes abruptly because of a thrown exception, then acquire LC, label the Class object for C as erroneous, notify all waiting threads, release LC, and complete abruptly, throwing the same exception that resulted from initializing SC. and [what to do with the exception from 11] 12. Acquire LC, label the Class object for C as erroneous, notify all waiting threads, release LC, and complete this procedure abruptly with reason E or its replacement as determined in the previous step. So no, the spec doesn't seem to make any distinction between Exceptions and errors for marking the class in an "erroneous state". That seems like a specification bug to me, particularly for an asynchronous transient error like OOME. If it's not a specification bug, then it's not clear these kinds of failures are solvable. And while these tests are hitting them kind of frequently, there's also no reason ordinary user code couldn't run into the same kinds of problems, unless one wants to declare that OOME is really bad and can't reliably be handled, which *is* implied by it being an Error. "An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch." But people often do (think they) want to catch OOME.
26-07-2021

The spec for initialization errors doesn't seem to differentiate between Exceptions and errors for marking the class in an "erroneous state", or does it? JVMLS 5.5: (where LC is lock for class object) ... 5. If the Class object for C is in an erroneous state, then initialization is not possible. Release LC and throw a NoClassDefFoundError. ... 11. Otherwise, the class or interface initialization method must have completed abruptly by throwing some exception E. If the class of E is not Error or one of its subclasses, then create a new instance of the class ExceptionInInitializerError with E as the argument, and use this object in place of E in the following step. If a new instance of ExceptionInInitializerError cannot be created because an OutOfMemoryError occurs, then use an OutOfMemoryError object in place of E in the following step. So there's a distinction between Exception and Error on whether the Exception is wrapped in ExceptionInInitializerError. Does that mean that if the exception is an Error or subclasses, do we not mark the class object for C in an erroneous state?
26-07-2021

JVMs 5.4.3 : Resolution errors aren't saved unless they're an instance of LinkageError. I thought initialization errors were the same. Will investigate. Thanks for pointing to the new failure.
26-07-2021

> I think setting the klass's state to "initialization_error" should not be done for some kinds exceptions. [~kbarrett] while I agree that some errors may be recoverable the JVMS does not allow for this and requires that any failure in executing the class initialization method results in the class being marked as erroneous.
25-07-2021

Looking at InstanceKlass::initialization_impl, I think what might be happening is that (1) we get OOME during klass initialization, (2) that sets the klass's state to "initialization_error", (3) the OOME propagates and is eventually caught and swallowed by the test, (4) the test later tries again to use the klass and finds it in the "initialization_error" state. I think setting the klass's state to "initialization_error" should not be done for some kinds exceptions. Maybe just OOME, or maybe some higher up category, like VirtualMachineError or Error. OOME looks like the right one though; it can happen more or less anywhere, and probably doesn't really indicate anything wrong with the computation, so trying again later is probably fine and indeed desirable. The main question is whether the klass reached some intermediate initialization step that isn't later resumable.
25-07-2021

ILW=MLM=P4
01-06-2021

If we throw the NCDFE because the class is in the erroneous state it means that the static initializer for the class previously threw an exception. If that was an OOME and we have exhausted the heap then the processing of the OOME can lead to a second (and third, and fourth ...) OOME which prevents any Java level evidence that an uncaught exception occurred ie no stack trace. VM exception logging will show something, but not in a very clear way. That said you would expect ThreadLocalRandom to be initialized relatively early before any OOME generation has been triggered. In particular the eatMemory logic can't start to eat memory until after it has does the MH lookup that is seen in the NCDFE stacktrace, which means it has to initialize ThreadLocalRandom before it can start eating memory. So there is definitely something non-obvious happening here. That said, any test that tries to trigger OOME is fragile because the test cannot control exactly where the OOME will occur.
29-05-2021

Oracle CI had a similar failure in vmTestbase/gc/lock/malloc/malloclock03/TestDescription.java, originally reported as JDK-8267882. The only occurrence of "Could not initialize class" is in instanceKlass.cpp:1067. This is in InstanceKlass::initialize_impl, when handling the case where the class is_in_error_state(). It may be that the initialization failed due to OOME but is being reported as a NoClassDefFoundError. The test is in GarbageUtils.eatMemory, where OOME would be handled usefully but NoClassDefFoundError not so much. Maybe the problem is that the wrong exception type is being thrown. Or maybe the problem is that the test or eatMemory should be more resiliant about what exceptions are being thrown. Moving this to the runtime team for that determination, and possibly to fix accordingly. Though this and all the linked failures are with zgc, so there may be some bad interaction between how the klass initialization handles OOME and zgc.
28-05-2021

I've looked through the history of the vmTestbase gclocker tests and can not find any indication that this is still happening. I propose that we close this bug as Cannot Reproduce.
11-05-2021

I ran 500 iterations of the test on Windows with -XX:+UseZGC (to match the reported failure) with no failures. Since the failed test involves JVMTI, I I looked for some relation to the recent JVMTI change to ObjectFree event handling (JDK-8212879). I didn't find any. (There might still be something lurking there; I'm not an expert in JVMTI.) For now this doesn't look like a GC issue. Transferring this to the runtime subcomponent as possible a problem in class loading. Some other possibilities are a JVMTI problem or a test (or JVMTI test infrastructure) problem.
04-12-2020