JDK-8364111 : InstanceMirrorKlass iterators should handle CDS and hidden classes consistently
  • Type: Bug
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 25,26
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2025-07-25
  • Updated: 2025-08-05
  • Resolved: 2025-07-31
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 26
26 b09Fixed
Related Reports
Blocks :  
Relates :  
Relates :  
Description
When implementing JDK-8358340, I stumbled upon a weird assert:

$ CONF=linux-x86_64-server-fastdebug make test TEST=gc/shenandoah/TestAllocIntArrays.java JTREG=REPEAT_COUNT=10

#  Internal Error (/home/shade/trunks/jdk/src/hotspot/share/memory/iterator.inline.hpp:59), pid=1222144, tid=1222159
#  assert(AOTLinkedClassBulkLoader::is_pending_aot_linked_class(k)) failed: sanity: java.lang.module.ModuleDescriptor$Modifier

Stack: [0x000071e2112d3000,0x000071e2113d3000],  sp=0x000071e2113d1930,  free space=1018k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V  [libjvm.so+0x3bad37]  ClaimMetadataVisitingOopIterateClosure::do_klass(Klass*)+0x427  (iterator.inline.hpp:59)
V  [libjvm.so+0x1afeb11]  void InstanceMirrorKlass::oop_oop_iterate_bounded<narrowOop, ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2> >(oop, ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2>*, MemRegion)+0x9d1  (devirtualizer.inline.hpp:126)
V  [libjvm.so+0x1afec75]  void OopOopIterateBoundedDispatch<ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2> >::Table::oop_oop_iterate_bounded<InstanceMirrorKlass, narrowOop>(ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2>*, oop, Klass*, MemRegion)+0x65  (iterator.inline.hpp:182)
V  [libjvm.so+0x1af6285]  void ShenandoahScanRemembered::process_clusters<ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2> >(unsigned long, unsigned long, HeapWordImpl**, ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2>*, bool, unsigned int)+0x8d5  (iterator.inline.hpp:306)
V  [libjvm.so+0x1af0c8d]  ShenandoahScanRememberedTask::do_work(unsigned int)+0xb1d  (shenandoahScanRemembered.inline.hpp:359)
V  [libjvm.so+0x1af0e4c]  ShenandoahScanRememberedTask::work(unsigned int)+0xcc  (shenandoahScanRemembered.cpp:664)
V  [libjvm.so+0x1e52348]  WorkerThread::run()+0x88  (workerThread.cpp:69)
V  [libjvm.so+0x1cf321a]  Thread::call_run()+0xba  (thread.cpp:243)
V  [libjvm.so+0x17a78c8]  thread_native_entry(Thread*)+0x128  (os_linux.cpp:868)
C  [libc.so.6+0x9caa4]

The assert effectively says that we are encountering the class with nullptr CLD, e.g. the class is not yet loaded. Printing out the class name shows class names that are likely not used in the test at all (something like CurrencyProvider, etc). So I believe this is a class that is loaded from CDS archived heap.

Notice that we are walking InstanceMirrorKlass. Fuller crash trace with slowdebug:

