diff a/src/hotspot/share/prims/jvmtiTagMap.cpp b/src/hotspot/share/prims/jvmtiTagMap.cpp
--- a/src/hotspot/share/prims/jvmtiTagMap.cpp
+++ b/src/hotspot/share/prims/jvmtiTagMap.cpp
@@ -69,23 +69,86 @@
 #include "utilities/objectBitSet.inline.hpp"
 #include "utilities/macros.hpp"
 
 typedef ObjectBitSet<mtServiceability> JVMTIBitSet;
 
+
+// Helper class to store objects to visit.
+class JvmtiHeapwalkVisitStack {
+private:
+  enum {
+    initial_visit_stack_size = 4000
+  };
+
+  GrowableArray<JvmtiHeapwalkObject>* _visit_stack;
+  JVMTIBitSet _bitset;
+
+  static GrowableArray<JvmtiHeapwalkObject>* create_visit_stack() {
+    return new (mtServiceability) GrowableArray<JvmtiHeapwalkObject>(initial_visit_stack_size, mtServiceability);
+  }
+
+public:
+  JvmtiHeapwalkVisitStack(): _visit_stack(create_visit_stack()) {
+  }
+  ~JvmtiHeapwalkVisitStack() {
+    if (_visit_stack != nullptr) {
+      delete _visit_stack;
+    }
+  }
+
+  bool is_empty() const {
+    return _visit_stack->is_empty();
+  }
+
+  void push(const JvmtiHeapwalkObject& obj) {
+    _visit_stack->push(obj);
+  }
+
+  // If the object hasn't been visited then push it onto the visit stack
+  // so that it will be visited later.
+  void check_for_visit(const JvmtiHeapwalkObject& obj) {
+    if (!is_visited(obj)) {
+      _visit_stack->push(obj);
+    }
+  }
+
+  JvmtiHeapwalkObject pop() {
+    return _visit_stack->pop();
+  }
+
+  bool is_visited(const JvmtiHeapwalkObject& obj) /*const*/ { // TODO: _bitset.is_marked() should be const
+    // The method is called only for objects from visit_stack to ensure an object is not visited twice.
+    // Flat objects can be added to visit_stack only when we visit their holder object, so we cannot get duplicate reference to it.
+    if (obj.is_flat()) {
+      return false;
+    }
+    return _bitset.is_marked(obj.obj());
+  }
+
+  void mark_visited(const JvmtiHeapwalkObject& obj) {
+    if (!obj.is_flat()) {
+      _bitset.mark_obj(obj.obj());
+    }
+  }
+};
+
+
 bool JvmtiTagMap::_has_object_free_events = false;
 
 // create a JvmtiTagMap
 JvmtiTagMap::JvmtiTagMap(JvmtiEnv* env) :
   _env(env),
   _lock(Mutex::nosafepoint, "JvmtiTagMap_lock"),
   _needs_cleaning(false),
-  _posting_events(false) {
+  _posting_events(false),
+  _converting_flat_object(false) {
 
   assert(JvmtiThreadState_lock->is_locked(), "sanity check");
   assert(((JvmtiEnvBase *)env)->tag_map() == nullptr, "tag map already exists for environment");
 
   _hashmap = new JvmtiTagMapTable();
+  _flat_hashmap = new JvmtiFlatTagMapTable();
 
   // finally add us to the environment
   ((JvmtiEnvBase *)env)->release_set_tag_map(this);
 }
 
@@ -97,18 +160,20 @@
   ((JvmtiEnvBase *)_env)->set_tag_map(nullptr);
 
   // finally destroy the hashmap
   delete _hashmap;
   _hashmap = nullptr;
+  delete _flat_hashmap;
 }
 
 // Called by env_dispose() to reclaim memory before deallocation.
 // Remove all the entries but keep the empty table intact.
 // This needs the table lock.
 void JvmtiTagMap::clear() {
   MutexLocker ml(lock(), Mutex::_no_safepoint_check_flag);
   _hashmap->clear();
+  _flat_hashmap->clear();
 }
 
 // returns the tag map for the given environments. If the tag map
 // doesn't exist then it is created.
 JvmtiTagMap* JvmtiTagMap::tag_map_for(JvmtiEnv* env) {
@@ -123,19 +188,14 @@
     DEBUG_ONLY(JavaThread::current()->check_possible_safepoint());
   }
   return tag_map;
 }
 
-// iterate over all entries in the tag map.
-void JvmtiTagMap::entry_iterate(JvmtiTagMapKeyClosure* closure) {
-  hashmap()->entry_iterate(closure);
-}
-
 // returns true if the hashmaps are empty
-bool JvmtiTagMap::is_empty() {
+bool JvmtiTagMap::is_empty() const {
   assert(SafepointSynchronize::is_at_safepoint() || is_locked(), "checking");
-  return hashmap()->is_empty();
+  return _hashmap->is_empty() && _flat_hashmap->is_empty();
 }
 
 // This checks for posting before operations that use
 // this tagmap table.
 void JvmtiTagMap::check_hashmap(GrowableArray<jlong>* objects) {
@@ -165,17 +225,191 @@
       tag_map->check_hashmap(objects);
     }
   }
 }
 
