RFR: 8309034: NoClassDefFoundError when initializing Long$LongCache

David Holmes dholmes at openjdk.org
Tue Jun 13 21:23:07 UTC 2023


On Tue, 13 Jun 2023 13:41:34 GMT, Coleen Phillimore <coleenp at openjdk.org> wrote:

>> When a class fails to initialize we try to preserve information about the original cause of the failure in a constructed `ExceptionInInitializerError` so that the EIIE can be attached to subsequent `NoClassDefFoundError`s thrown when an erroneous class is later accessed. If construction of the EIIE itself throws an exception we presently do nothing and the original problem is lost. The main reasons we would fail to create the EIIE are because we throw `OutOfMemoryError` or `StackOverflowError`. If the original class initialization failed due to stackoverflow then it is very likely the attempt to create the EIIE will as well. This leads to a situation, as per the obscure message in the bug synopsis, where you get totally unexpected failure modes with nothing to tell that the stackoverflow was the original cause. You would need to enable VM exception logging to try and determine that.
>> 
>> This enhancement improves the current situation by setting the cause of the class initialization failure to be a `StackOverflowError` or `OutOfMemoryError`, if the original failure was the same, and the attempt to create the EIIE fails. We cannot create these dynamically of course (else we'd have created the EIIE) so these are pre-allocated, stackless shared instances, created a VM startup. The preallocated `OutOfMemoryError` already exists so we just added a pre-allocated `StackOverflowError`. Now when we get the `NoClassDefFoundError` instead of the obscure and uninformative:
>> 
>> java.lang.NoClassDefFoundError: Could not initialize class java.lang.Long$LongCache
>> 	at java.base/java.lang.Long.valueOf(Long.java:1202)
>> 	at a.<init>(Test_545.java:10)
>> 	at Test_545.j(Test_545.java:38)
>> 	at Test_545.main(Test_545.java:25)
>> 
>> we now see an additional piece of information to shed light on the original issue:
>> 
>> java.lang.NoClassDefFoundError: Could not initialize class java.lang.Long$LongCache
>> 	at java.base/java.lang.Long.valueOf(Long.java:1202)
>> 	at a.<init>(Test_545.java:10)
>> 	at Test_545.j(Test_545.java:38)
>> 	at Test_545.main(Test_545.java:25)
>> Caused by: java.lang.StackOverflowError
>> 
>> Testing:
>> 
>> Tiers 1-3 sanity testing
>> 
>> The original reproducer was adapted into a regression test, but it was discovered that the failure mode was only observed on x64 systems (not unexpected as different architectures have different stack requirements and so will exhibit different stackoverflow behaviour). This was run 50 times on each x64 platform to check for intermi...
>
> src/hotspot/share/oops/instanceKlass.cpp line 1002:
> 
>> 1000:     } else {
>> 1001:       return;
>> 1002:     }
> 
> If the original exception was OOM or SOE, maybe we should skip the call to create_initialization_error?  Or in that function should return the pre allocated exceptions unconditionally?  It seems like we could avoid code that tries to allocate at an unstable state in the vm since we're explicitly testing for these conditions now.
> I think I'd rather see this code moved to create_initialization_error even though it doesn't create it for these.

Thanks for looking at this Coleen.

An OOME during initialization does not necessarily imply an unstable state in the VM as it may not relate to the Java heap. So I think it is better to try to do the right thing and only use the shared instances as a fall-back when things go wrong.

I actually started with the code in `create_initialization_error` but decided it was the wrong place to decide what to do if that fails. I think the policy belongs in instanceKlass.

-------------

PR Review Comment: https://git.openjdk.org/jdk/pull/14438#discussion_r1228708643


More information about the hotspot-runtime-dev mailing list