JDK-8371673 : Permanent oops not implemented for AOT streamed heap
  • Type: Bug
  • Component: hotspot
  • Sub-Component: gc
  • Affected Version: repo-leyden
  • Priority: P4
  • Status: New
  • Resolution: Unresolved
  • Submitted: 2025-11-12
  • Updated: 2025-12-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.
Other
repo-leydenUnresolved
Related Reports
Relates :  
Description
HeapShared::get_archived_object_permanent_index() is a new API added in Leyden/premain to support the AOT compiler. It is possible for the AOT compiler to ask for a "permanent index" for any archived oop (including those that are covered by HeapShared::get_root()).

This API is not implemented in streaming mode:

$ gdb --args java -cp HelloWorld.jar -XX:+UseZGC -XX:AOTMode=create -Xlog:aot,cds -XX:AOTCache=hw.aot -XX:AOTConfiguration=hw.aotconfig 

Thread 56 "C2 Comp..hread8" hit Breakpoint 2, HeapShared::get_archived_object_permanent_index (obj=0x40000a1cf78)
    at /jdk3/le4/open/src/hotspot/share/cds/heapShared.cpp:502
502	  MutexLocker ml(ArchivedObjectTables_lock, Mutex::_no_safepoint_check_flag);
(gdb) n
504	  if (!CDSConfig::is_dumping_heap()) {
(gdb) 
507	  if (_dumptime_permanent_oop_table == nullptr) {
(gdb) 
508	    return -1;
(gdb) where
#0  HeapShared::get_archived_object_permanent_index (obj=0x40000a1cf78) at /jdk3/le4/open/src/hotspot/share/cds/heapShared.cpp:502
#1  0x00007ffff5830ea2 in AOTCacheAccess::get_archived_object_permanent_index (obj=0x40000a1cf78)
    at /jdk3/le4/open/src/hotspot/share/cds/aotCacheAccess.cpp:78
#2  0x00007ffff5849f81 in AOTCodeCache::write_oop (this=0x7ffff01652d0, obj=0x40000a1cf78)
    at /jdk3/le4/open/src/hotspot/share/code/aotCodeCache.cpp:2578
#3  0x00007ffff5848471 in AOTCodeCache::write_nmethod_reloc_immediates (this=0x7ffff01652d0, oop_list=..., metadata_list=...)
    at /jdk3/le4/open/src/hotspot/share/code/aotCodeCache.cpp:2225
#4  0x00007ffff5845d06 in AOTCodeCache::write_nmethod (this=0x7ffff01652d0, nm=0x7fffdf481e08, for_preload=true)
    at /jdk3/le4/open/src/hotspot/share/code/aotCodeCache.cpp:1725
#5  0x00007ffff58452f3 in AOTCodeCache::store_nmethod (nm=0x7fffdf481e08, compiler=0x7ffff023acb0, for_preload=true)
    at /jdk3/le4/open/src/hotspot/share/code/aotCodeCache.cpp:1593

Because of this, the AOT code is less efficient. When the permanent index cannot be obtained for an oop, we will get an uncommon trap.


Comments
Tried out removing perm objects in favour of normal roots: https://github.com/fisk/jdk/commit/f07211536b41fc7ecb95658c0c9e49b4a18a71bd Seems to work.
08-12-2025

Reading the current implementation of HeapShared::get_archived_object_permanent_index in the premain branch, it looks like we first check if we are not dumping the heap, then return -1 with a comment saying this is some old Leyden workflow. This means that the rest of the logic there that actually gives you a "permanent object index", is used when we are also dumping the heap. This suggests to me that we could just use the normal archived heap roots and not invent the same wheel again? Or maybe I'm missing something?
08-12-2025

I built some support for this last time I took this stuff for a spin over the leyden repo. It's a bit old and things were called "CDS" back then, so might need dusting off a little bit. But in general I implemented permanent objects by using the super stable "object index" instead. In a way, this was sort of trivial with streaming. Some dusty code here: https://github.com/fisk/jdk/blob/d1a43269f2ae942a1045e9d06e735b206e840679/src/hotspot/share/cds/streamingArchiveHeapLoader.cpp#L763 Either way, is there a reason (today) why this isn't just normal heap roots? It seems like we have two separate mechanisms of pointing out roots and refer to them across runs. If I understood the motivation correctly, it was so that we could perform a static dump with "normal roots", that doesn't really know what the AOT code wants, and then separately dump the AOT code, which could then just have references to the objects created by the static archive. Is this still the case, or is the AOT code also dumped in a static archive? If so, then it would appear to me that we could just use normal archive roots instead?
08-12-2025

It is actually much worse for Leyden: since the code dumping bails when the AOT code references any non-archived object. In the stack Ioi listed, AOTCodeCache::store_nmethod would basically fail to store the nmethod in AOT cache. But wait, it gets worse, we lose *a lot* of stored code when running with -XX:-UseCompressedOops and/or large Java heap. This is because +AOTStreamableObjects flips *ON* when -UseCompressedOops! if (FLAG_IS_DEFAULT(AOTStreamableObjects)) { // By default, the value of AOTStreamableObjects should match !UseCompressedOops. FLAG_SET_DEFAULT(AOTStreamableObjects, !UseCompressedOops); Why does it do that? Ooooof.
08-12-2025

In mapped mode, each object in the _runtime_permanent_oops is represented by the offset of this object in the archive region. The following code is executed before the first GC, so the offsets are still valid. void CachedCodeDirectoryInternal::runtime_init_internal() { int count = _permanent_oop_count; int* table = _permanent_oop_offsets; _runtime_permanent_oops = new GrowableArrayCHeap<OopHandle, mtClassShared>(); for (int i = 0; i < count; i++) { oop obj = HeapShared::is_loading_streaming_mode() ? nullptr : /* FIXME not implemented */ AOTMappedHeapLoader::oop_from_offset(table[i]); OopHandle oh(Universe::vm_global(), obj); _runtime_permanent_oops->append(oh); } };
12-11-2025