-// Return the tag value for an object, or 0 if the object is
-// not tagged
-//
-static inline jlong tag_for(JvmtiTagMap* tag_map, oop o) {
-  return tag_map->hashmap()->find(o);
+// Converts entries from JvmtiFlatTagMapTable to JvmtiTagMapTable in batches.
+//   1. (JvmtiTagMap is locked)
+//      reads entries from JvmtiFlatTagMapTable (describe flat value objects);
+//   2. (JvmtiTagMap is unlocked)
+//      creates heap-allocated copies of the flat object;
+//   3. (JvmtiTagMap is locked)
+//      ensures source entry still exists, removes it from JvmtiFlatTagMapTable, adds new entry to JvmtiTagMapTable.
+// If some error occurs in step 2 (OOM?), the process stops.
+class JvmtiTagMapFlatEntryConverter: public StackObj {
+private:
+  struct Entry {
+    // source flat value object
+    Handle holder;
+    int offset;
+    InlineKlass* inline_klass;
+    LayoutKind layout_kind;
+    // converted heap-allocated object
+    Handle dst;
+
+    Entry(): holder(), offset(0), inline_klass(nullptr), dst() {}
+    Entry(Handle holder, int offset, InlineKlass* inline_klass, LayoutKind lk)
+      : holder(holder), offset(offset), inline_klass(inline_klass), layout_kind(lk), dst() {}
+  };
+
+  int _batch_size;
+  GrowableArray<Entry> _entries;
+  bool _has_error;
+
+public:
+  JvmtiTagMapFlatEntryConverter(int batch_size): _batch_size(batch_size), _entries(batch_size, mtServiceability), _has_error(false) { }
+  ~JvmtiTagMapFlatEntryConverter() {}
+
+  // returns false if there is nothing to convert
+  bool import_entries(JvmtiFlatTagMapTable* table) {
+    if (_has_error) {
+      // stop the process to avoid infinite loop
+      return false;
+    }
+
+    class Importer: public JvmtiFlatTagMapKeyClosure {
+    private:
+      GrowableArray<Entry>& _entries;
+      int _batch_size;
+    public:
+      Importer(GrowableArray<Entry>& entries, int batch_size): _entries(entries), _batch_size(batch_size) {}
+
+      bool do_entry(JvmtiFlatTagMapKey& key, jlong& tag) {
+        Entry entry(Handle(Thread::current(), key.holder()), key.offset(), key.inline_klass(), key.layout_kind());
+        _entries.append(entry);
+
+        return _entries.length() < _batch_size;
+      }
+    } importer(_entries, _batch_size);
+    table->entry_iterate(&importer);
+
+    return !_entries.is_empty();
+  }
+
+  void convert() {
+    for (int i = 0; i < _entries.length(); i++) {
+      EXCEPTION_MARK;
+      Entry& entry = _entries.at(i);
+      oop obj = entry.inline_klass->read_payload_from_addr(entry.holder(), entry.offset, entry.layout_kind, JavaThread::current());
+
+      if (HAS_PENDING_EXCEPTION) {
+        tty->print_cr("Exception in JvmtiTagMapFlatEntryConverter: ");
+        java_lang_Throwable::print(PENDING_EXCEPTION, tty);
+        tty->cr();
+        CLEAR_PENDING_EXCEPTION;
+        // stop the conversion
+        _has_error = true;
+      } else {
+        entry.dst = Handle(Thread::current(), obj);
+      }
+    }
+  }
+
+  // returns number of converted entries
+  int move(JvmtiFlatTagMapTable* src_table, JvmtiTagMapTable* dst_table) {
+    int count = 0;
+    for (int i = 0; i < _entries.length(); i++) {
+      Entry& entry = _entries.at(i);
+      if (entry.dst() == nullptr) {
+        // some error during conversion, skip the entry
+        continue;
+      }
+      JvmtiHeapwalkObject obj(entry.holder(), entry.offset, entry.inline_klass, entry.layout_kind);
+      jlong tag = src_table->remove(obj);
+
+      if (tag != 0) { // ensure the entry is still in the src_table
+        dst_table->add(entry.dst(), tag);
+        count++;
+      } else {
+
+      }
+    }
+    // and clean the array
+    _entries.clear();
+    return count;
+  }
+};
+
+
+void JvmtiTagMap::convert_flat_object_entries() {
+  Thread* current = Thread::current();
+  assert(current->is_Java_thread(), "must be executed on JavaThread");
+
+  log_debug(jvmti, table)("convert_flat_object_entries, main table size = %d, flat table size = %d",
+                          _hashmap->number_of_entries(), _flat_hashmap->number_of_entries());
+
+  {
+    MonitorLocker ml(lock(), Mutex::_no_safepoint_check_flag);
+    // If another thread is converting, let it finish.
+    while (_converting_flat_object) {
+      ml.wait();
+    }
+    if (_flat_hashmap->is_empty()) {
+      // nothing to convert
+      return;
+    }
+    _converting_flat_object = true;
+  }
+
+  const int BATCH_SIZE = 1024;
+  JvmtiTagMapFlatEntryConverter converter(BATCH_SIZE);
+
+  int count = 0;
+  while (true) {
+    HandleMark hm(current);
+    {
+      MonitorLocker ml(lock(), Mutex::_no_safepoint_check_flag);
+      if (!converter.import_entries(_flat_hashmap)) {
+        break;
+      }
+    }
+    // Convert flat objects to heap-allocated without table lock (so agent callbacks can get/set tags).
+    converter.convert();
+    {
+      MonitorLocker ml(lock(), Mutex::_no_safepoint_check_flag);
+      count += converter.move(_flat_hashmap, _hashmap);
+    }
+  }
+
+  log_info(jvmti, table)("%d flat value objects are converted, flat table size = %d",
+                         count, _flat_hashmap->number_of_entries());
+  {
+    MonitorLocker ml(lock(), Mutex::_no_safepoint_check_flag);
+    _converting_flat_object = false;
+    ml.notify_all();
+  }
 }
 
+jlong JvmtiTagMap::find(const JvmtiHeapwalkObject& obj) const {
+  jlong tag = _hashmap->find(obj);
+  if (tag == 0 && obj.is_value()) {
+    tag = _flat_hashmap->find(obj);
+  }
+  return tag;
+}
+
+void JvmtiTagMap::add(const JvmtiHeapwalkObject& obj, jlong tag) {
+  if (obj.is_flat()) {
+    // we may have tag for equal (non-flat) object in _hashmap, try to update it 1st
+    if (!_hashmap->update(obj, tag)) {
+      // no entry in _hashmap, add to _flat_hashmap
+      _flat_hashmap->add(obj, tag);
+    }
+  } else {
+    _hashmap->add(obj, tag);
+  }
+}
+
+void JvmtiTagMap::remove(const JvmtiHeapwalkObject& obj) {
+  if (!_hashmap->remove(obj)) {
+    if (obj.is_value()) {
+      _flat_hashmap->remove(obj);
+    }
+  }
+}
+
+
 // A CallbackWrapper is a support class for querying and tagging an object
 // around a callback to a profiler. The constructor does pre-callback
 // work to get the tag value, klass tag value, ... and the destructor
 // does the post-callback work of tagging or untagging the object.
 //
@@ -190,68 +424,69 @@
 // tag value has changed.
 //
 class CallbackWrapper : public StackObj {
  private:
   JvmtiTagMap* _tag_map;
-  JvmtiTagMapTable* _hashmap;
-  oop _o;
+  const JvmtiHeapwalkObject& _o;
   jlong _obj_size;
   jlong _obj_tag;
   jlong _klass_tag;
 
  protected:
   JvmtiTagMap* tag_map() const { return _tag_map; }
 
   // invoked post-callback to tag, untag, or update the tag of an object
-  void inline post_callback_tag_update(oop o, JvmtiTagMapTable* hashmap,
-                                       jlong obj_tag);
+  void inline post_callback_tag_update(const JvmtiHeapwalkObject& o, JvmtiTagMap* tag_map, jlong obj_tag);
+
  public:
-  CallbackWrapper(JvmtiTagMap* tag_map, oop o) {
+  CallbackWrapper(JvmtiTagMap* tag_map, const JvmtiHeapwalkObject& o)
+    : _tag_map(tag_map), _o(o)
+  {
     assert(Thread::current()->is_VM_thread() || tag_map->is_locked(),
            "MT unsafe or must be VM thread");
 
-    // object to tag
-    _o = o;
-
     // object size
-    _obj_size = (jlong)_o->size() * wordSize;
-
-    // record the context
-    _tag_map = tag_map;
-    _hashmap = tag_map->hashmap();
+    if (!o.is_flat()) {
+      // common case: we have oop
+      _obj_size = (jlong)o.obj()->size() * wordSize;
+    } else {
+      // flat value object, we know its InstanceKlass
+      assert(_o.inline_klass() != nullptr, "must be");
+      _obj_size = _o.inline_klass()->size() * wordSize;;
+    }
 
     // get object tag
-    _obj_tag = _hashmap->find(_o);
+    _obj_tag = _tag_map->find(_o);
 
     // get the class and the class's tag value
     assert(vmClasses::Class_klass()->is_mirror_instance_klass(), "Is not?");
 
-    _klass_tag = tag_for(tag_map, _o->klass()->java_mirror());
+    _klass_tag = _tag_map->find(_o.klass()->java_mirror());
   }
 
   ~CallbackWrapper() {
-    post_callback_tag_update(_o, _hashmap, _obj_tag);
+    post_callback_tag_update(_o, _tag_map, _obj_tag);
   }
 
   inline jlong* obj_tag_p()                     { return &_obj_tag; }
   inline jlong obj_size() const                 { return _obj_size; }
   inline jlong obj_tag() const                  { return _obj_tag; }
   inline jlong klass_tag() const                { return _klass_tag; }
 };
 
 // callback post-callback to tag, untag, or update the tag of an object
-void inline CallbackWrapper::post_callback_tag_update(oop o,
-                                                      JvmtiTagMapTable* hashmap,
+void inline CallbackWrapper::post_callback_tag_update(const JvmtiHeapwalkObject& o,
+                                                      JvmtiTagMap* tag_map,
                                                       jlong obj_tag) {
   if (obj_tag == 0) {
     // callback has untagged the object, remove the entry if present
-    hashmap->remove(o);
+    tag_map->remove(o);
   } else {
     // object was previously tagged or not present - the callback may have
     // changed the tag value
     assert(Thread::current()->is_VM_thread(), "must be VMThread");
-    hashmap->add(o, obj_tag);
+    tag_map->add(o, obj_tag);
   }
 }
 
 // An extended CallbackWrapper used when reporting an object reference
 // to the agent.
@@ -269,48 +504,43 @@
 // checking to see if the referrer object has been tagged, untagged,
 // or the tag value has changed.
 //
 class TwoOopCallbackWrapper : public CallbackWrapper {
  private:
+  const JvmtiHeapwalkObject& _referrer;
   bool _is_reference_to_self;
-  JvmtiTagMapTable* _referrer_hashmap;
-  oop _referrer;
   jlong _referrer_obj_tag;
   jlong _referrer_klass_tag;
   jlong* _referrer_tag_p;
 
   bool is_reference_to_self() const             { return _is_reference_to_self; }
 
  public:
-  TwoOopCallbackWrapper(JvmtiTagMap* tag_map, oop referrer, oop o) :
-    CallbackWrapper(tag_map, o)
+  TwoOopCallbackWrapper(JvmtiTagMap* tag_map, const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& o) :
+    CallbackWrapper(tag_map, o), _referrer(referrer)
   {
     // self reference needs to be handled in a special way
     _is_reference_to_self = (referrer == o);
 
     if (_is_reference_to_self) {
       _referrer_klass_tag = klass_tag();
       _referrer_tag_p = obj_tag_p();
     } else {
-      _referrer = referrer;
-      // record the context
-      _referrer_hashmap = tag_map->hashmap();
-
       // get object tag
-      _referrer_obj_tag = _referrer_hashmap->find(_referrer);
+      _referrer_obj_tag = tag_map->find(_referrer);
 
       _referrer_tag_p = &_referrer_obj_tag;
 
       // get referrer class tag.
-      _referrer_klass_tag = tag_for(tag_map, _referrer->klass()->java_mirror());
+      _referrer_klass_tag = tag_map->find(_referrer.klass()->java_mirror());
     }
   }
 
   ~TwoOopCallbackWrapper() {
     if (!is_reference_to_self()) {
       post_callback_tag_update(_referrer,
-                               _referrer_hashmap,
+                               tag_map(),
                                _referrer_obj_tag);
     }
   }
 
   // address of referrer tag
@@ -334,21 +564,19 @@
   // safepoints with the hashmap lock held.
   check_hashmap(nullptr);  /* don't collect dead objects */
 
   // resolve the object
   oop o = JNIHandles::resolve_non_null(object);
-
   // see if the object is already tagged
-  JvmtiTagMapTable* hashmap = _hashmap;
-
+  JvmtiHeapwalkObject obj(o);
   if (tag == 0) {
     // remove the entry if present
-    hashmap->remove(o);
+    _hashmap->remove(obj);
   } else {
     // if the object is already tagged or not present then we add/update
     // the tag
-    hashmap->add(o, tag);
+    add(obj, tag);
   }
 }
 
 // get the tag for an object
 jlong JvmtiTagMap::get_tag(jobject object) {
@@ -360,11 +588,11 @@
   check_hashmap(nullptr); /* don't collect dead objects */
 
   // resolve the object
   oop o = JNIHandles::resolve_non_null(object);
 
-  return tag_for(this, o);
+  return find(o);
 }
 
 
 // Helper class used to describe the static or instance fields of a class.
 // For each field it holds the field index (as defined by the JVMTI specification),
@@ -373,17 +601,32 @@
 class ClassFieldDescriptor: public CHeapObj<mtInternal> {
  private:
   int _field_index;
   int _field_offset;
   char _field_type;
+  InlineKlass* _inline_klass; // nullptr for heap object
+  LayoutKind _layout_kind;
  public:
-  ClassFieldDescriptor(int index, char type, int offset) :
-    _field_index(index), _field_offset(offset), _field_type(type) {
+  ClassFieldDescriptor(int index, const FieldStreamBase& fld) :
+      _field_index(index), _field_offset(fld.offset()), _field_type(fld.signature()->char_at(0)) {
+    if (fld.is_flat()) {
+      const fieldDescriptor& fd = fld.field_descriptor();
+      InstanceKlass* holder_klass = fd.field_holder();
+      InlineLayoutInfo* layout_info = holder_klass->inline_layout_info_adr(fd.index());
+      _inline_klass = layout_info->klass();
+      _layout_kind = layout_info->kind();
+    } else {
+      _inline_klass = nullptr;
+      _layout_kind = LayoutKind::REFERENCE;
+    }
   }
   int field_index()  const  { return _field_index; }
   char field_type()  const  { return _field_type; }
   int field_offset() const  { return _field_offset; }
+  bool is_flat()     const  { return _inline_klass != nullptr; }
+  InlineKlass* inline_klass() const { return _inline_klass; }
+  LayoutKind layout_kind() const { return _layout_kind; }
 };
 
 class ClassFieldMap: public CHeapObj<mtInternal> {
  private:
   enum {
@@ -398,22 +641,22 @@
 
   // calculates number of fields in all interfaces
   static int interfaces_field_count(InstanceKlass* ik);
 
   // add a field
-  void add(int index, char type, int offset);
+  void add(int index, const FieldStreamBase& fld);
 
  public:
   ~ClassFieldMap();
 
   // access
   int field_count()                     { return _fields->length(); }
   ClassFieldDescriptor* field_at(int i) { return _fields->at(i); }
 
   // functions to create maps of static or instance fields
   static ClassFieldMap* create_map_of_static_fields(Klass* k);
-  static ClassFieldMap* create_map_of_instance_fields(oop obj);
+  static ClassFieldMap* create_map_of_instance_fields(Klass* k);
 };
 
 ClassFieldMap::ClassFieldMap() {
   _fields = new (mtServiceability)
     GrowableArray<ClassFieldDescriptor*>(initial_field_count, mtServiceability);
@@ -434,12 +677,12 @@
     count += fld.field_count();
   }
   return count;
 }
 
-void ClassFieldMap::add(int index, char type, int offset) {
-  ClassFieldDescriptor* field = new ClassFieldDescriptor(index, type, offset);
+void ClassFieldMap::add(int index, const FieldStreamBase& fld) {
+  ClassFieldDescriptor* field = new ClassFieldDescriptor(index, fld);
   _fields->append(field);
 }
 
 // Returns a heap allocated ClassFieldMap to describe the static fields
 // of the given class.
@@ -460,21 +703,21 @@
   for (FilteredJavaFieldStream fld(ik); !fld.done(); fld.next(), index++) {
     // ignore instance fields
     if (!fld.access_flags().is_static()) {
       continue;
     }
-    field_map->add(index, fld.signature()->char_at(0), fld.offset());
+    field_map->add(index, fld);
   }
 
   return field_map;
 }
 
 // Returns a heap allocated ClassFieldMap to describe the instance fields
 // of the given class. All instance fields are included (this means public
 // and private fields declared in superclasses too).
-ClassFieldMap* ClassFieldMap::create_map_of_instance_fields(oop obj) {
-  InstanceKlass* ik = InstanceKlass::cast(obj->klass());
+ClassFieldMap* ClassFieldMap::create_map_of_instance_fields(Klass* k) {
+  InstanceKlass* ik = InstanceKlass::cast(k);
 
   // create the field map
   ClassFieldMap* field_map = new ClassFieldMap();
 
   // fields of the superclasses are reported first, so need to know total field number to calculate field indices
@@ -490,11 +733,11 @@
     for (int index = 0; !fld.done(); fld.next(), index++) {
       // ignore static fields
       if (fld.access_flags().is_static()) {
         continue;
       }
-      field_map->add(start_index + index, fld.signature()->char_at(0), fld.offset());
+      field_map->add(start_index + index, fld);
     }
     // update total_field_number for superclass (decrease by the field count in the current class)
     total_field_number = start_index;
   }
 
@@ -520,13 +763,13 @@
 
   static GrowableArray<InstanceKlass*>* _class_list;
   static void add_to_class_list(InstanceKlass* ik);
 
  public:
-  // returns the field map for a given object (returning map cached
+  // returns the field map for a given klass (returning map cached
   // by InstanceKlass if possible
-  static ClassFieldMap* get_map_of_instance_fields(oop obj);
+  static ClassFieldMap* get_map_of_instance_fields(Klass* k);
 
   // removes the field map from all instanceKlasses - should be
   // called before VM operation completes
   static void clear_cache();
 
@@ -574,26 +817,25 @@
       GrowableArray<InstanceKlass*>(initial_class_count, mtServiceability);
   }
   _class_list->push(ik);
 }
 
-// returns the instance field map for the given object
+// returns the instance field map for the given klass
 // (returns field map cached by the InstanceKlass if possible)
-ClassFieldMap* JvmtiCachedClassFieldMap::get_map_of_instance_fields(oop obj) {
+ClassFieldMap* JvmtiCachedClassFieldMap::get_map_of_instance_fields(Klass *k) {
   assert(Thread::current()->is_VM_thread(), "must be VMThread");
   assert(ClassFieldMapCacheMark::is_active(), "ClassFieldMapCacheMark not active");
 
-  Klass* k = obj->klass();
   InstanceKlass* ik = InstanceKlass::cast(k);
 
   // return cached map if possible
   JvmtiCachedClassFieldMap* cached_map = ik->jvmti_cached_class_field_map();
   if (cached_map != nullptr) {
     assert(cached_map->field_map() != nullptr, "missing field list");
     return cached_map->field_map();
   } else {
-    ClassFieldMap* field_map = ClassFieldMap::create_map_of_instance_fields(obj);
+    ClassFieldMap* field_map = ClassFieldMap::create_map_of_instance_fields(k);
     cached_map = new JvmtiCachedClassFieldMap(field_map);
     ik->set_jvmti_cached_class_field_map(cached_map);
     add_to_class_list(ik);
     return field_map;
   }
@@ -641,13 +883,13 @@
   }
   return false;
 }
 
 // helper function to indicate if an object is filtered by a klass filter
-static inline bool is_filtered_by_klass_filter(oop obj, Klass* klass_filter) {
+static inline bool is_filtered_by_klass_filter(const JvmtiHeapwalkObject& obj, Klass* klass_filter) {
   if (klass_filter != nullptr) {
-    if (obj->klass() != klass_filter) {
+    if (obj.klass() != klass_filter) {
       return true;
     }
   }
   return false;
 }
@@ -674,13 +916,15 @@
 
 // helper function to invoke string primitive value callback
 // returns visit control flags
 static jint invoke_string_value_callback(jvmtiStringPrimitiveValueCallback cb,
                                          CallbackWrapper* wrapper,
-                                         oop str,
+                                         const JvmtiHeapwalkObject& obj,
                                          void* user_data)
 {
+  assert(!obj.is_flat(), "cannot be flat");
+  oop str = obj.obj();
   assert(str->klass() == vmClasses::String_klass(), "not a string");
 
   typeArrayOop s_value = java_lang_String::value(str);
 
   // JDK-6584008: the value field may be null if a String instance is
@@ -725,17 +969,18 @@
 
 // helper function to invoke string primitive value callback
 // returns visit control flags
 static jint invoke_array_primitive_value_callback(jvmtiArrayPrimitiveValueCallback cb,
                                                   CallbackWrapper* wrapper,
-                                                  oop obj,
+                                                  const JvmtiHeapwalkObject& obj,
                                                   void* user_data)
 {
-  assert(obj->is_typeArray(), "not a primitive array");
+  assert(!obj.is_flat(), "cannot be flat");
+  assert(obj.obj()->is_typeArray(), "not a primitive array");
 
   // get base address of first element
-  typeArrayOop array = typeArrayOop(obj);
+  typeArrayOop array = typeArrayOop(obj.obj());
   BasicType type = TypeArrayKlass::cast(array->klass())->element_type();
   void* elements = array->base(type);
 
   // jvmtiPrimitiveType is defined so this mapping is always correct
   jvmtiPrimitiveType elem_type = (jvmtiPrimitiveType)type2char(type);
@@ -821,19 +1066,19 @@
 
 // helper function to invoke the primitive field callback for all instance fields
 // of a given object
 static jint invoke_primitive_field_callback_for_instance_fields(
   CallbackWrapper* wrapper,
-  oop obj,
+  const JvmtiHeapwalkObject& obj,
   jvmtiPrimitiveFieldCallback cb,
   void* user_data)
 {
   // for instance fields only the index will be set
   static jvmtiHeapReferenceInfo reference_info = { 0 };
 
   // get the map of the instance fields
-  ClassFieldMap* fields = JvmtiCachedClassFieldMap::get_map_of_instance_fields(obj);
+  ClassFieldMap* fields = JvmtiCachedClassFieldMap::get_map_of_instance_fields(obj.klass());
 
   // invoke the callback for each instance primitive field
   for (int i=0; i<fields->field_count(); i++) {
     ClassFieldDescriptor* field = fields->field_at(i);
 
@@ -843,13 +1088,12 @@
       continue;
     }
     // one-to-one mapping
     jvmtiPrimitiveType value_type = (jvmtiPrimitiveType)type;
 
-    // get offset and field value
-    int offset = field->field_offset();
-    address addr = cast_from_oop<address>(obj) + offset;
+    // get field value
+    address addr = cast_from_oop<address>(obj.obj()) + obj.offset() + field->field_offset();
     jvalue value;
     copy_to_jvalue(&value, addr, value_type);
 
     // field index
     reference_info.field.index = field->field_index();
@@ -958,11 +1202,12 @@
                          o->klass()->external_name());
     return;
   }
 
   // prepare for the calllback
-  CallbackWrapper wrapper(tag_map(), o);
+  JvmtiHeapwalkObject wrapper_obj(o);
+  CallbackWrapper wrapper(tag_map(), wrapper_obj);
 
   // if the object is tagged and we're only interested in untagged objects
   // then don't invoke the callback. Similarly, if the object is untagged
   // and we're only interested in tagged objects we skip the callback.
   if (wrapper.obj_tag() != 0) {
@@ -1010,10 +1255,14 @@
       _iteration_aborted = true;
     }
     return is_abort;
   }
 
+  void visit_object(const JvmtiHeapwalkObject& obj);
+  void visit_flat_fields(const JvmtiHeapwalkObject& obj);
+  void visit_flat_array_elements(const JvmtiHeapwalkObject& obj);
+
  public:
   IterateThroughHeapObjectClosure(JvmtiTagMap* tag_map,
                                   Klass* klass,
                                   int heap_filter,
                                   const jvmtiHeapCallbacks* heap_callbacks,
@@ -1025,39 +1274,43 @@
     _user_data(user_data),
     _iteration_aborted(false)
   {
   }
 
-  void do_object(oop o);
+  void do_object(oop obj);
 };
 
 // invoked for each object in the heap
 void IterateThroughHeapObjectClosure::do_object(oop obj) {
   // check if iteration has been halted
   if (is_iteration_aborted()) return;
 
-  // apply class filter
-  if (is_filtered_by_klass_filter(obj, klass())) return;
-
   // skip if object is a dormant shared object whose mirror hasn't been loaded
-  if (obj != nullptr &&   obj->klass()->java_mirror() == nullptr) {
+  if (obj != nullptr && obj->klass()->java_mirror() == nullptr) {
     log_debug(aot, heap)("skipped dormant archived object " INTPTR_FORMAT " (%s)", p2i(obj),
                          obj->klass()->external_name());
     return;
   }
 
+  visit_object(obj);
+}
+
+void IterateThroughHeapObjectClosure::visit_object(const JvmtiHeapwalkObject& obj) {
+  // apply class filter
+  if (is_filtered_by_klass_filter(obj, klass())) return;
+
   // prepare for callback
   CallbackWrapper wrapper(tag_map(), obj);
 
   // check if filtered by the heap filter
   if (is_filtered_by_heap_filter(wrapper.obj_tag(), wrapper.klass_tag(), heap_filter())) {
     return;
   }
 
   // for arrays we need the length, otherwise -1
-  bool is_array = obj->is_array();
-  int len = is_array ? arrayOop(obj)->length() : -1;
+  bool is_array = obj.klass()->is_array_klass();
+  int len = is_array ? arrayOop(obj.obj())->length() : -1;
 
   // invoke the object callback (if callback is provided)
   if (callbacks()->heap_iteration_callback != nullptr) {
     jvmtiHeapIterationCallback cb = callbacks()->heap_iteration_callback;
     jint res = (*cb)(wrapper.klass_tag(),
@@ -1067,52 +1320,133 @@
                      (void*)user_data());
     if (check_flags_for_abort(res)) return;
   }
 
   // for objects and classes we report primitive fields if callback provided
-  if (callbacks()->primitive_field_callback != nullptr && obj->is_instance()) {
+  if (callbacks()->primitive_field_callback != nullptr && obj.klass()->is_instance_klass()) {
     jint res;
     jvmtiPrimitiveFieldCallback cb = callbacks()->primitive_field_callback;
-    if (obj->klass() == vmClasses::Class_klass()) {
+    if (obj.klass() == vmClasses::Class_klass()) {
+      assert(!obj.is_flat(), "Class object cannot be flattened");
       res = invoke_primitive_field_callback_for_static_fields(&wrapper,
-                                                                    obj,
-                                                                    cb,
-                                                                    (void*)user_data());
+                                                              obj.obj(),
+                                                              cb,
+                                                              (void*)user_data());
     } else {
       res = invoke_primitive_field_callback_for_instance_fields(&wrapper,
-                                                                      obj,
-                                                                      cb,
-                                                                      (void*)user_data());
+                                                                obj,
+                                                                cb,
+                                                                (void*)user_data());
     }
     if (check_flags_for_abort(res)) return;
   }
 
   // string callback
   if (!is_array &&
       callbacks()->string_primitive_value_callback != nullptr &&
-      obj->klass() == vmClasses::String_klass()) {
+      obj.klass() == vmClasses::String_klass()) {
     jint res = invoke_string_value_callback(
                 callbacks()->string_primitive_value_callback,
                 &wrapper,
                 obj,
-                (void*)user_data() );
+                (void*)user_data());
     if (check_flags_for_abort(res)) return;
   }
 
   // array callback
   if (is_array &&
       callbacks()->array_primitive_value_callback != nullptr &&
-      obj->is_typeArray()) {
+      obj.klass()->is_typeArray_klass()) {
     jint res = invoke_array_primitive_value_callback(
                callbacks()->array_primitive_value_callback,
                &wrapper,
                obj,
-               (void*)user_data() );
+               (void*)user_data());
     if (check_flags_for_abort(res)) return;
   }
-};
 
+  // All info for the object is reported.
+
+  // If the object has flat fields, report them as heap objects.
+  if (obj.klass()->is_instance_klass()) {
+    if (InstanceKlass::cast(obj.klass())->has_inline_type_fields()) {
+      visit_flat_fields(obj);
+      // check if iteration has been halted
+      if (is_iteration_aborted()) {
+        return;
+      }
+    }
+  }
+  // If the object is flat array, report all elements as heap objects.
+  if (is_array && obj.obj()->is_flatArray()) {
+    assert(!obj.is_flat(), "Array object cannot be flattened");
+    visit_flat_array_elements(obj);
+  }
+}
+
+void IterateThroughHeapObjectClosure::visit_flat_fields(const JvmtiHeapwalkObject& obj) {
+  // iterate over instance fields
+  ClassFieldMap* fields = JvmtiCachedClassFieldMap::get_map_of_instance_fields(obj.klass());
+  for (int i = 0; i < fields->field_count(); i++) {
+    ClassFieldDescriptor* field = fields->field_at(i);
+    // skip non-flat and (for safety) primitive fields
+    if (!field->is_flat() || is_primitive_field_type(field->field_type())) {
+      continue;
+    }
+
+    int field_offset = field->field_offset();
+    if (obj.is_flat()) {
+      // the object is inlined, its fields are stored without the header
+      field_offset += obj.offset() - obj.inline_klass()->payload_offset();
+    }
+    // check for possible nulls
+    bool can_be_null = field->layout_kind() == LayoutKind::NULLABLE_ATOMIC_FLAT;
+    if (can_be_null) {
+      address payload = cast_from_oop<address>(obj.obj()) + field_offset;
+      if (field->inline_klass()->is_payload_marked_as_null(payload)) {
+        continue;
+      }
+    }
+    JvmtiHeapwalkObject field_obj(obj.obj(), field_offset, field->inline_klass(), field->layout_kind());
+
+    visit_object(field_obj);
+
+    // check if iteration has been halted
+    if (is_iteration_aborted()) {
+      return;
+    }
+  }
+}
+
+void IterateThroughHeapObjectClosure::visit_flat_array_elements(const JvmtiHeapwalkObject& obj) {
+  assert(!obj.is_flat() && obj.obj()->is_flatArray() , "sanity check");
+  flatArrayOop array = flatArrayOop(obj.obj());
+  FlatArrayKlass* faklass = FlatArrayKlass::cast(array->klass());
+  InlineKlass* vk = InlineKlass::cast(faklass->element_klass());
+  bool need_null_check = faklass->layout_kind() == LayoutKind::NULLABLE_ATOMIC_FLAT;
+
+  for (int index = 0; index < array->length(); index++) {
+    address addr = (address)array->value_at_addr(index, faklass->layout_helper());
+    // check for null
+    if (need_null_check) {
+      if (vk->is_payload_marked_as_null(addr)) {
+        continue;
+      }
+    }
+
+    // offset in the array oop
+    int offset = (int)(addr - cast_from_oop<address>(array));
+    JvmtiHeapwalkObject elem(obj.obj(), offset, vk, faklass->layout_kind());
+
+    visit_object(elem);
+
+    // check if iteration has been halted
+    if (is_iteration_aborted()) {
+      return;
+    }
+  }
+}
 
 // Deprecated function to iterate over all objects in the heap
 void JvmtiTagMap::iterate_over_heap(jvmtiHeapObjectFilter object_filter,
                                     Klass* klass,
                                     jvmtiHeapObjectCallback heap_object_callback,
@@ -1133,10 +1467,12 @@
                                      heap_object_callback,
                                      user_data);
     VM_HeapIterateOperation op(&blk, &dead_objects);
     VMThread::execute(&op);
   }
+  convert_flat_object_entries();
+
   // Post events outside of Heap_lock
   post_dead_objects(&dead_objects);
 }
 
 
@@ -1160,10 +1496,12 @@
                                         callbacks,
                                         user_data);
     VM_HeapIterateOperation op(&blk, &dead_objects);
     VMThread::execute(&op);
   }
