diff a/src/hotspot/share/cds/archiveHeapWriter.hpp b/src/hotspot/share/cds/archiveHeapWriter.hpp
--- a/src/hotspot/share/cds/archiveHeapWriter.hpp
+++ b/src/hotspot/share/cds/archiveHeapWriter.hpp
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.
@@ -61,10 +61,11 @@
   size_t heap_roots_offset() const { return _heap_roots_offset; }
 };
 
 #if INCLUDE_CDS_JAVA_HEAP
 class ArchiveHeapWriter : AllStatic {
+  friend class HeapShared;
   // ArchiveHeapWriter manipulates three types of addresses:
   //
   //     "source" vs "buffered" vs "requested"
   //
   // (Note: the design and convention is the same as for the archiving of Metaspace objects.
@@ -138,10 +139,11 @@
   static address _requested_bottom;
   static address _requested_top;
 
   static GrowableArrayCHeap<NativePointerInfo, mtClassShared>* _native_pointers;
   static GrowableArrayCHeap<oop, mtClassShared>* _source_objs;
+  static GrowableArrayCHeap<oop, mtClassShared>* _perm_objs;
 
   // We sort _source_objs_order to minimize the number of bits in ptrmap and oopmap.
   // See comments near the body of ArchiveHeapWriter::compare_objs_by_oop_fields().
   // The objects will be written in the order of:
   //_source_objs->at(_source_objs_order->at(0)._index)
@@ -191,21 +193,26 @@
   static size_t buffered_address_to_offset(address buffered_addr) {
     assert(in_buffer(buffered_addr), "sanity");
     return buffered_addr - buffer_bottom();
   }
 
-  static void copy_roots_to_buffer(GrowableArrayCHeap<oop, mtClassShared>* roots);
-  static void copy_source_objs_to_buffer(GrowableArrayCHeap<oop, mtClassShared>* roots);
+  static size_t create_objarray_in_buffer(GrowableArrayCHeap<oop, mtClassShared>* input, int from,
+                                          int num_elms, int extra_length, size_t& objarray_word_size);
+  static int copy_source_objs_to_buffer(GrowableArrayCHeap<oop, mtClassShared>* roots, GrowableArray<size_t>* permobj_seg_offsets);
+  template <typename T> static void add_permobj_segments_to_roots(GrowableArrayCHeap<oop, mtClassShared>* roots,
+                                                                  ArchiveHeapInfo* info, GrowableArray<size_t>* permobj_seg_offsets);
+  static void update_stats(oop src_obj);
   static size_t copy_one_source_obj_to_buffer(oop src_obj);
 
   static void maybe_fill_gc_region_gap(size_t required_byte_size);
   static size_t filler_array_byte_size(int length);
   static int filler_array_length(size_t fill_bytes);
   static HeapWord* init_filler_array_at_buffer_top(int array_length, size_t fill_bytes);
 
   static void set_requested_address(ArchiveHeapInfo* info);
-  static void relocate_embedded_oops(GrowableArrayCHeap<oop, mtClassShared>* roots, ArchiveHeapInfo* info);
+  static void relocate_embedded_oops(GrowableArrayCHeap<oop, mtClassShared>* roots, ArchiveHeapInfo* info,
+                                     GrowableArray<size_t>* permobj_seg_offsets, int num_permobj);
   static void compute_ptrmap(ArchiveHeapInfo *info);
   static bool is_in_requested_range(oop o);
   static oop requested_obj_from_buffer_offset(size_t offset);
 
   static oop load_oop_from_buffer(oop* buffered_addr);
@@ -217,17 +224,30 @@
   template <typename T> static void store_requested_oop_in_buffer(T* buffered_addr, oop request_oop);
 
   template <typename T> static T* requested_addr_to_buffered_addr(T* p);
   template <typename T> static void relocate_field_in_buffer(T* field_addr_in_buffer, CHeapBitMap* oopmap);
   template <typename T> static void mark_oop_pointer(T* buffered_addr, CHeapBitMap* oopmap);
-  template <typename T> static void relocate_root_at(oop requested_roots, int index, CHeapBitMap* oopmap);
+  template <typename T> static void relocate_root_at(oop requested_roots, address buffered_roots_addr, int index, CHeapBitMap* oopmap);
 
   static void update_header_for_requested_obj(oop requested_obj, oop src_obj, Klass* src_klass);
 
+  // "Permanent Objects"
+  //
+  // These objects are guaranteed to be in the heap at runtime. The AOT can use
+  // HeapShared::get_archived_object_permanent_index() and HeapShared::get_archived_object() to
+  // inline these objects into the AOT cache.
+  //
+  // Currently all archived objects are "permanent". We may want to narrow the scope ....
+  //
+  // The permobjs are divided into multiple segments, each containing 64K elements (or 4096 in debug builds).
+  // This is to avoid overflowing MIN_GC_REGION_ALIGNMENT.
+  static constexpr int PERMOBJ_SEGMENT_MAX_SHIFT  = DEBUG_ONLY(12) NOT_DEBUG(16);
+  static constexpr int PERMOBJ_SEGMENT_MAX_LENGTH = 1 << PERMOBJ_SEGMENT_MAX_SHIFT;
+  static constexpr int PERMOBJ_SEGMENT_MAX_MASK   = PERMOBJ_SEGMENT_MAX_LENGTH - 1;
+
   static int compare_objs_by_oop_fields(HeapObjOrder* a, HeapObjOrder* b);
   static void sort_source_objs();
-
 public:
   static void init() NOT_CDS_JAVA_HEAP_RETURN;
   static void add_source_obj(oop src_obj);
   static bool is_too_large_to_archive(size_t size);
   static bool is_too_large_to_archive(oop obj);
@@ -240,10 +260,13 @@
   }
   static size_t heap_roots_word_size() {
     return _heap_roots_word_size;
   }
   static size_t get_filler_size_at(address buffered_addr);
+  static int get_permobj_segment_at(address buffered_addr, size_t* byte_size, int* permobj_segment_length);
+  static oop get_permobj_source_addr(int permobj_segment, int index);
+  static oop get_perm_object_by_index(int permanent_index);
 
   static void mark_native_pointer(oop src_obj, int offset);
   static bool is_marked_as_native_pointer(ArchiveHeapInfo* heap_info, oop src_obj, int field_offset);
   static oop source_obj_to_requested_obj(oop src_obj);
   static oop buffered_addr_to_source_obj(address buffered_addr);