Stack: [0x00007394d70f1000,0x00007394d71f1000],  sp=0x00007394d71ef780,  free space=1017k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V  [libjvm.so+0x486ac5]  ClaimMetadataVisitingOopIterateClosure::do_klass(Klass*)+0x87  (iterator.inline.hpp:58)
V  [libjvm.so+0x193cac9]  EnableIf<!std::is_same<ClaimMetadataVisitingOopIterateClosure, OopIterateClosure>::value, void>::type call_do_klass<ClaimMetadataVisitingOopIterateClosure, OopIterateClosure, ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2> >(void (ClaimMetadataVisitingOopIterateClosure::*)(Klass*), void (OopIterateClosure::*)(Klass*), ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2>*, Klass*)+0x46  (devirtualizer.inline.hpp:126)
V  [libjvm.so+0x1959991]  void Devirtualizer::do_klass<ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2> >(ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2>*, Klass*)+0x5d  (devirtualizer.inline.hpp:131)
V  [libjvm.so+0x19a166e]  void InstanceMirrorKlass::oop_oop_iterate_bounded<narrowOop, ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2> >(oopDesc*, ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2>*, MemRegion)+0x376  (instanceMirrorKlass.inline.hpp:132)
V  [libjvm.so+0x19a0406]  void OopOopIterateBoundedDispatch<ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2> >::Table::oop_oop_iterate_bounded<InstanceMirrorKlass, narrowOop>(ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2>*, oopDesc*, Klass*, MemRegion)+0x48  (iterator.inline.hpp:181)
V  [libjvm.so+0x199d8e5]  void OopIteratorClosureDispatch::oop_oop_iterate<ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2> >(ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2>*, oopDesc*, Klass*, MemRegion)+0x4b  (iterator.inline.hpp:305)
V  [libjvm.so+0x199d80f]  unsigned long oopDesc::oop_iterate_size<ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2> >(ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2>*, MemRegion)+0x75  (oop.inline.hpp:397)
V  [libjvm.so+0x199d1eb]  void ShenandoahScanRemembered::process_clusters<ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2> >(unsigned long, unsigned long, HeapWordImpl**, ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2>*, bool, unsigned int)+0x112b  (shenandoahScanRemembered.inline.hpp:241)
V  [libjvm.so+0x199bd5a]  void ShenandoahScanRemembered::process_region_slice<ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2> >(ShenandoahHeapRegion*, unsigned long, unsigned long, HeapWordImpl**, ShenandoahMarkRefsClosure<(ShenandoahGenerationType)2>*, bool, unsigned int)+0x2a6  (shenandoahScanRemembered.inline.hpp:363)
V  [libjvm.so+0x1997b80]  ShenandoahScanRememberedTask::do_work(unsigned int)+0x3be  (shenandoahScanRemembered.cpp:700)
V  [libjvm.so+0x199773d]  ShenandoahScanRememberedTask::work(unsigned int)+0x9d  (shenandoahScanRemembered.cpp:664)
V  [libjvm.so+0x1cd861d]  WorkerTaskDispatcher::worker_run_task()+0x95  (workerThread.cpp:69)
V  [libjvm.so+0x1cd8d74]  WorkerThread::run()+0x34  (workerThread.cpp:200)
V  [libjvm.so+0x1b579b5]  Thread::call_run()+0x1bd  (thread.cpp:243)

I believe this whole thing is a variant of JDK-8253081, but for Generational Shenandoah. After initial CDS load, we proceed to run the application, and at _some point_ we scan a dirty card, and _by accident_ discover a Java mirror for a class that is not yet loaded. We then go into InstanceMirrorKlass::oop_oop_iterate_bounded, which resolves the Klass* from that Java mirror, which turns out to be not yet loaded. So its CLD is nullptr, and the related visitor asserts.
Comments
Now, it is interesting that JDK-8331497 actually added a null-check for CLDs in the relevant iterator: https://github.com/openjdk/jdk/commit/41a2d49f0a1ed298b8ab023ce634335464454fe7#diff-9512406aa5bffbef807aa26007f54e2bfb6b0635e1829aa77bf483bdfd342c8cR55-R59 So this saves us from the crash in release JDK 24+ builds. The assert fails, because in these tests, we are not even doing the AOTClassLinking, we are only using the pre-built static CDS archive. So the `AOTLinkedClassBulkLoader::is_pending_aot_linked_class` is guaranteed to be `false`.
31-07-2025

Changeset: ebb7f5d3 Branch: master Author: Aleksey Shipilev <shade@openjdk.org> Date: 2025-07-31 06:07:17 +0000 URL: https://git.openjdk.org/jdk/commit/ebb7f5d39be8497fc89e25d0905335102e12c063
31-07-2025

A pull request was submitted for review. Branch: master URL: https://git.openjdk.org/jdk/pull/26477 Date: 2025-07-25 12:01:00 +0000
25-07-2025

JDK-8253081 added filter for nullptr CLDs in InstanceMirrorKlass::oop_oop_iterate. But GenShen enters through InstanceMirrorKlass::oop_oop_iterate_bounded, which does not have this filter. Replicating the nullptr CLD filter in InstanceMirrorKlass::oop_oop_iterate_bounded fixes the test! Which makes sense since we do not even enter the path where JDK-8331497 asserts later. Additionally, IMK::oop_oop_iterate seems to handle hidden class mirrors, while IMK::oop_oop_iterate_bounded does not. That probably causes some other bugs. So I am thinking that we should sync up IMK iterators to do the same thing, namely handling nullptr CLDs and hidden classes in their Java mirrors consistently.
25-07-2025