+  convert_flat_object_entries();
+
   // Post events outside of Heap_lock
   post_dead_objects(&dead_objects);
 }
 
 void JvmtiTagMap::remove_dead_entries_locked(GrowableArray<jlong>* objects) {
@@ -1173,11 +1511,11 @@
     if (!env()->is_enabled(JVMTI_EVENT_OBJECT_FREE)) {
       objects = nullptr;
     }
     log_info(jvmti, table)("TagMap table needs cleaning%s",
                            ((objects != nullptr) ? " and posting" : ""));
-    hashmap()->remove_dead_entries(objects);
+    _hashmap->remove_dead_entries(objects);
     _needs_cleaning = false;
   }
 }
 
 void JvmtiTagMap::remove_dead_entries(GrowableArray<jlong>* objects) {
@@ -1326,19 +1664,22 @@
 
 // return the list of objects with the specified tags
 jvmtiError JvmtiTagMap::get_objects_with_tags(const jlong* tags,
   jint count, jint* count_ptr, jobject** object_result_ptr, jlong** tag_result_ptr) {
 
+  // ensure flat object conversion is completed
+  convert_flat_object_entries();
+
   TagObjectCollector collector(env(), tags, count);
   {
     // iterate over all tagged objects
     MutexLocker ml(lock(), Mutex::_no_safepoint_check_flag);
     // Can't post ObjectFree events here from a JavaThread, so this
     // will race with the gc_notification thread in the tiny
     // window where the object is not marked but hasn't been notified that
     // it is collected yet.
-    entry_iterate(&collector);
+    _hashmap->entry_iterate(&collector);
   }
   return collector.result(count_ptr, object_result_ptr, tag_result_ptr);
 }
 
 // helper to map a jvmtiHeapReferenceKind to an old style jvmtiHeapRootKind
@@ -1374,11 +1715,11 @@
   jvmtiHeapRootCallback _heap_root_callback;
   jvmtiStackReferenceCallback _stack_ref_callback;
   jvmtiObjectReferenceCallback _object_ref_callback;
 
   // used for caching
-  oop _last_referrer;
+  JvmtiHeapwalkObject _last_referrer;
   jlong _last_referrer_tag;
 
  public:
   BasicHeapWalkContext() : HeapWalkContext(false) { }
 
@@ -1387,21 +1728,21 @@
                        jvmtiObjectReferenceCallback object_ref_callback) :
     HeapWalkContext(true),
     _heap_root_callback(heap_root_callback),
     _stack_ref_callback(stack_ref_callback),
     _object_ref_callback(object_ref_callback),
-    _last_referrer(nullptr),
+    _last_referrer(),
     _last_referrer_tag(0) {
   }
 
   // accessors
   jvmtiHeapRootCallback heap_root_callback() const         { return _heap_root_callback; }
   jvmtiStackReferenceCallback stack_ref_callback() const   { return _stack_ref_callback; }
   jvmtiObjectReferenceCallback object_ref_callback() const { return _object_ref_callback;  }
 
-  oop last_referrer() const               { return _last_referrer; }
-  void set_last_referrer(oop referrer)    { _last_referrer = referrer; }
+  JvmtiHeapwalkObject last_referrer() const    { return _last_referrer; }
+  void set_last_referrer(const JvmtiHeapwalkObject& referrer) { _last_referrer = referrer; }
   jlong last_referrer_tag() const         { return _last_referrer_tag; }
   void set_last_referrer_tag(jlong value) { _last_referrer_tag = value; }
 };
 
 // The advanced heap walk context for the FollowReferences functions.
@@ -1470,128 +1811,130 @@
   }
 
   // context needed for all heap walks
   static JvmtiTagMap* _tag_map;
   static const void* _user_data;
-  static GrowableArray<oop>* _visit_stack;
-  static JVMTIBitSet* _bitset;
+  static JvmtiHeapwalkVisitStack* _visit_stack;
 
   // accessors
   static JvmtiTagMap* tag_map()                        { return _tag_map; }
   static const void* user_data()                       { return _user_data; }
-  static GrowableArray<oop>* visit_stack()             { return _visit_stack; }
+  static JvmtiHeapwalkVisitStack* visit_stack()        { return _visit_stack; }
 
   // if the object hasn't been visited then push it onto the visit stack
   // so that it will be visited later
-  static inline bool check_for_visit(oop obj) {
-    if (!_bitset->is_marked(obj)) visit_stack()->push(obj);
+  static inline bool check_for_visit(const JvmtiHeapwalkObject&obj) {
+    visit_stack()->check_for_visit(obj);
     return true;
   }
 
+  // return element count if the obj is array, -1 otherwise
+  static jint get_array_length(const JvmtiHeapwalkObject& obj) {
+    if (!obj.klass()->is_array_klass()) {
+      return -1;
+    }
+    assert(!obj.is_flat(), "array cannot be flat");
+    return (jint)arrayOop(obj.obj())->length();
+  }
+
+
   // invoke basic style callbacks
   static inline bool invoke_basic_heap_root_callback
-    (jvmtiHeapRootKind root_kind, oop obj);
+    (jvmtiHeapRootKind root_kind, const JvmtiHeapwalkObject& obj);
   static inline bool invoke_basic_stack_ref_callback
     (jvmtiHeapRootKind root_kind, jlong thread_tag, jint depth, jmethodID method,
-     int slot, oop obj);
+     int slot, const JvmtiHeapwalkObject& obj);
   static inline bool invoke_basic_object_reference_callback
-    (jvmtiObjectReferenceKind ref_kind, oop referrer, oop referree, jint index);
+    (jvmtiObjectReferenceKind ref_kind, const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint index);
 
   // invoke advanced style callbacks
   static inline bool invoke_advanced_heap_root_callback
-    (jvmtiHeapReferenceKind ref_kind, oop obj);
+    (jvmtiHeapReferenceKind ref_kind, const JvmtiHeapwalkObject& obj);
   static inline bool invoke_advanced_stack_ref_callback
     (jvmtiHeapReferenceKind ref_kind, jlong thread_tag, jlong tid, int depth,
-     jmethodID method, jlocation bci, jint slot, oop obj);
+     jmethodID method, jlocation bci, jint slot, const JvmtiHeapwalkObject& obj);
   static inline bool invoke_advanced_object_reference_callback
-    (jvmtiHeapReferenceKind ref_kind, oop referrer, oop referree, jint index);
+    (jvmtiHeapReferenceKind ref_kind, const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint index);
 
   // used to report the value of primitive fields
   static inline bool report_primitive_field
-    (jvmtiHeapReferenceKind ref_kind, oop obj, jint index, address addr, char type);
+    (jvmtiHeapReferenceKind ref_kind, const JvmtiHeapwalkObject& obj, jint index, address addr, char type);
 
  public:
   // initialize for basic mode
   static void initialize_for_basic_heap_walk(JvmtiTagMap* tag_map,
-                                             GrowableArray<oop>* visit_stack,
                                              const void* user_data,
                                              BasicHeapWalkContext context,
-                                             JVMTIBitSet* bitset);
+                                             JvmtiHeapwalkVisitStack* visit_stack);
 
   // initialize for advanced mode
   static void initialize_for_advanced_heap_walk(JvmtiTagMap* tag_map,
-                                                GrowableArray<oop>* visit_stack,
                                                 const void* user_data,
                                                 AdvancedHeapWalkContext context,
-                                                JVMTIBitSet* bitset);
+                                                JvmtiHeapwalkVisitStack* visit_stack);
 
    // functions to report roots
-  static inline bool report_simple_root(jvmtiHeapReferenceKind kind, oop o);
+  static inline bool report_simple_root(jvmtiHeapReferenceKind kind, const JvmtiHeapwalkObject& o);
   static inline bool report_jni_local_root(jlong thread_tag, jlong tid, jint depth,
-    jmethodID m, oop o);
+    jmethodID m, const JvmtiHeapwalkObject& o);
   static inline bool report_stack_ref_root(jlong thread_tag, jlong tid, jint depth,
-    jmethodID method, jlocation bci, jint slot, oop o);
+    jmethodID method, jlocation bci, jint slot, const JvmtiHeapwalkObject& o);
 
   // functions to report references
-  static inline bool report_array_element_reference(oop referrer, oop referree, jint index);
-  static inline bool report_class_reference(oop referrer, oop referree);
-  static inline bool report_class_loader_reference(oop referrer, oop referree);
-  static inline bool report_signers_reference(oop referrer, oop referree);
-  static inline bool report_protection_domain_reference(oop referrer, oop referree);
-  static inline bool report_superclass_reference(oop referrer, oop referree);
-  static inline bool report_interface_reference(oop referrer, oop referree);
-  static inline bool report_static_field_reference(oop referrer, oop referree, jint slot);
-  static inline bool report_field_reference(oop referrer, oop referree, jint slot);
-  static inline bool report_constant_pool_reference(oop referrer, oop referree, jint index);
-  static inline bool report_primitive_array_values(oop array);
-  static inline bool report_string_value(oop str);
-  static inline bool report_primitive_instance_field(oop o, jint index, address value, char type);
-  static inline bool report_primitive_static_field(oop o, jint index, address value, char type);
+  static inline bool report_array_element_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint index);
+  static inline bool report_class_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree);
+  static inline bool report_class_loader_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree);
+  static inline bool report_signers_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree);
+  static inline bool report_protection_domain_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree);
+  static inline bool report_superclass_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree);
+  static inline bool report_interface_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree);
+  static inline bool report_static_field_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint slot);
+  static inline bool report_field_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint slot);
+  static inline bool report_constant_pool_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint index);
+  static inline bool report_primitive_array_values(const JvmtiHeapwalkObject& array);
+  static inline bool report_string_value(const JvmtiHeapwalkObject& str);
+  static inline bool report_primitive_instance_field(const JvmtiHeapwalkObject& o, jint index, address value, char type);
+  static inline bool report_primitive_static_field(const JvmtiHeapwalkObject& o, jint index, address value, char type);
 };
 
 // statics
 int CallbackInvoker::_heap_walk_type;
 BasicHeapWalkContext CallbackInvoker::_basic_context;
 AdvancedHeapWalkContext CallbackInvoker::_advanced_context;
 JvmtiTagMap* CallbackInvoker::_tag_map;
 const void* CallbackInvoker::_user_data;
-GrowableArray<oop>* CallbackInvoker::_visit_stack;
-JVMTIBitSet* CallbackInvoker::_bitset;
+JvmtiHeapwalkVisitStack* CallbackInvoker::_visit_stack;
 
 // initialize for basic heap walk (IterateOverReachableObjects et al)
 void CallbackInvoker::initialize_for_basic_heap_walk(JvmtiTagMap* tag_map,
-                                                     GrowableArray<oop>* visit_stack,
                                                      const void* user_data,
                                                      BasicHeapWalkContext context,
-                                                     JVMTIBitSet* bitset) {
+                                                     JvmtiHeapwalkVisitStack* visit_stack) {
   _tag_map = tag_map;
-  _visit_stack = visit_stack;
   _user_data = user_data;
   _basic_context = context;
   _advanced_context.invalidate();       // will trigger assertion if used
   _heap_walk_type = basic;
-  _bitset = bitset;
+  _visit_stack = visit_stack;
 }
 
 // initialize for advanced heap walk (FollowReferences)
 void CallbackInvoker::initialize_for_advanced_heap_walk(JvmtiTagMap* tag_map,
-                                                        GrowableArray<oop>* visit_stack,
                                                         const void* user_data,
                                                         AdvancedHeapWalkContext context,
-                                                        JVMTIBitSet* bitset) {
+                                                        JvmtiHeapwalkVisitStack* visit_stack) {
   _tag_map = tag_map;
-  _visit_stack = visit_stack;
   _user_data = user_data;
   _advanced_context = context;
   _basic_context.invalidate();      // will trigger assertion if used
   _heap_walk_type = advanced;
-  _bitset = bitset;
+  _visit_stack = visit_stack;
 }
 
 
 // invoke basic style heap root callback
-inline bool CallbackInvoker::invoke_basic_heap_root_callback(jvmtiHeapRootKind root_kind, oop obj) {
+inline bool CallbackInvoker::invoke_basic_heap_root_callback(jvmtiHeapRootKind root_kind, const JvmtiHeapwalkObject& obj) {
   // if we heap roots should be reported
   jvmtiHeapRootCallback cb = basic_context()->heap_root_callback();
   if (cb == nullptr) {
     return check_for_visit(obj);
   }
@@ -1614,11 +1957,11 @@
 inline bool CallbackInvoker::invoke_basic_stack_ref_callback(jvmtiHeapRootKind root_kind,
                                                              jlong thread_tag,
                                                              jint depth,
                                                              jmethodID method,
                                                              int slot,
-                                                             oop obj) {
+                                                             const JvmtiHeapwalkObject& obj) {
   // if we stack refs should be reported
   jvmtiStackReferenceCallback cb = basic_context()->stack_ref_callback();
   if (cb == nullptr) {
     return check_for_visit(obj);
   }
@@ -1641,23 +1984,23 @@
   return control != JVMTI_ITERATION_ABORT;
 }
 
 // invoke basic style object reference callback
 inline bool CallbackInvoker::invoke_basic_object_reference_callback(jvmtiObjectReferenceKind ref_kind,
-                                                                    oop referrer,
-                                                                    oop referree,
+                                                                    const JvmtiHeapwalkObject& referrer,
+                                                                    const JvmtiHeapwalkObject& referree,
                                                                     jint index) {
 
   BasicHeapWalkContext* context = basic_context();
 
   // callback requires the referrer's tag. If it's the same referrer
   // as the last call then we use the cached value.
   jlong referrer_tag;
   if (referrer == context->last_referrer()) {
     referrer_tag = context->last_referrer_tag();
   } else {
-    referrer_tag = tag_for(tag_map(), referrer);
+    referrer_tag = tag_map()->find(referrer);
   }
 
   // do the callback
   CallbackWrapper wrapper(tag_map(), referree);
   jvmtiObjectReferenceCallback cb = context->object_ref_callback();
@@ -1685,11 +2028,11 @@
   }
 }
 
 // invoke advanced style heap root callback
 inline bool CallbackInvoker::invoke_advanced_heap_root_callback(jvmtiHeapReferenceKind ref_kind,
-                                                                oop obj) {
+                                                                const JvmtiHeapwalkObject& obj) {
   AdvancedHeapWalkContext* context = advanced_context();
 
   // check that callback is provided
   jvmtiHeapReferenceCallback cb = context->heap_reference_callback();
   if (cb == nullptr) {
@@ -1710,11 +2053,11 @@
                                  context->heap_filter())) {
     return check_for_visit(obj);
   }
 
   // for arrays we need the length, otherwise -1
-  jint len = (jint)(obj->is_array() ? arrayOop(obj)->length() : -1);
+  jint len = get_array_length(obj);
 
   // invoke the callback
   jint res  = (*cb)(ref_kind,
                     nullptr, // referrer info
                     wrapper.klass_tag(),
@@ -1739,11 +2082,11 @@
                                                                 jlong tid,
                                                                 int depth,
                                                                 jmethodID method,
                                                                 jlocation bci,
                                                                 jint slot,
-                                                                oop obj) {
+                                                                const JvmtiHeapwalkObject& obj) {
   AdvancedHeapWalkContext* context = advanced_context();
 
   // check that callback is provider
   jvmtiHeapReferenceCallback cb = context->heap_reference_callback();
   if (cb == nullptr) {
@@ -1773,11 +2116,11 @@
   reference_info.stack_local.method = method;
   reference_info.stack_local.location = bci;
   reference_info.stack_local.slot = slot;
 
   // for arrays we need the length, otherwise -1
-  jint len = (jint)(obj->is_array() ? arrayOop(obj)->length() : -1);
+  jint len = get_array_length(obj);
 
   // call into the agent
   int res = (*cb)(ref_kind,
                   &reference_info,
                   wrapper.klass_tag(),
@@ -1806,12 +2149,12 @@
                       | (1 << JVMTI_HEAP_REFERENCE_STACK_LOCAL)   \
                       | (1 << JVMTI_HEAP_REFERENCE_JNI_LOCAL))
 
 // invoke the object reference callback to report a reference
 inline bool CallbackInvoker::invoke_advanced_object_reference_callback(jvmtiHeapReferenceKind ref_kind,
-                                                                       oop referrer,
-                                                                       oop obj,
+                                                                       const JvmtiHeapwalkObject& referrer,
+                                                                       const JvmtiHeapwalkObject& obj,
                                                                        jint index)
 {
   // field index is only valid field in reference_info
   static jvmtiHeapReferenceInfo reference_info = { 0 };
 
@@ -1840,11 +2183,11 @@
 
   // field index is only valid field in reference_info
   reference_info.field.index = index;
 
   // for arrays we need the length, otherwise -1
-  jint len = (jint)(obj->is_array() ? arrayOop(obj)->length() : -1);
+  jint len = get_array_length(obj);
 
   // invoke the callback
   int res = (*cb)(ref_kind,
                   (REF_INFO_MASK & (1 << ref_kind)) ? &reference_info : nullptr,
                   wrapper.klass_tag(),
@@ -1863,11 +2206,11 @@
   }
   return true;
 }
 
 // report a "simple root"
-inline bool CallbackInvoker::report_simple_root(jvmtiHeapReferenceKind kind, oop obj) {
+inline bool CallbackInvoker::report_simple_root(jvmtiHeapReferenceKind kind, const JvmtiHeapwalkObject& obj) {
   assert(kind != JVMTI_HEAP_REFERENCE_STACK_LOCAL &&
          kind != JVMTI_HEAP_REFERENCE_JNI_LOCAL, "not a simple root");
 
   if (is_basic_heap_walk()) {
     // map to old style root kind
@@ -1879,12 +2222,12 @@
   }
 }
 
 
 // invoke the primitive array values
-inline bool CallbackInvoker::report_primitive_array_values(oop obj) {
-  assert(obj->is_typeArray(), "not a primitive array");
+inline bool CallbackInvoker::report_primitive_array_values(const JvmtiHeapwalkObject& obj) {
+  assert(obj.klass()->is_typeArray_klass(), "not a primitive array");
 
   AdvancedHeapWalkContext* context = advanced_context();
   assert(context->array_primitive_value_callback() != nullptr, "no callback");
 
   // apply class filter
@@ -1908,12 +2251,12 @@
                                                   (void*)user_data());
   return (!(res & JVMTI_VISIT_ABORT));
 }
 
 // invoke the string value callback
-inline bool CallbackInvoker::report_string_value(oop str) {
-  assert(str->klass() == vmClasses::String_klass(), "not a string");
+inline bool CallbackInvoker::report_string_value(const JvmtiHeapwalkObject& str) {
+  assert(str.klass() == vmClasses::String_klass(), "not a string");
 
   AdvancedHeapWalkContext* context = advanced_context();
   assert(context->string_primitive_value_callback() != nullptr, "no callback");
 
   // apply class filter
@@ -1938,11 +2281,11 @@
   return (!(res & JVMTI_VISIT_ABORT));
 }
 
 // invoke the primitive field callback
 inline bool CallbackInvoker::report_primitive_field(jvmtiHeapReferenceKind ref_kind,
-                                                    oop obj,
+                                                    const JvmtiHeapwalkObject& obj,
                                                     jint index,
                                                     address addr,
                                                     char type)
 {
   // for primitive fields only the index will be set
@@ -1986,11 +2329,11 @@
   return (!(res & JVMTI_VISIT_ABORT));
 }
 
 
 // instance field
-inline bool CallbackInvoker::report_primitive_instance_field(oop obj,
+inline bool CallbackInvoker::report_primitive_instance_field(const JvmtiHeapwalkObject& obj,
                                                              jint index,
                                                              address value,
                                                              char type) {
   return report_primitive_field(JVMTI_HEAP_REFERENCE_FIELD,
                                 obj,
@@ -1998,11 +2341,11 @@
                                 value,
                                 type);
 }
 
 // static field
-inline bool CallbackInvoker::report_primitive_static_field(oop obj,
+inline bool CallbackInvoker::report_primitive_static_field(const JvmtiHeapwalkObject& obj,
                                                            jint index,
                                                            address value,
                                                            char type) {
   return report_primitive_field(JVMTI_HEAP_REFERENCE_STATIC_FIELD,
                                 obj,
@@ -2010,11 +2353,11 @@
                                 value,
                                 type);
 }
 
 // report a JNI local (root object) to the profiler
-inline bool CallbackInvoker::report_jni_local_root(jlong thread_tag, jlong tid, jint depth, jmethodID m, oop obj) {
+inline bool CallbackInvoker::report_jni_local_root(jlong thread_tag, jlong tid, jint depth, jmethodID m, const JvmtiHeapwalkObject& obj) {
   if (is_basic_heap_walk()) {
     return invoke_basic_stack_ref_callback(JVMTI_HEAP_ROOT_JNI_LOCAL,
                                            thread_tag,
                                            depth,
                                            m,
@@ -2037,11 +2380,11 @@
                                                    jlong tid,
                                                    jint depth,
                                                    jmethodID method,
                                                    jlocation bci,
                                                    jint slot,
-                                                   oop obj) {
+                                                   const JvmtiHeapwalkObject& obj) {
   if (is_basic_heap_walk()) {
     return invoke_basic_stack_ref_callback(JVMTI_HEAP_ROOT_STACK_LOCAL,
                                            thread_tag,
                                            depth,
                                            method,
@@ -2058,93 +2401,93 @@
                                               obj);
   }
 }
 
 // report an object referencing a class.
-inline bool CallbackInvoker::report_class_reference(oop referrer, oop referree) {
+inline bool CallbackInvoker::report_class_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree) {
   if (is_basic_heap_walk()) {
     return invoke_basic_object_reference_callback(JVMTI_REFERENCE_CLASS, referrer, referree, -1);
   } else {
     return invoke_advanced_object_reference_callback(JVMTI_HEAP_REFERENCE_CLASS, referrer, referree, -1);
   }
 }
 
 // report a class referencing its class loader.
-inline bool CallbackInvoker::report_class_loader_reference(oop referrer, oop referree) {
+inline bool CallbackInvoker::report_class_loader_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree) {
   if (is_basic_heap_walk()) {
     return invoke_basic_object_reference_callback(JVMTI_REFERENCE_CLASS_LOADER, referrer, referree, -1);
   } else {
     return invoke_advanced_object_reference_callback(JVMTI_HEAP_REFERENCE_CLASS_LOADER, referrer, referree, -1);
   }
 }
 
 // report a class referencing its signers.
-inline bool CallbackInvoker::report_signers_reference(oop referrer, oop referree) {
+inline bool CallbackInvoker::report_signers_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree) {
   if (is_basic_heap_walk()) {
     return invoke_basic_object_reference_callback(JVMTI_REFERENCE_SIGNERS, referrer, referree, -1);
   } else {
     return invoke_advanced_object_reference_callback(JVMTI_HEAP_REFERENCE_SIGNERS, referrer, referree, -1);
   }
 }
 
 // report a class referencing its protection domain..
-inline bool CallbackInvoker::report_protection_domain_reference(oop referrer, oop referree) {
+inline bool CallbackInvoker::report_protection_domain_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree) {
   if (is_basic_heap_walk()) {
     return invoke_basic_object_reference_callback(JVMTI_REFERENCE_PROTECTION_DOMAIN, referrer, referree, -1);
   } else {
     return invoke_advanced_object_reference_callback(JVMTI_HEAP_REFERENCE_PROTECTION_DOMAIN, referrer, referree, -1);
   }
 }
 
 // report a class referencing its superclass.
-inline bool CallbackInvoker::report_superclass_reference(oop referrer, oop referree) {
+inline bool CallbackInvoker::report_superclass_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree) {
   if (is_basic_heap_walk()) {
     // Send this to be consistent with past implementation
     return invoke_basic_object_reference_callback(JVMTI_REFERENCE_CLASS, referrer, referree, -1);
   } else {
     return invoke_advanced_object_reference_callback(JVMTI_HEAP_REFERENCE_SUPERCLASS, referrer, referree, -1);
   }
 }
 
 // report a class referencing one of its interfaces.
-inline bool CallbackInvoker::report_interface_reference(oop referrer, oop referree) {
+inline bool CallbackInvoker::report_interface_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree) {
   if (is_basic_heap_walk()) {
     return invoke_basic_object_reference_callback(JVMTI_REFERENCE_INTERFACE, referrer, referree, -1);
   } else {
     return invoke_advanced_object_reference_callback(JVMTI_HEAP_REFERENCE_INTERFACE, referrer, referree, -1);
   }
 }
 
 // report a class referencing one of its static fields.
-inline bool CallbackInvoker::report_static_field_reference(oop referrer, oop referree, jint slot) {
+inline bool CallbackInvoker::report_static_field_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint slot) {
   if (is_basic_heap_walk()) {
     return invoke_basic_object_reference_callback(JVMTI_REFERENCE_STATIC_FIELD, referrer, referree, slot);
   } else {
     return invoke_advanced_object_reference_callback(JVMTI_HEAP_REFERENCE_STATIC_FIELD, referrer, referree, slot);
   }
 }
 
 // report an array referencing an element object
-inline bool CallbackInvoker::report_array_element_reference(oop referrer, oop referree, jint index) {
+inline bool CallbackInvoker::report_array_element_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint index) {
   if (is_basic_heap_walk()) {
     return invoke_basic_object_reference_callback(JVMTI_REFERENCE_ARRAY_ELEMENT, referrer, referree, index);
   } else {
     return invoke_advanced_object_reference_callback(JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT, referrer, referree, index);
   }
 }
 
 // report an object referencing an instance field object
-inline bool CallbackInvoker::report_field_reference(oop referrer, oop referree, jint slot) {
+inline bool CallbackInvoker::report_field_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint slot) {
   if (is_basic_heap_walk()) {
     return invoke_basic_object_reference_callback(JVMTI_REFERENCE_FIELD, referrer, referree, slot);
   } else {
     return invoke_advanced_object_reference_callback(JVMTI_HEAP_REFERENCE_FIELD, referrer, referree, slot);
   }
 }
 
 // report an array referencing an element object
-inline bool CallbackInvoker::report_constant_pool_reference(oop referrer, oop referree, jint index) {
+inline bool CallbackInvoker::report_constant_pool_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint index) {
   if (is_basic_heap_walk()) {
     return invoke_basic_object_reference_callback(JVMTI_REFERENCE_CONSTANT_POOL, referrer, referree, index);
   } else {
     return invoke_advanced_object_reference_callback(JVMTI_HEAP_REFERENCE_CONSTANT_POOL, referrer, referree, index);
   }
@@ -2267,11 +2610,11 @@
   bool process_frames(vframe* vf);
 };
 
 bool StackRefCollector::set_thread(oop o) {
   _threadObj = o;
-  _thread_tag = tag_for(_tag_map, _threadObj);
+  _thread_tag = _tag_map->find(_threadObj);
   _tid = java_lang_Thread::thread_id(_threadObj);
 
   _is_top_frame = true;
   _depth = 0;
   _last_entry_frame = nullptr;
@@ -2400,34 +2743,24 @@
 //      on the thread's execution stack
 // - All visible/explainable objects from Universes::oops_do
 //
 class VM_HeapWalkOperation: public VM_Operation {
  private:
-  enum {
-    initial_visit_stack_size = 4000
-  };
-
   bool _is_advanced_heap_walk;                      // indicates FollowReferences
   JvmtiTagMap* _tag_map;
   Handle _initial_object;
-  GrowableArray<oop>* _visit_stack;                 // the visit stack
-
-  JVMTIBitSet _bitset;
+  JvmtiHeapwalkVisitStack _visit_stack;
 
   // Dead object tags in JvmtiTagMap
   GrowableArray<jlong>* _dead_objects;
 
   bool _following_object_refs;                      // are we following object references
 
   bool _reporting_primitive_fields;                 // optional reporting
   bool _reporting_primitive_array_values;
   bool _reporting_string_values;
 
-  GrowableArray<oop>* create_visit_stack() {
-    return new (mtServiceability) GrowableArray<oop>(initial_visit_stack_size, mtServiceability);
-  }
-
   // accessors
   bool is_advanced_heap_walk() const               { return _is_advanced_heap_walk; }
   JvmtiTagMap* tag_map() const                     { return _tag_map; }
   Handle initial_object() const                    { return _initial_object; }
 
@@ -2435,26 +2768,27 @@
 
   bool is_reporting_primitive_fields()  const      { return _reporting_primitive_fields; }
   bool is_reporting_primitive_array_values() const { return _reporting_primitive_array_values; }
   bool is_reporting_string_values() const          { return _reporting_string_values; }
 
-  GrowableArray<oop>* visit_stack() const          { return _visit_stack; }
+  JvmtiHeapwalkVisitStack* visit_stack()           { return &_visit_stack; }
 
   // iterate over the various object types
-  inline bool iterate_over_array(oop o);
-  inline bool iterate_over_type_array(oop o);
-  inline bool iterate_over_class(oop o);
-  inline bool iterate_over_object(oop o);
+  inline bool iterate_over_array(const JvmtiHeapwalkObject& o);
+  inline bool iterate_over_flat_array(const JvmtiHeapwalkObject& o);
+  inline bool iterate_over_type_array(const JvmtiHeapwalkObject& o);
+  inline bool iterate_over_class(const JvmtiHeapwalkObject& o);
+  inline bool iterate_over_object(const JvmtiHeapwalkObject& o);
 
   // root collection
   inline bool collect_simple_roots();
   inline bool collect_stack_roots();
   inline bool collect_stack_refs(JavaThread* java_thread, JNILocalRootsClosure* blk);
   inline bool collect_vthread_stack_refs(oop vt);
 
   // visit an object
-  inline bool visit(oop o);
+  inline bool visit(const JvmtiHeapwalkObject& o);
 
  public:
   VM_HeapWalkOperation(JvmtiTagMap* tag_map,
                        Handle initial_object,
                        BasicHeapWalkContext callbacks,
@@ -2484,14 +2818,12 @@
   _initial_object = initial_object;
   _following_object_refs = (callbacks.object_ref_callback() != nullptr);
   _reporting_primitive_fields = false;
   _reporting_primitive_array_values = false;
   _reporting_string_values = false;
-  _visit_stack = create_visit_stack();
   _dead_objects = objects;
-
-  CallbackInvoker::initialize_for_basic_heap_walk(tag_map, _visit_stack, user_data, callbacks, &_bitset);
+  CallbackInvoker::initialize_for_basic_heap_walk(tag_map, user_data, callbacks, &_visit_stack);
 }
 
 VM_HeapWalkOperation::VM_HeapWalkOperation(JvmtiTagMap* tag_map,
                                            Handle initial_object,
                                            AdvancedHeapWalkContext callbacks,
@@ -2502,27 +2834,22 @@
   _initial_object = initial_object;
   _following_object_refs = true;
   _reporting_primitive_fields = (callbacks.primitive_field_callback() != nullptr);;
   _reporting_primitive_array_values = (callbacks.array_primitive_value_callback() != nullptr);;
   _reporting_string_values = (callbacks.string_primitive_value_callback() != nullptr);;
-  _visit_stack = create_visit_stack();
   _dead_objects = objects;
-  CallbackInvoker::initialize_for_advanced_heap_walk(tag_map, _visit_stack, user_data, callbacks, &_bitset);
+  CallbackInvoker::initialize_for_advanced_heap_walk(tag_map, user_data, callbacks, &_visit_stack);
 }
 
 VM_HeapWalkOperation::~VM_HeapWalkOperation() {
-  if (_following_object_refs) {
-    assert(_visit_stack != nullptr, "checking");
-    delete _visit_stack;
-    _visit_stack = nullptr;
-  }
 }
 
 // an array references its class and has a reference to
 // each element in the array
-inline bool VM_HeapWalkOperation::iterate_over_array(oop o) {
-  objArrayOop array = objArrayOop(o);
+inline bool VM_HeapWalkOperation::iterate_over_array(const JvmtiHeapwalkObject& o) {
+  assert(!o.is_flat(), "Array object cannot be flattened");
+  objArrayOop array = objArrayOop(o.obj());
 
   // array reference to its class
   oop mirror = ObjArrayKlass::cast(array->klass())->java_mirror();
   if (!CallbackInvoker::report_class_reference(o, mirror)) {
     return false;
@@ -2542,13 +2869,52 @@
     }
   }
   return true;
 }
 
+// similar to iterate_over_array(), but itrates over flat array
+inline bool VM_HeapWalkOperation::iterate_over_flat_array(const JvmtiHeapwalkObject& o) {
+  assert(!o.is_flat(), "Array object cannot be flattened");
+  flatArrayOop array = flatArrayOop(o.obj());
+  FlatArrayKlass* faklass = FlatArrayKlass::cast(array->klass());
+  InlineKlass* vk = InlineKlass::cast(faklass->element_klass());
+  bool need_null_check = faklass->layout_kind() == LayoutKind::NULLABLE_ATOMIC_FLAT;
+
+  // array reference to its class
+  oop mirror = faklass->java_mirror();
+  if (!CallbackInvoker::report_class_reference(o, mirror)) {
+    return false;
+  }
+
+  // iterate over the array and report each reference to a
+  // non-null element
+  for (int index = 0; index < array->length(); index++) {
+    address addr = (address)array->value_at_addr(index, faklass->layout_helper());
+
+    // check for null
+    if (need_null_check) {
+      if (vk->is_payload_marked_as_null(addr)) {
+        continue;
+      }
+    }
+
+    // offset in the array oop
+    int offset = (int)(addr - cast_from_oop<address>(array));
+    JvmtiHeapwalkObject elem(o.obj(), offset, vk, faklass->layout_kind());
+
+    // report the array reference
+    if (!CallbackInvoker::report_array_element_reference(o, elem, index)) {
+      return false;
+    }
+  }
+  return true;
+}
+
 // a type array references its class
-inline bool VM_HeapWalkOperation::iterate_over_type_array(oop o) {
-  Klass* k = o->klass();
+inline bool VM_HeapWalkOperation::iterate_over_type_array(const JvmtiHeapwalkObject& o) {
+  assert(!o.is_flat(), "Array object cannot be flattened");
+  Klass* k = o.klass();
   oop mirror = k->java_mirror();
   if (!CallbackInvoker::report_class_reference(o, mirror)) {
     return false;
   }
 
@@ -2578,24 +2944,26 @@
 }
 #endif // #ifdef ASSERT
 
 // a class references its super class, interfaces, class loader, ...
 // and finally its static fields
-inline bool VM_HeapWalkOperation::iterate_over_class(oop java_class) {
+inline bool VM_HeapWalkOperation::iterate_over_class(const JvmtiHeapwalkObject& o) {
+  assert(!o.is_flat(), "Klass object cannot be flattened");
+  Klass* klass = java_lang_Class::as_Klass(o.obj());
   int i;
-  Klass* klass = java_lang_Class::as_Klass(java_class);
 
   if (klass->is_instance_klass()) {
     InstanceKlass* ik = InstanceKlass::cast(klass);
 
     // Ignore the class if it hasn't been initialized yet
     if (!ik->is_linked()) {
       return true;
     }
 
     // get the java mirror
-    oop mirror = klass->java_mirror();
+    oop mirror_oop = klass->java_mirror();
+    JvmtiHeapwalkObject mirror(mirror_oop);
 
     // super (only if something more interesting than java.lang.Object)
     InstanceKlass* java_super = ik->java_super();
     if (java_super != nullptr && java_super != vmClasses::Object_klass()) {
       oop super = java_super->java_mirror();
@@ -2680,22 +3048,22 @@
     ClassFieldMap* field_map = ClassFieldMap::create_map_of_static_fields(klass);
     for (i=0; i<field_map->field_count(); i++) {
       ClassFieldDescriptor* field = field_map->field_at(i);
       char type = field->field_type();
       if (!is_primitive_field_type(type)) {
-        oop fld_o = mirror->obj_field(field->field_offset());
-        assert(verify_static_oop(ik, mirror, field->field_offset()), "sanity check");
+        oop fld_o = mirror_oop->obj_field(field->field_offset());
+        assert(verify_static_oop(ik, mirror_oop, field->field_offset()), "sanity check");
         if (fld_o != nullptr) {
           int slot = field->field_index();
           if (!CallbackInvoker::report_static_field_reference(mirror, fld_o, slot)) {
             delete field_map;
             return false;
           }
         }
       } else {
          if (is_reporting_primitive_fields()) {
-           address addr = cast_from_oop<address>(mirror) + field->field_offset();
+           address addr = cast_from_oop<address>(mirror_oop) + field->field_offset();
            int slot = field->field_index();
            if (!CallbackInvoker::report_primitive_static_field(mirror, slot, addr, type)) {
              delete field_map;
              return false;
           }
@@ -2711,47 +3079,65 @@
 }
 
 // an object references a class and its instance fields
 // (static fields are ignored here as we report these as
 // references from the class).
-inline bool VM_HeapWalkOperation::iterate_over_object(oop o) {
+inline bool VM_HeapWalkOperation::iterate_over_object(const JvmtiHeapwalkObject& o) {
   // reference to the class
-  if (!CallbackInvoker::report_class_reference(o, o->klass()->java_mirror())) {
+  if (!CallbackInvoker::report_class_reference(o, o.klass()->java_mirror())) {
     return false;
   }
 
   // iterate over instance fields
-  ClassFieldMap* field_map = JvmtiCachedClassFieldMap::get_map_of_instance_fields(o);
+  ClassFieldMap* field_map = JvmtiCachedClassFieldMap::get_map_of_instance_fields(o.klass());
   for (int i=0; i<field_map->field_count(); i++) {
     ClassFieldDescriptor* field = field_map->field_at(i);
     char type = field->field_type();
+    int slot = field->field_index();
+    int field_offset = field->field_offset();
+    if (o.is_flat()) {
+      // the object is inlined, its fields are stored without the header
+      field_offset += o.offset() - o.inline_klass()->payload_offset();
+    }
     if (!is_primitive_field_type(type)) {
-      oop fld_o = o->obj_field_access<AS_NO_KEEPALIVE | ON_UNKNOWN_OOP_REF>(field->field_offset());
-      // ignore any objects that aren't visible to profiler
-      if (fld_o != nullptr) {
-        assert(Universe::heap()->is_in(fld_o), "unsafe code should not "
-               "have references to Klass* anymore");
-        int slot = field->field_index();
-        if (!CallbackInvoker::report_field_reference(o, fld_o, slot)) {
+      if (field->is_flat()) {
+        // check for possible nulls
+        bool can_be_null = field->layout_kind() == LayoutKind::NULLABLE_ATOMIC_FLAT;
+        if (can_be_null) {
+          address payload = cast_from_oop<address>(o.obj()) + field_offset;
+          if (field->inline_klass()->is_payload_marked_as_null(payload)) {
+            continue;
+          }
+        }
+        JvmtiHeapwalkObject field_obj(o.obj(), field_offset, field->inline_klass(), field->layout_kind());
+        if (!CallbackInvoker::report_field_reference(o, field_obj, slot)) {
           return false;
         }
+      } else {
+        oop fld_o = o.obj()->obj_field_access<AS_NO_KEEPALIVE | ON_UNKNOWN_OOP_REF>(field_offset);
+        // ignore any objects that aren't visible to profiler
+        if (fld_o != nullptr) {
+          assert(Universe::heap()->is_in(fld_o), "unsafe code should not have references to Klass* anymore");
+          if (!CallbackInvoker::report_field_reference(o, fld_o, slot)) {
+            return false;
+          }
+        }
       }
     } else {
       if (is_reporting_primitive_fields()) {
         // primitive instance field
-        address addr = cast_from_oop<address>(o) + field->field_offset();
-        int slot = field->field_index();
+        address addr = cast_from_oop<address>(o.obj()) + field_offset;
         if (!CallbackInvoker::report_primitive_instance_field(o, slot, addr, type)) {
           return false;
         }
       }
     }
   }
 
   // if the object is a java.lang.String
   if (is_reporting_string_values() &&
-      o->klass() == vmClasses::String_klass()) {
+      o.klass() == vmClasses::String_klass()) {
     if (!CallbackInvoker::report_string_value(o)) {
       return false;
     }
   }
   return true;
@@ -2816,11 +3202,11 @@
   if (!java_thread->has_last_Java_frame()) {
     if (!stack_collector.set_thread(JVMTI_HEAP_REFERENCE_THREAD, threadObj)) {
       return false;
     }
     // no last java frame but there may be JNI locals
-    blk->set_context(tag_for(_tag_map, threadObj), java_lang_Thread::thread_id(threadObj), 0, (jmethodID)nullptr);
+    blk->set_context(_tag_map->find(threadObj), java_lang_Thread::thread_id(threadObj), 0, (jmethodID)nullptr);
     java_thread->active_handles()->oops_do(blk);
     return !blk->stopped();
   }
   // vframes are resource allocated
   Thread* current_thread = Thread::current();
@@ -2916,41 +3302,49 @@
 // visit an object
 // first mark the object as visited
 // second get all the outbound references from this object (in other words, all
 // the objects referenced by this object).
 //
-bool VM_HeapWalkOperation::visit(oop o) {
+bool VM_HeapWalkOperation::visit(const JvmtiHeapwalkObject& o) {
   // mark object as visited
-  assert(!_bitset.is_marked(o), "can't visit same object more than once");
-  _bitset.mark_obj(o);
+  assert(!visit_stack()->is_visited(o), "can't visit same object more than once");
+  visit_stack()->mark_visited(o);
 
+  Klass* klass = o.klass();
   // instance
-  if (o->is_instance()) {
-    if (o->klass() == vmClasses::Class_klass()) {
-      if (!java_lang_Class::is_primitive(o)) {
+  if (klass->is_instance_klass()) {
+    if (klass == vmClasses::Class_klass()) {
+      assert(!o.is_flat(), "Class object cannot be flattened");
+      if (!java_lang_Class::is_primitive(o.obj())) {
         // a java.lang.Class
         return iterate_over_class(o);
       }
     } else {
       // we report stack references only when initial object is not specified
       // (in the case we start from heap roots which include platform thread stack references)
-      if (initial_object().is_null() && java_lang_VirtualThread::is_subclass(o->klass())) {
-        if (!collect_vthread_stack_refs(o)) {
+      if (initial_object().is_null() && java_lang_VirtualThread::is_subclass(klass)) {
+        assert(!o.is_flat(), "VirtualThread object cannot be flattened");
+        if (!collect_vthread_stack_refs(o.obj())) {
           return false;
         }
       }
       return iterate_over_object(o);
     }
   }
 
+  // flat object array
+  if (klass->is_flatArray_klass()) {
+      return iterate_over_flat_array(o);
+  }
+
   // object array
-  if (o->is_objArray()) {
+  if (klass->is_objArray_klass()) {
     return iterate_over_array(o);
   }
 
   // type array
-  if (o->is_typeArray()) {
+  if (klass->is_typeArray_klass()) {
     return iterate_over_type_array(o);
   }
 
   return true;
 }
@@ -2978,12 +3372,12 @@
   if (is_following_references()) {
 
     // visit each object until all reachable objects have been
     // visited or the callback asked to terminate the iteration.
     while (!visit_stack()->is_empty()) {
-      oop o = visit_stack()->pop();
-      if (!_bitset.is_marked(o)) {
+      const JvmtiHeapwalkObject o = visit_stack()->pop();
+      if (!visit_stack()->is_visited(o)) {
         if (!visit(o)) {
           break;
         }
       }
     }
@@ -3008,10 +3402,12 @@
     MutexLocker ml(Heap_lock);
     BasicHeapWalkContext context(heap_root_callback, stack_ref_callback, object_ref_callback);
     VM_HeapWalkOperation op(this, Handle(), context, user_data, &dead_objects);
     VMThread::execute(&op);
   }
+  convert_flat_object_entries();
+
   // Post events outside of Heap_lock
   post_dead_objects(&dead_objects);
 }
 
 // iterate over all objects that are reachable from a given object
@@ -3030,10 +3426,12 @@
     MutexLocker ml(Heap_lock);
     BasicHeapWalkContext context(nullptr, nullptr, object_ref_callback);
     VM_HeapWalkOperation op(this, initial_object, context, user_data, &dead_objects);
     VMThread::execute(&op);
   }
+  convert_flat_object_entries();
+
   // Post events outside of Heap_lock
   post_dead_objects(&dead_objects);
 }
 
 // follow references from an initial object or the GC roots
@@ -3062,10 +3460,12 @@
     MutexLocker ml(Heap_lock);
     AdvancedHeapWalkContext context(heap_filter, klass, callbacks);
     VM_HeapWalkOperation op(this, initial_object, context, user_data, &dead_objects);
     VMThread::execute(&op);
   }
+  convert_flat_object_entries();
+
   // Post events outside of Heap_lock
   post_dead_objects(&dead_objects);
 }
 
 // Verify gc_notification follows set_needs_cleaning.
