diff a/.jcheck/conf b/.jcheck/conf
--- a/.jcheck/conf
+++ b/.jcheck/conf
@@ -1,12 +1,12 @@
 [general]
-project=jdk
+project=lilliput
 jbs=JDK
 version=23
 
 [checks]
-error=author,committer,reviewers,merge,issues,executable,symlink,message,hg-tag,whitespace,problemlists
+error=author,committer,reviewers,merge,executable,symlink,message,hg-tag,whitespace,problemlists
 
 [repository]
 tags=(?:jdk-(?:[1-9]([0-9]*)(?:\.(?:0|[1-9][0-9]*)){0,4})(?:\+(?:(?:[0-9]+))|(?:-ga)))|(?:jdk[4-9](?:u\d{1,3})?-(?:(?:b\d{2,3})|(?:ga)))|(?:hs\d\d(?:\.\d{1,2})?-b\d\d)
 branches=
 
@@ -20,16 +20,13 @@
 
 [checks "merge"]
 message=Merge
 
 [checks "reviewers"]
-reviewers=1
+committers=1
 ignore=duke
 
 [checks "committer"]
 role=committer
 
-[checks "issues"]
-pattern=^([124-8][0-9]{6}): (\S.*)$
-
 [checks "problemlists"]
 dirs=test/jdk|test/langtools|test/lib-test|test/hotspot/jtreg|test/jaxp
diff a/make/Images.gmk b/make/Images.gmk
--- a/make/Images.gmk
+++ b/make/Images.gmk
@@ -130,14 +130,20 @@
 CDS_DUMP_FLAGS = -Xmx128M -Xms128M
 
 # Helper function for creating the CDS archives for the JDK and JRE
 #
 # Param1 - VM variant (e.g., server, client, zero, ...)
-# Param2 - _nocoops, or empty
+# Param2 - _nocoops, _coh, _nocoops_coh, or empty
 define CreateCDSArchive
-  $1_$2_DUMP_EXTRA_ARG := $(if $(filter _nocoops, $2),-XX:-UseCompressedOops,)
-  $1_$2_DUMP_TYPE      := $(if $(filter _nocoops, $2),-NOCOOPS,)
+  $1_$2_COOPS_OPTION := $(if $(findstring _nocoops, $2),-XX:-UseCompressedOops)
+  # enable and also explicitly disable coh as needed.
+  ifeq ($(call isTargetCpuBits, 64), true)
+    $1_$2_COH_OPTION := -XX:+UnlockExperimentalVMOptions \
+                        $(if $(findstring _coh, $2),-XX:+UseCompactObjectHeaders,-XX:-UseCompactObjectHeaders)
+  endif
+  $1_$2_DUMP_EXTRA_ARG := $$($1_$2_COOPS_OPTION) $$($1_$2_COH_OPTION)
+  $1_$2_DUMP_TYPE      := $(if $(findstring _nocoops, $2),-NOCOOPS,)$(if $(findstring _coh, $2),-COH,)
 
   # Only G1 supports dumping the shared heap, so explicitly use G1 if the JVM supports it.
   $1_$2_CDS_DUMP_FLAGS := $(CDS_DUMP_FLAGS) $(if $(filter g1gc, $(JVM_FEATURES_$1)),-XX:+UseG1GC)
 
   ifeq ($(OPENJDK_TARGET_OS), windows)
@@ -188,10 +194,18 @@
 
   ifeq ($(call isTargetCpuBits, 64), true)
     $(foreach v, $(JVM_VARIANTS), \
       $(eval $(call CreateCDSArchive,$v,_nocoops)) \
     )
+    ifeq ($(BUILD_CDS_ARCHIVE_COH), true)
+      $(foreach v, $(JVM_VARIANTS), \
+        $(eval $(call CreateCDSArchive,$v,_coh)) \
+      )
+      $(foreach v, $(JVM_VARIANTS), \
+        $(eval $(call CreateCDSArchive,$v,_nocoops_coh)) \
+      )
+    endif
   endif
 endif
 
 ################################################################################
 # src.zip
diff a/make/autoconf/configure.ac b/make/autoconf/configure.ac
--- a/make/autoconf/configure.ac
+++ b/make/autoconf/configure.ac
@@ -1,7 +1,7 @@
 #
-# Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 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.  Oracle designates this
@@ -258,10 +258,11 @@
 
 JDKOPT_ENABLE_DISABLE_GENERATE_CLASSLIST
 JDKOPT_EXCLUDE_TRANSLATIONS
 JDKOPT_ENABLE_DISABLE_MANPAGES
 JDKOPT_ENABLE_DISABLE_CDS_ARCHIVE
+JDKOPT_ENABLE_DISABLE_CDS_ARCHIVE_COH
 JDKOPT_ENABLE_DISABLE_COMPATIBLE_CDS_ALIGNMENT
 JDKOPT_SETUP_MACOSX_SIGNING
 
 ###############################################################################
 #
diff a/make/autoconf/jdk-options.m4 b/make/autoconf/jdk-options.m4
--- a/make/autoconf/jdk-options.m4
+++ b/make/autoconf/jdk-options.m4
@@ -1,7 +1,7 @@
 #
-# Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 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.  Oracle designates this
@@ -665,10 +665,36 @@
         fi
       ])
   AC_SUBST(BUILD_CDS_ARCHIVE)
 ])
 
+################################################################################
+#
+# Enable or disable the default CDS archive generation for Compact Object Headers
+#
+AC_DEFUN([JDKOPT_ENABLE_DISABLE_CDS_ARCHIVE_COH],
+[
+  UTIL_ARG_ENABLE(NAME: cds-archive-coh, DEFAULT: auto, RESULT: BUILD_CDS_ARCHIVE_COH,
+      DESC: [enable generation of default CDS archives for compact object headers (requires --enable-cds-archive)],
+      DEFAULT_DESC: [auto],
+      CHECKING_MSG: [if default CDS archives for compact object headers should be generated],
+      CHECK_AVAILABLE: [
+        AC_MSG_CHECKING([if CDS archive with compact object headers is available])
+        if test "x$BUILD_CDS_ARCHIVE" = "xfalse"; then
+          AC_MSG_RESULT([no (CDS default archive generation is disabled)])
+          AVAILABLE=false
+        elif test "x$OPENJDK_TARGET_CPU" != "xx86_64" &&
+             test "x$OPENJDK_TARGET_CPU" != "xaarch64"; then
+          AC_MSG_RESULT([no (compact object headers not supported for this platform)])
+          AVAILABLE=false
+        else
+          AC_MSG_RESULT([yes])
+        fi
+      ])
+  AC_SUBST(BUILD_CDS_ARCHIVE_COH)
+])
+
 ################################################################################
 #
 # Enable the alternative CDS core region alignment
 #
 AC_DEFUN([JDKOPT_ENABLE_DISABLE_COMPATIBLE_CDS_ALIGNMENT],
diff a/make/autoconf/spec.gmk.template b/make/autoconf/spec.gmk.template
--- a/make/autoconf/spec.gmk.template
+++ b/make/autoconf/spec.gmk.template
@@ -366,10 +366,11 @@
 EXCLUDE_TRANSLATIONS := @EXCLUDE_TRANSLATIONS@
 
 BUILD_MANPAGES := @BUILD_MANPAGES@
 
 BUILD_CDS_ARCHIVE := @BUILD_CDS_ARCHIVE@
+BUILD_CDS_ARCHIVE_COH := @BUILD_CDS_ARCHIVE_COH@
 
 ENABLE_COMPATIBLE_CDS_ALIGNMENT := @ENABLE_COMPATIBLE_CDS_ALIGNMENT@
 
 ALLOW_ABSOLUTE_PATHS_IN_OUTPUT := @ALLOW_ABSOLUTE_PATHS_IN_OUTPUT@
 
diff a/src/hotspot/cpu/aarch64/aarch64.ad b/src/hotspot/cpu/aarch64/aarch64.ad
--- a/src/hotspot/cpu/aarch64/aarch64.ad
+++ b/src/hotspot/cpu/aarch64/aarch64.ad
@@ -6602,20 +6602,34 @@
 
 // Load Narrow Klass Pointer
 instruct loadNKlass(iRegNNoSp dst, memory4 mem)
 %{
   match(Set dst (LoadNKlass mem));
-  predicate(!needs_acquiring_load(n));
+  predicate(!needs_acquiring_load(n) && !UseCompactObjectHeaders);
 
   ins_cost(4 * INSN_COST);
   format %{ "ldrw  $dst, $mem\t# compressed class ptr" %}
 
   ins_encode(aarch64_enc_ldrw(dst, mem));
 
   ins_pipe(iload_reg_mem);
 %}
 
+instruct loadNKlassCompactHeaders(iRegNNoSp dst, memory4 mem, rFlagsReg cr)
+%{
+  match(Set dst (LoadNKlass mem));
+  effect(KILL cr);
+  predicate(!needs_acquiring_load(n) && UseCompactObjectHeaders);
+
+  ins_cost(4 * INSN_COST);
+  format %{ "ldrw  $dst, $mem\t# compressed class ptr" %}
+  ins_encode %{
+    __ load_nklass_compact($dst$$Register, $mem$$base$$Register, $mem$$index$$Register, $mem$$scale, $mem$$disp);
+  %}
+  ins_pipe(pipe_slow);
+%}
+
 // Load Float
 instruct loadF(vRegF dst, memory4 mem)
 %{
   match(Set dst (LoadF mem));
   predicate(!needs_acquiring_load(n));
@@ -15951,11 +15965,11 @@
 
   ins_cost(5 * INSN_COST);
   format %{ "fastlock $object,$box\t! kills $tmp,$tmp2,$tmp3" %}
 
   ins_encode %{
-    __ fast_lock_lightweight($object$$Register, $tmp$$Register, $tmp2$$Register, $tmp3$$Register);
+    __ fast_lock_lightweight($object$$Register, $box$$Register, $tmp$$Register, $tmp2$$Register, $tmp3$$Register);
   %}
 
   ins_pipe(pipe_serial);
 %}
 
@@ -15967,11 +15981,11 @@
 
   ins_cost(5 * INSN_COST);
   format %{ "fastunlock $object,$box\t! kills $tmp, $tmp2, $tmp3" %}
 
   ins_encode %{
-    __ fast_unlock_lightweight($object$$Register, $tmp$$Register, $tmp2$$Register, $tmp3$$Register);
+    __ fast_unlock_lightweight($object$$Register, $box$$Register, $tmp$$Register, $tmp2$$Register, $tmp3$$Register);
   %}
 
   ins_pipe(pipe_serial);
 %}
 
diff a/src/hotspot/cpu/aarch64/c1_CodeStubs_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_CodeStubs_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/c1_CodeStubs_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/c1_CodeStubs_aarch64.cpp
@@ -30,10 +30,11 @@
 #include "c1/c1_LIRAssembler.hpp"
 #include "c1/c1_MacroAssembler.hpp"
 #include "c1/c1_Runtime1.hpp"
 #include "classfile/javaClasses.hpp"
 #include "nativeInst_aarch64.hpp"
+#include "runtime/objectMonitor.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "vmreg_aarch64.inline.hpp"
 
 
 #define __ ce->masm()->
@@ -231,11 +232,10 @@
   }
   __ adr(lr, _continuation);
   __ far_jump(RuntimeAddress(Runtime1::entry_for(exit_id)));
 }
 
-
 // Implementation of patching:
 // - Copy the code at given offset to an inlined buffer (first the bytes, then the number of bytes)
 // - Replace original code with a call to the stub
 // At Runtime:
 // - call to stub, jump to runtime
diff a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp
@@ -2243,12 +2243,10 @@
   int elem_size = type2aelembytes(basic_type);
   int scale = exact_log2(elem_size);
 
   Address src_length_addr = Address(src, arrayOopDesc::length_offset_in_bytes());
   Address dst_length_addr = Address(dst, arrayOopDesc::length_offset_in_bytes());
-  Address src_klass_addr = Address(src, oopDesc::klass_offset_in_bytes());
-  Address dst_klass_addr = Address(dst, oopDesc::klass_offset_in_bytes());
 
   // test for null
   if (flags & LIR_OpArrayCopy::src_null_check) {
     __ cbz(src, *stub->entry());
   }
@@ -2305,19 +2303,11 @@
 
   if (flags & LIR_OpArrayCopy::type_check) {
     // We don't know the array types are compatible
     if (basic_type != T_OBJECT) {
       // Simple test for basic type arrays
-      if (UseCompressedClassPointers) {
-        __ ldrw(tmp, src_klass_addr);
-        __ ldrw(rscratch1, dst_klass_addr);
-        __ cmpw(tmp, rscratch1);
-      } else {
-        __ ldr(tmp, src_klass_addr);
-        __ ldr(rscratch1, dst_klass_addr);
-        __ cmp(tmp, rscratch1);
-      }
+      __ cmp_klass(src, dst, tmp, rscratch1);
       __ br(Assembler::NE, *stub->entry());
     } else {
       // For object arrays, if src is a sub class of dst then we can
       // safely do the copy.
       Label cont, slow;
@@ -2435,40 +2425,18 @@
     // dst type is exactly the expected type and the src type is a
     // subtype which we can't check or src is the same array as dst
     // but not necessarily exactly of type default_type.
     Label known_ok, halt;
     __ mov_metadata(tmp, default_type->constant_encoding());
-    if (UseCompressedClassPointers) {
-      __ encode_klass_not_null(tmp);
-    }
 
     if (basic_type != T_OBJECT) {
-
-      if (UseCompressedClassPointers) {
-        __ ldrw(rscratch1, dst_klass_addr);
-        __ cmpw(tmp, rscratch1);
-      } else {
-        __ ldr(rscratch1, dst_klass_addr);
-        __ cmp(tmp, rscratch1);
-      }
+      __ cmp_klass(dst, tmp, rscratch1);
       __ br(Assembler::NE, halt);
-      if (UseCompressedClassPointers) {
-        __ ldrw(rscratch1, src_klass_addr);
-        __ cmpw(tmp, rscratch1);
-      } else {
-        __ ldr(rscratch1, src_klass_addr);
-        __ cmp(tmp, rscratch1);
-      }
+      __ cmp_klass(src, tmp, rscratch1);
       __ br(Assembler::EQ, known_ok);
     } else {
-      if (UseCompressedClassPointers) {
-        __ ldrw(rscratch1, dst_klass_addr);
-        __ cmpw(tmp, rscratch1);
-      } else {
-        __ ldr(rscratch1, dst_klass_addr);
-        __ cmp(tmp, rscratch1);
-      }
+      __ cmp_klass(dst, tmp, rscratch1);
       __ br(Assembler::EQ, known_ok);
       __ cmp(src, dst);
       __ br(Assembler::EQ, known_ok);
     }
     __ bind(halt);
@@ -2546,11 +2514,16 @@
   if (info != nullptr) {
     add_debug_info_for_null_check_here(info);
   }
 
   if (UseCompressedClassPointers) {
-    __ ldrw(result, Address (obj, oopDesc::klass_offset_in_bytes()));
+    if (UseCompactObjectHeaders) {
+      __ ldr(result, Address(obj, oopDesc::mark_offset_in_bytes()));
+      __ lsr(result, result, markWord::klass_shift);
+    } else {
+      __ ldrw(result, Address (obj, oopDesc::klass_offset_in_bytes()));
+    }
     __ decode_klass_not_null(result);
   } else {
     __ ldr(result, Address (obj, oopDesc::klass_offset_in_bytes()));
   }
 }
diff a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp
@@ -79,11 +79,11 @@
     tstw(hdr, JVM_ACC_IS_VALUE_BASED_CLASS);
     br(Assembler::NE, slow_case);
   }
 
   if (LockingMode == LM_LIGHTWEIGHT) {
-    lightweight_lock(obj, hdr, temp, rscratch2, slow_case);
+    lightweight_lock(disp_hdr, obj, hdr, temp, rscratch2, slow_case);
   } else if (LockingMode == LM_LEGACY) {
     Label done;
     // Load object header
     ldr(hdr, Address(obj, hdr_offset));
     // and mark it as unlocked
@@ -173,30 +173,34 @@
   }
 }
 
 void C1_MacroAssembler::initialize_header(Register obj, Register klass, Register len, Register t1, Register t2) {
   assert_different_registers(obj, klass, len);
-  // This assumes that all prototype bits fit in an int32_t
-  mov(t1, (int32_t)(intptr_t)markWord::prototype().value());
-  str(t1, Address(obj, oopDesc::mark_offset_in_bytes()));
 
-  if (UseCompressedClassPointers) { // Take care not to kill klass
-    encode_klass_not_null(t1, klass);
-    strw(t1, Address(obj, oopDesc::klass_offset_in_bytes()));
+  if (UseCompactObjectHeaders) {
+    ldr(t1, Address(klass, Klass::prototype_header_offset()));
+    str(t1, Address(obj, oopDesc::mark_offset_in_bytes()));
   } else {
-    str(klass, Address(obj, oopDesc::klass_offset_in_bytes()));
+    // This assumes that all prototype bits fit in an int32_t
+    mov(t1, (int32_t)(intptr_t)markWord::prototype().value());
+    str(t1, Address(obj, oopDesc::mark_offset_in_bytes()));
+    if (UseCompressedClassPointers) { // Take care not to kill klass
+      encode_klass_not_null(t1, klass);
+      strw(t1, Address(obj, oopDesc::klass_offset_in_bytes()));
+    } else {
+      str(klass, Address(obj, oopDesc::klass_offset_in_bytes()));
+    }
   }
-
   if (len->is_valid()) {
     strw(len, Address(obj, arrayOopDesc::length_offset_in_bytes()));
     int base_offset = arrayOopDesc::length_offset_in_bytes() + BytesPerInt;
     if (!is_aligned(base_offset, BytesPerWord)) {
       assert(is_aligned(base_offset, BytesPerInt), "must be 4-byte aligned");
       // Clear gap/first 4 bytes following the length field.
       strw(zr, Address(obj, base_offset));
     }
-  } else if (UseCompressedClassPointers) {
+  } else if (UseCompressedClassPointers && !UseCompactObjectHeaders) {
     store_klass_gap(obj, zr);
   }
 }
 
 // preserves obj, destroys len_in_bytes
diff a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp
@@ -222,38 +222,41 @@
   decrement(Address(rthread, JavaThread::held_monitor_count_offset()));
 
   bind(no_count);
 }
 
-void C2_MacroAssembler::fast_lock_lightweight(Register obj, Register t1,
+void C2_MacroAssembler::fast_lock_lightweight(Register obj, Register box, Register t1,
                                               Register t2, Register t3) {
   assert(LockingMode == LM_LIGHTWEIGHT, "must be");
-  assert_different_registers(obj, t1, t2, t3);
+  assert_different_registers(obj, box, t1, t2, t3);
 
   // Handle inflated monitor.
   Label inflated;
   // Finish fast lock successfully. MUST branch to with flag == EQ
   Label locked;
   // Finish fast lock unsuccessfully. MUST branch to with flag == NE
   Label slow_path;
 
+  // Clear box. TODO[OMWorld]: Is this necessary? May also defer this to not write twice.
+  str(zr, Address(box, BasicLock::object_monitor_cache_offset_in_bytes()));
+
   if (DiagnoseSyncOnValueBasedClasses != 0) {
     load_klass(t1, obj);
     ldrw(t1, Address(t1, Klass::access_flags_offset()));
     tstw(t1, JVM_ACC_IS_VALUE_BASED_CLASS);
     br(Assembler::NE, slow_path);
   }
 
   const Register t1_mark = t1;
+  const Register t3_t = t3;
 
   { // Lightweight locking
 
     // Push lock to the lock stack and finish successfully. MUST branch to with flag == EQ
     Label push;
 
     const Register t2_top = t2;
-    const Register t3_t = t3;
 
     // Check if lock-stack is full.
     ldrw(t2_top, Address(rthread, JavaThread::lock_stack_top_offset()));
     cmpw(t2_top, (unsigned)LockStack::end_offset() - 1);
     br(Assembler::GT, slow_path);
@@ -287,30 +290,100 @@
   }
 
   { // Handle inflated monitor.
     bind(inflated);
 
-    // mark contains the tagged ObjectMonitor*.
-    const Register t1_tagged_monitor = t1_mark;
-    const uintptr_t monitor_tag = markWord::monitor_value;
-    const Register t2_owner_addr = t2;
-    const Register t3_owner = t3;
+    if (!OMUseC2Cache) {
+      // Set Flags == NE
+      cmp(zr, obj);
+      b(slow_path);
+    } else {
+
+      if (OMCacheHitRate) increment(Address(rthread, JavaThread::lock_lookup_offset()));
 
-    // Compute owner address.
-    lea(t2_owner_addr, Address(t1_tagged_monitor, (in_bytes(ObjectMonitor::owner_offset()) - monitor_tag)));
+      Label monitor_found;
 
-    // CAS owner (null => current thread).
-    cmpxchg(t2_owner_addr, zr, rthread, Assembler::xword, /*acquire*/ true,
-            /*release*/ false, /*weak*/ false, t3_owner);
-    br(Assembler::EQ, locked);
+      // Load cache address
+      lea(t3_t, Address(rthread, JavaThread::om_cache_oops_offset()));
 
-    // Check if recursive.
-    cmp(t3_owner, rthread);
-    br(Assembler::NE, slow_path);
+      const int num_unrolled = MIN2(OMC2UnrollCacheEntries, OMCacheSize);
+      for (int i = 0; i < num_unrolled; i++) {
+        ldr(t1, Address(t3_t));
+        cmp(obj, t1);
+        br(Assembler::EQ, monitor_found);
+        if (i + 1 != num_unrolled) {
+          increment(t3_t, in_bytes(OMCache::oop_to_oop_difference()));
+        }
+      }
+
+      if (num_unrolled == 0 || (OMC2UnrollCacheLookupLoopTail && num_unrolled != OMCacheSize)) {
+        if (num_unrolled != 0) {
+          // Loop after unrolling, advance iterator.
+          increment(t3_t, in_bytes(OMCache::oop_to_oop_difference()));
+        }
+
+        Label loop;
+
+        // Search for obj in cache.
+        bind(loop);
+
+        // Check for match.
+        ldr(t1, Address(t3_t));
+        cmp(obj, t1);
+        br(Assembler::EQ, monitor_found);
+
+        // Search until null encountered, guaranteed _null_sentinel at end.
+        increment(t3_t, in_bytes(OMCache::oop_to_oop_difference()));
+        cbnz(t1, loop);
+        // Cache Miss, NE set from cmp above, cbnz does not set flags
+        b(slow_path);
+      } else {
+        b(slow_path);
+      }
+
+      bind(monitor_found);
+      ldr(t1, Address(t3_t, OMCache::oop_to_monitor_difference()));
+      if (OMCacheHitRate) increment(Address(rthread, JavaThread::lock_hit_offset()));
+
+      // ObjectMonitor* is in t1
+      const Register t1_monitor = t1;
+      const Register t2_owner_addr = t2;
+      const Register t3_owner = t3;
+
+      Label recursive;
+      Label monitor_locked;
+
+      // Compute owner address.
+      lea(t2_owner_addr, Address(t1_monitor, ObjectMonitor::owner_offset()));
+
+      if (OMRecursiveFastPath) {
+        ldr(t3_owner, Address(t2_owner_addr));
+        cmp(t3_owner, rthread);
+        br(Assembler::EQ, recursive);
+      }
+
+      // CAS owner (null => current thread).
+      cmpxchg(t2_owner_addr, zr, rthread, Assembler::xword, /*acquire*/ true,
+              /*release*/ false, /*weak*/ false, t3_owner);
+      br(Assembler::EQ, monitor_locked);
+
+      if (OMRecursiveFastPath) {
+        b(slow_path);
+      } else {
+        // Check if recursive.
+        cmp(t3_owner, rthread);
+        br(Assembler::NE, slow_path);
+      }
+
+      // Recursive.
+      bind(recursive);
+      increment(Address(t1_monitor, ObjectMonitor::recursions_offset()), 1);
+
+      bind(monitor_locked);
+      str(t1_monitor, Address(box, BasicLock::object_monitor_cache_offset_in_bytes()));
+    }
 
-    // Recursive.
-    increment(Address(t1_tagged_monitor, in_bytes(ObjectMonitor::recursions_offset()) - monitor_tag), 1);
   }
 
   bind(locked);
   increment(Address(rthread, JavaThread::held_monitor_count_offset()));
 
@@ -329,14 +402,14 @@
   bind(flag_correct);
 #endif
   // C2 uses the value of Flags (NE vs EQ) to determine the continuation.
 }
 
-void C2_MacroAssembler::fast_unlock_lightweight(Register obj, Register t1, Register t2,
-                                                Register t3) {
+void C2_MacroAssembler::fast_unlock_lightweight(Register obj, Register box, Register t1,
+                                                Register t2, Register t3) {
   assert(LockingMode == LM_LIGHTWEIGHT, "must be");
-  assert_different_registers(obj, t1, t2, t3);
+  assert_different_registers(obj, box, t1, t2, t3);
 
   // Handle inflated monitor.
   Label inflated, inflated_load_monitor;
   // Finish fast unlock successfully. MUST branch to with flag == EQ
   Label unlocked;
@@ -347,10 +420,12 @@
   const Register t2_top = t2;
   const Register t3_t = t3;
 
   { // Lightweight unlock
 
+    Label push_and_slow_path;
+
     // Check if obj is top of lock-stack.
     ldrw(t2_top, Address(rthread, JavaThread::lock_stack_top_offset()));
     subw(t2_top, t2_top, oopSize);
     ldr(t3_t, Address(rthread, t2_top));
     cmp(obj, t3_t);
@@ -370,19 +445,23 @@
     // Not recursive.
     // Load Mark.
     ldr(t1_mark, Address(obj, oopDesc::mark_offset_in_bytes()));
 
     // Check header for monitor (0b10).
-    tbnz(t1_mark, exact_log2(markWord::monitor_value), inflated);
+    // Because we got here by popping (meaning we pushed in locked)
+    // there will be no monitor in the box. So we need to push back the obj
+    // so that the runtime can fix any potential anonymous owner.
+    tbnz(t1_mark, exact_log2(markWord::monitor_value), push_and_slow_path);
 
     // Try to unlock. Transition lock bits 0b00 => 0b01
     assert(oopDesc::mark_offset_in_bytes() == 0, "required to avoid lea");
     orr(t3_t, t1_mark, markWord::unlocked_value);
     cmpxchg(/*addr*/ obj, /*expected*/ t1_mark, /*new*/ t3_t, Assembler::xword,
             /*acquire*/ false, /*release*/ true, /*weak*/ false, noreg);
     br(Assembler::EQ, unlocked);
 
+    bind(push_and_slow_path);
     // Compare and exchange failed.
     // Restore lock-stack and handle the unlock in runtime.
     DEBUG_ONLY(str(obj, Address(rthread, t2_top));)
     addw(t2_top, t2_top, oopSize);
     str(t2_top, Address(rthread, JavaThread::lock_stack_top_offset()));
@@ -410,56 +489,63 @@
     br(Assembler::NE, inflated);
     stop("Fast Unlock lock on stack");
     bind(check_done);
 #endif
 
-    // mark contains the tagged ObjectMonitor*.
-    const Register t1_monitor = t1_mark;
-    const uintptr_t monitor_tag = markWord::monitor_value;
-
-    // Untag the monitor.
-    sub(t1_monitor, t1_mark, monitor_tag);
-
-    const Register t2_recursions = t2;
-    Label not_recursive;
-
-    // Check if recursive.
-    ldr(t2_recursions, Address(t1_monitor, ObjectMonitor::recursions_offset()));
-    cbz(t2_recursions, not_recursive);
-
-    // Recursive unlock.
-    sub(t2_recursions, t2_recursions, 1u);
-    str(t2_recursions, Address(t1_monitor, ObjectMonitor::recursions_offset()));
-    // Set flag == EQ
-    cmp(t2_recursions, t2_recursions);
-    b(unlocked);
-
-    bind(not_recursive);
-
-    Label release;
-    const Register t2_owner_addr = t2;
-
-    // Compute owner address.
-    lea(t2_owner_addr, Address(t1_monitor, ObjectMonitor::owner_offset()));
-
-    // Check if the entry lists are empty.
-    ldr(rscratch1, Address(t1_monitor, ObjectMonitor::EntryList_offset()));
-    ldr(t3_t, Address(t1_monitor, ObjectMonitor::cxq_offset()));
-    orr(rscratch1, rscratch1, t3_t);
-    cmp(rscratch1, zr);
-    br(Assembler::EQ, release);
-
-    // The owner may be anonymous and we removed the last obj entry in
-    // the lock-stack. This loses the information about the owner.
-    // Write the thread to the owner field so the runtime knows the owner.
-    str(rthread, Address(t2_owner_addr));
-    b(slow_path);
-
-    bind(release);
-    // Set owner to null.
-    // Release to satisfy the JMM
-    stlr(zr, t2_owner_addr);
+    if (!OMUseC2Cache) {
+      b(slow_path);
+    } else {
+      const Register t1_monitor = t1;
+
+      if (OMCacheHitRate) increment(Address(rthread, JavaThread::unlock_lookup_offset()));
+      ldr(t1_monitor, Address(box, BasicLock::object_monitor_cache_offset_in_bytes()));
+      // TODO: Cleanup these constants (with an enum and asserts)
+      cmp(t1_monitor, (uint8_t)2);
+      // Non symmetrical, take slow path monitor == 0 or 1, 0 and 1 < 2, both LS and NE
+      br(Assembler::LO, slow_path);
+      if (OMCacheHitRate) increment(Address(rthread, JavaThread::unlock_hit_offset()));
+
+      const Register t2_recursions = t2;
+      Label not_recursive;
+
+      // Check if recursive.
+      ldr(t2_recursions, Address(t1_monitor, ObjectMonitor::recursions_offset()));
+      cbz(t2_recursions, not_recursive);
+
+      // Recursive unlock.
+      sub(t2_recursions, t2_recursions, 1u);
+      str(t2_recursions, Address(t1_monitor, ObjectMonitor::recursions_offset()));
+      // Set flag == EQ
+      cmp(t2_recursions, t2_recursions);
+      b(unlocked);
+
+      bind(not_recursive);
+
+      Label release;
+      const Register t2_owner_addr = t2;
+
+      // Compute owner address.
+      lea(t2_owner_addr, Address(t1_monitor, ObjectMonitor::owner_offset()));
+
+      // Check if the entry lists are empty.
+      ldr(rscratch1, Address(t1_monitor, ObjectMonitor::EntryList_offset()));
+      ldr(t3_t, Address(t1_monitor, ObjectMonitor::cxq_offset()));
+      orr(rscratch1, rscratch1, t3_t);
+      cmp(rscratch1, zr);
+      br(Assembler::EQ, release);
+
+      // The owner may be anonymous and we removed the last obj entry in
+      // the lock-stack. This loses the information about the owner.
+      // Write the thread to the owner field so the runtime knows the owner.
+      str(rthread, Address(t2_owner_addr));
+      b(slow_path);
+
+      bind(release);
+      // Set owner to null.
+      // Release to satisfy the JMM
+      stlr(zr, t2_owner_addr);
+    }
   }
 
   bind(unlocked);
   decrement(Address(rthread, JavaThread::held_monitor_count_offset()));
 
@@ -2492,5 +2578,24 @@
       return true;
     }
   }
   return MacroAssembler::in_scratch_emit_size();
 }
+
+void C2_MacroAssembler::load_nklass_compact(Register dst, Register obj, Register index, int scale, int disp) {
+  // Note: Don't clobber obj anywhere in that method!
+
+  // The incoming address is pointing into obj-start + klass_offset_in_bytes. We need to extract
+  // obj-start, so that we can load from the object's mark-word instead. Usually the address
+  // comes as obj-start in obj and klass_offset_in_bytes in disp. However, sometimes C2
+  // emits code that pre-computes obj-start + klass_offset_in_bytes into a register, and
+  // then passes that register as obj and 0 in disp. The following code extracts the base
+  // and offset to load the mark-word.
+  int offset = oopDesc::mark_offset_in_bytes() + disp - oopDesc::klass_offset_in_bytes();
+  if (index == noreg) {
+    ldr(dst, Address(obj, offset));
+  } else {
+    lea(dst, Address(obj, index, Address::lsl(scale)));
+    ldr(dst, Address(dst, offset));
+  }
+  lsr(dst, dst, markWord::klass_shift);
+}
diff a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp
--- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp
+++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp
@@ -37,12 +37,12 @@
  public:
   // Code used by cmpFastLock and cmpFastUnlock mach instructions in .ad file.
   void fast_lock(Register object, Register box, Register tmp, Register tmp2, Register tmp3);
   void fast_unlock(Register object, Register box, Register tmp, Register tmp2);
   // Code used by cmpFastLockLightweight and cmpFastUnlockLightweight mach instructions in .ad file.
-  void fast_lock_lightweight(Register object, Register t1, Register t2, Register t3);
-  void fast_unlock_lightweight(Register object, Register t1, Register t2, Register t3);
+  void fast_lock_lightweight(Register object, Register box, Register t1, Register t2, Register t3);
+  void fast_unlock_lightweight(Register object, Register box, Register t1, Register t2, Register t3);
 
   void string_compare(Register str1, Register str2,
                       Register cnt1, Register cnt2, Register result,
                       Register tmp1, Register tmp2, FloatRegister vtmp1,
                       FloatRegister vtmp2, FloatRegister vtmp3,
@@ -177,6 +177,8 @@
                           FloatRegister one, SIMD_Arrangement T);
 
   void vector_signum_sve(FloatRegister dst, FloatRegister src, FloatRegister zero,
                          FloatRegister one, FloatRegister vtmp, PRegister pgtmp, SIMD_RegVariant T);
 
+  void load_nklass_compact(Register dst, Register obj, Register index, int scale, int disp);
+
 #endif // CPU_AARCH64_C2_MACROASSEMBLER_AARCH64_HPP
diff a/src/hotspot/cpu/aarch64/compressedKlass_aarch64.cpp b/src/hotspot/cpu/aarch64/compressedKlass_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/compressedKlass_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/compressedKlass_aarch64.cpp
@@ -116,18 +116,33 @@
   }
 
   return result;
 }
 
-void CompressedKlassPointers::initialize(address addr, size_t len) {
+bool CompressedKlassPointers::pd_initialize(address addr, size_t len) {
+
+  if (tiny_classpointer_mode()) {
+    // In tiny-classpointer mode, we do what all other platforms do.
+    return false;
+  }
+
+  // Aarch64 uses an own initialization logic that avoids zero-base shifted mode
+  // (_base=0 _shift>0), instead preferring non-zero-based mode with shift=0
   constexpr uintptr_t unscaled_max = nth_bit(32);
   assert(len <= unscaled_max, "Klass range larger than 32 bits?");
 
-  // Shift is always 0 on aarch64.
   _shift = 0;
 
-  // On aarch64, we don't bother with zero-based encoding (base=0 shift>0).
   address const end = addr + len;
   _base = (end <= (address)unscaled_max) ? nullptr : addr;
 
   _range = end - _base;
+
+#ifdef ASSERT
+  _klass_range_start = addr;
+  _klass_range_end = addr + len;
+  calc_lowest_highest_narrow_klass_id();
+  sanity_check_after_initialization();
+#endif
+
+  return true;
 }
diff a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp
@@ -44,10 +44,11 @@
 #include "runtime/basicLock.hpp"
 #include "runtime/frame.inline.hpp"
 #include "runtime/javaThread.hpp"
 #include "runtime/safepointMechanism.hpp"
 #include "runtime/sharedRuntime.hpp"
+#include "utilities/globalDefinitions.hpp"
 #include "utilities/powerOfTwo.hpp"
 
 void InterpreterMacroAssembler::narrow(Register result) {
 
   // Get method->_constMethod->_result_type
@@ -699,11 +700,11 @@
       tstw(tmp, JVM_ACC_IS_VALUE_BASED_CLASS);
       br(Assembler::NE, slow_case);
     }
 
     if (LockingMode == LM_LIGHTWEIGHT) {
-      lightweight_lock(obj_reg, tmp, tmp2, tmp3, slow_case);
+      lightweight_lock(lock_reg, obj_reg, tmp, tmp2, tmp3, slow_case);
       b(count);
     } else if (LockingMode == LM_LEGACY) {
       // Load (object->mark() | 1) into swap_reg
       ldr(rscratch1, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
       orr(swap_reg, rscratch1, 1);
@@ -755,19 +756,13 @@
       br(Assembler::EQ, count);
     }
     bind(slow_case);
 
     // Call the runtime routine for slow case
-    if (LockingMode == LM_LIGHTWEIGHT) {
-      call_VM(noreg,
-              CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter_obj),
-              obj_reg);
-    } else {
-      call_VM(noreg,
-              CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
-              lock_reg);
-    }
+    call_VM(noreg,
+            CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
+            lock_reg);
     b(done);
 
     bind(count);
     increment(Address(rthread, JavaThread::held_monitor_count_offset()));
 
@@ -802,10 +797,11 @@
     const Register tmp_reg    = c_rarg4;  // Temporary used by lightweight_unlock
 
     save_bcp(); // Save in case of exception
 
     if (LockingMode != LM_LIGHTWEIGHT) {
+      // TODO[OMWorld]: Cleanup lock_reg usage for placeholder
       // Convert from BasicObjectLock structure to object and BasicLock
       // structure Store the BasicLock address into %r0
       lea(swap_reg, Address(lock_reg, BasicObjectLock::lock_offset()));
     }
 
diff a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp
@@ -1000,14 +1000,15 @@
   movptr(rscratch2, (intptr_t)Universe::non_oop_word());
   return trampoline_call(Address(entry, rh));
 }
 
 int MacroAssembler::ic_check_size() {
+  int extra_instructions = UseCompactObjectHeaders ? 1 : 0;
   if (target_needs_far_branch(CAST_FROM_FN_PTR(address, SharedRuntime::get_ic_miss_stub()))) {
-    return NativeInstruction::instruction_size * 7;
+    return NativeInstruction::instruction_size * (7 + extra_instructions);
   } else {
-    return NativeInstruction::instruction_size * 5;
+    return NativeInstruction::instruction_size * (5 + extra_instructions);
   }
 }
 
 int MacroAssembler::ic_check(int end_alignment) {
   Register receiver = j_rarg0;
@@ -1021,11 +1022,15 @@
   // before the inline cache check here, and not after
   align(end_alignment, offset() + ic_check_size());
 
   int uep_offset = offset();
 
-  if (UseCompressedClassPointers) {
+  if (UseCompactObjectHeaders) {
+    load_nklass_compact(tmp1, receiver);
+    ldrw(tmp2, Address(data, CompiledICData::speculated_klass_offset()));
+    cmpw(tmp1, tmp2);
+  } else if (UseCompressedClassPointers) {
     ldrw(tmp1, Address(receiver, oopDesc::klass_offset_in_bytes()));
     ldrw(tmp2, Address(data, CompiledICData::speculated_klass_offset()));
     cmpw(tmp1, tmp2);
   } else {
     ldr(tmp1, Address(receiver, oopDesc::klass_offset_in_bytes()));
@@ -4476,12 +4481,26 @@
   ldr(holder, Address(method, Method::const_offset()));                      // ConstMethod*
   ldr(holder, Address(holder, ConstMethod::constants_offset()));             // ConstantPool*
   ldr(holder, Address(holder, ConstantPool::pool_holder_offset()));          // InstanceKlass*
 }
 
+// Loads the obj's Klass* into dst.
+// Preserves all registers (incl src, rscratch1 and rscratch2).
+// Input:
+// src - the oop we want to load the klass from.
+// dst - output nklass.
+void MacroAssembler::load_nklass_compact(Register dst, Register src) {
+  assert(UseCompactObjectHeaders, "expects UseCompactObjectHeaders");
+  ldr(dst, Address(src, oopDesc::mark_offset_in_bytes()));
+  lsr(dst, dst, markWord::klass_shift);
+}
+
 void MacroAssembler::load_klass(Register dst, Register src) {
-  if (UseCompressedClassPointers) {
+  if (UseCompactObjectHeaders) {
+    load_nklass_compact(dst, src);
+    decode_klass_not_null(dst);
+  } else if (UseCompressedClassPointers) {
     ldrw(dst, Address(src, oopDesc::klass_offset_in_bytes()));
     decode_klass_not_null(dst);
   } else {
     ldr(dst, Address(src, oopDesc::klass_offset_in_bytes()));
   }
@@ -4533,12 +4552,17 @@
   ldr(dst, Address(dst, mirror_offset));
   resolve_oop_handle(dst, tmp1, tmp2);
 }
 
 void MacroAssembler::cmp_klass(Register oop, Register trial_klass, Register tmp) {
+  assert_different_registers(oop, trial_klass, tmp);
   if (UseCompressedClassPointers) {
-    ldrw(tmp, Address(oop, oopDesc::klass_offset_in_bytes()));
+    if (UseCompactObjectHeaders) {
+      load_nklass_compact(tmp, oop);
+    } else {
+      ldrw(tmp, Address(oop, oopDesc::klass_offset_in_bytes()));
+    }
     if (CompressedKlassPointers::base() == nullptr) {
       cmp(trial_klass, tmp, LSL, CompressedKlassPointers::shift());
       return;
     } else if (((uint64_t)CompressedKlassPointers::base() & 0xffffffff) == 0
                && CompressedKlassPointers::shift() == 0) {
@@ -4551,22 +4575,40 @@
     ldr(tmp, Address(oop, oopDesc::klass_offset_in_bytes()));
   }
   cmp(trial_klass, tmp);
 }
 
+void MacroAssembler::cmp_klass(Register src, Register dst, Register tmp1, Register tmp2) {
+  if (UseCompactObjectHeaders) {
+    load_nklass_compact(tmp1, src);
+    load_nklass_compact(tmp2, dst);
+    cmpw(tmp1, tmp2);
+  } else if (UseCompressedClassPointers) {
+    ldrw(tmp1, Address(src, oopDesc::klass_offset_in_bytes()));
+    ldrw(tmp2, Address(dst, oopDesc::klass_offset_in_bytes()));
+    cmpw(tmp1, tmp2);
+  } else {
+    ldr(tmp1, Address(src, oopDesc::klass_offset_in_bytes()));
+    ldr(tmp2, Address(dst, oopDesc::klass_offset_in_bytes()));
+    cmp(tmp1, tmp2);
+  }
+}
+
 void MacroAssembler::store_klass(Register dst, Register src) {
   // FIXME: Should this be a store release?  concurrent gcs assumes
   // klass length is valid if klass field is not null.
+  assert(!UseCompactObjectHeaders, "not with compact headers");
   if (UseCompressedClassPointers) {
     encode_klass_not_null(src);
     strw(src, Address(dst, oopDesc::klass_offset_in_bytes()));
   } else {
     str(src, Address(dst, oopDesc::klass_offset_in_bytes()));
   }
 }
 
 void MacroAssembler::store_klass_gap(Register dst, Register src) {
+  assert(!UseCompactObjectHeaders, "not with compact headers");
   if (UseCompressedClassPointers) {
     // Store to klass gap in destination
     strw(src, Address(dst, oopDesc::klass_gap_offset_in_bytes()));
   }
 }
@@ -4711,13 +4753,10 @@
 
   if (_klass_decode_mode != KlassDecodeNone) {
     return _klass_decode_mode;
   }
 
-  assert(LogKlassAlignmentInBytes == CompressedKlassPointers::shift()
-         || 0 == CompressedKlassPointers::shift(), "decode alg wrong");
-
   if (CompressedKlassPointers::base() == nullptr) {
     return (_klass_decode_mode = KlassDecodeZero);
   }
 
   if (operand_valid_for_logical_immediate(
@@ -4739,28 +4778,28 @@
 
 void MacroAssembler::encode_klass_not_null(Register dst, Register src) {
   switch (klass_decode_mode()) {
   case KlassDecodeZero:
     if (CompressedKlassPointers::shift() != 0) {
-      lsr(dst, src, LogKlassAlignmentInBytes);
+      lsr(dst, src, CompressedKlassPointers::shift());
     } else {
       if (dst != src) mov(dst, src);
     }
     break;
 
   case KlassDecodeXor:
     if (CompressedKlassPointers::shift() != 0) {
       eor(dst, src, (uint64_t)CompressedKlassPointers::base());
-      lsr(dst, dst, LogKlassAlignmentInBytes);
+      lsr(dst, dst, CompressedKlassPointers::shift());
     } else {
       eor(dst, src, (uint64_t)CompressedKlassPointers::base());
     }
     break;
 
   case KlassDecodeMovk:
     if (CompressedKlassPointers::shift() != 0) {
-      ubfx(dst, src, LogKlassAlignmentInBytes, 32);
+      ubfx(dst, src, CompressedKlassPointers::shift(), 32);
     } else {
       movw(dst, src);
     }
     break;
 
@@ -4778,19 +4817,19 @@
   assert (UseCompressedClassPointers, "should only be used for compressed headers");
 
   switch (klass_decode_mode()) {
   case KlassDecodeZero:
     if (CompressedKlassPointers::shift() != 0) {
-      lsl(dst, src, LogKlassAlignmentInBytes);
+      lsl(dst, src, CompressedKlassPointers::shift());
     } else {
       if (dst != src) mov(dst, src);
     }
     break;
 
   case KlassDecodeXor:
     if (CompressedKlassPointers::shift() != 0) {
-      lsl(dst, src, LogKlassAlignmentInBytes);
+      lsl(dst, src, CompressedKlassPointers::shift());
       eor(dst, dst, (uint64_t)CompressedKlassPointers::base());
     } else {
       eor(dst, src, (uint64_t)CompressedKlassPointers::base());
     }
     break;
@@ -4801,11 +4840,11 @@
 
     if (dst != src) movw(dst, src);
     movk(dst, shifted_base >> 32, 32);
 
     if (CompressedKlassPointers::shift() != 0) {
-      lsl(dst, dst, LogKlassAlignmentInBytes);
+      lsl(dst, dst, CompressedKlassPointers::shift());
     }
 
     break;
   }
 
@@ -5201,10 +5240,18 @@
   int elem_per_word = wordSize/elem_size;
   int log_elem_size = exact_log2(elem_size);
   int length_offset = arrayOopDesc::length_offset_in_bytes();
   int base_offset
     = arrayOopDesc::base_offset_in_bytes(elem_size == 2 ? T_CHAR : T_BYTE);
+  // When the base is not aligned to 8 bytes, then we let
+  // the compare loop include the array length, and skip
+  // the explicit comparison of length.
+  bool is_8aligned = is_aligned(base_offset, BytesPerWord);
+  assert(is_aligned(base_offset, BytesPerWord) || is_aligned(length_offset, BytesPerWord),
+         "base_offset or length_offset must be 8-byte aligned");
+  int start_offset = is_8aligned ? base_offset : length_offset;
+  int extra_length = is_8aligned ? 0 : BytesPerInt / elem_size;
   int stubBytesThreshold = 3 * 64 + (UseSIMDForArrayEquals ? 0 : 16);
 
   assert(elem_size == 1 || elem_size == 2, "must be char or byte");
   assert_different_registers(a1, a2, result, cnt1, rscratch1, rscratch2);
 
@@ -5235,14 +5282,19 @@
     // if (a1.length != a2.length)
     //      return false;
     bind(A_IS_NOT_NULL);
     ldrw(cnt1, Address(a1, length_offset));
     ldrw(cnt2, Address(a2, length_offset));
+    if (extra_length != 0) {
+      // Increase loop counter by size of length field.
+      addw(cnt1, cnt1, extra_length);
+      addw(cnt2, cnt2, extra_length);
+    }
     eorw(tmp5, cnt1, cnt2);
     cbnzw(tmp5, DONE);
-    lea(a1, Address(a1, base_offset));
-    lea(a2, Address(a2, base_offset));
+    lea(a1, Address(a1, start_offset));
+    lea(a2, Address(a2, start_offset));
     // Check for short strings, i.e. smaller than wordSize.
     subs(cnt1, cnt1, elem_per_word);
     br(Assembler::LT, SHORT);
     // Main 8 byte comparison loop.
     bind(NEXT_WORD); {
@@ -5302,18 +5354,23 @@
     mov(result, false);
     cbz(a1, DONE);
     ldrw(cnt1, Address(a1, length_offset));
     cbz(a2, DONE);
     ldrw(cnt2, Address(a2, length_offset));
+    if (extra_length != 0) {
+      // Increase loop counter by size of length field.
+      addw(cnt1, cnt1, extra_length);
+      addw(cnt2, cnt2, extra_length);
+    }
     // on most CPUs a2 is still "locked"(surprisingly) in ldrw and it's
     // faster to perform another branch before comparing a1 and a2
     cmp(cnt1, (u1)elem_per_word);
     br(LE, SHORT); // short or same
-    ldr(tmp3, Address(pre(a1, base_offset)));
+    ldr(tmp3, Address(pre(a1, start_offset)));
     subs(zr, cnt1, stubBytesThreshold);
     br(GE, STUB);
-    ldr(tmp4, Address(pre(a2, base_offset)));
+    ldr(tmp4, Address(pre(a2, start_offset)));
     sub(tmp5, zr, cnt1, LSL, 3 + log_elem_size);
     cmp(cnt2, cnt1);
     br(NE, DONE);
 
     // Main 16 byte comparison loop with 2 exits
@@ -5344,11 +5401,11 @@
     eor(tmp2, tmp1, tmp2);
     cbnz(tmp2, DONE);
     b(LAST_CHECK);
 
     bind(STUB);
-    ldr(tmp4, Address(pre(a2, base_offset)));
+    ldr(tmp4, Address(pre(a2, start_offset)));
     cmp(cnt2, cnt1);
     br(NE, DONE);
     if (elem_size == 2) { // convert to byte counter
       lsl(cnt1, cnt1, 1);
     }
@@ -5371,12 +5428,12 @@
     bind(SHORT);
     cmp(cnt2, cnt1);
     br(NE, DONE);
     cbz(cnt1, SAME);
     sub(tmp5, zr, cnt1, LSL, 3 + log_elem_size);
-    ldr(tmp3, Address(a1, base_offset));
-    ldr(tmp4, Address(a2, base_offset));
+    ldr(tmp3, Address(pre(a1, start_offset)));
+    ldr(tmp4, Address(pre(a2, start_offset)));
     bind(LAST_CHECK);
     eor(tmp4, tmp3, tmp4);
     lslv(tmp5, tmp4, tmp5);
     cmp(tmp5, zr);
     bind(CSET_EQ);
@@ -6384,23 +6441,25 @@
 // Implements lightweight-locking.
 //
 //  - obj: the object to be locked
 //  - t1, t2, t3: temporary registers, will be destroyed
 //  - slow: branched to if locking fails, absolute offset may larger than 32KB (imm14 encoding).
-void MacroAssembler::lightweight_lock(Register obj, Register t1, Register t2, Register t3, Label& slow) {
+void MacroAssembler::lightweight_lock(Register basic_lock, Register obj, Register t1, Register t2, Register t3, Label& slow) {
   assert(LockingMode == LM_LIGHTWEIGHT, "only used with new lightweight locking");
-  assert_different_registers(obj, t1, t2, t3, rscratch1);
+  assert_different_registers(basic_lock, obj, t1, t2, t3, rscratch1);
 
   Label push;
   const Register top = t1;
   const Register mark = t2;
   const Register t = t3;
 
   // Preload the markWord. It is important that this is the first
   // instruction emitted as it is part of C1's null check semantics.
   ldr(mark, Address(obj, oopDesc::mark_offset_in_bytes()));
 
+  str(zr, Address(basic_lock, BasicObjectLock::lock_offset() + in_ByteSize((BasicLock::object_monitor_cache_offset_in_bytes()))));
+
   // Check if the lock-stack is full.
   ldrw(top, Address(rthread, JavaThread::lock_stack_top_offset()));
   cmpw(top, (unsigned)LockStack::end_offset());
   br(Assembler::GE, slow);
 
diff a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp
--- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp
+++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp
@@ -862,13 +862,15 @@
 
   void load_method_holder_cld(Register rresult, Register rmethod);
   void load_method_holder(Register holder, Register method);
 
   // oop manipulations
+  void load_nklass_compact(Register dst, Register src);
   void load_klass(Register dst, Register src);
   void store_klass(Register dst, Register src);
   void cmp_klass(Register oop, Register trial_klass, Register tmp);
+  void cmp_klass(Register src, Register dst, Register tmp1, Register tmp2);
 
   void resolve_weak_handle(Register result, Register tmp1, Register tmp2);
   void resolve_oop_handle(Register result, Register tmp1, Register tmp2);
   void load_mirror(Register dst, Register method, Register tmp1, Register tmp2);
 
@@ -1600,11 +1602,11 @@
   void cache_wbsync(bool is_pre);
 
   // Code for java.lang.Thread::onSpinWait() intrinsic.
   void spin_wait();
 
-  void lightweight_lock(Register obj, Register t1, Register t2, Register t3, Label& slow);
+  void lightweight_lock(Register basic_lock, Register obj, Register t1, Register t2, Register t3, Label& slow);
   void lightweight_unlock(Register obj, Register t1, Register t2, Register t3, Label& slow);
 
 private:
   // Check the current thread doesn't need a cross modify fence.
   void verify_cross_modify_fence_not_required() PRODUCT_RETURN;
diff a/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp b/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp
@@ -1792,11 +1792,11 @@
       // Save the test result, for recursive case, the result is zero
       __ str(swap_reg, Address(lock_reg, mark_word_offset));
       __ br(Assembler::NE, slow_path_lock);
     } else {
       assert(LockingMode == LM_LIGHTWEIGHT, "must be");
-      __ lightweight_lock(obj_reg, swap_reg, tmp, lock_tmp, slow_path_lock);
+      __ lightweight_lock(lock_reg, obj_reg, swap_reg, tmp, lock_tmp, slow_path_lock);
     }
     __ bind(count);
     __ increment(Address(rthread, JavaThread::held_monitor_count_offset()));
 
     // Slow path will re-enter here
diff a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp
@@ -3631,30 +3631,44 @@
       __ b(initialize_header);
     }
 
     // The object is initialized before the header.  If the object size is
     // zero, go directly to the header initialization.
-    __ sub(r3, r3, sizeof(oopDesc));
+    if (UseCompactObjectHeaders) {
+      assert(is_aligned(oopDesc::base_offset_in_bytes(), BytesPerLong), "oop base offset must be 8-byte-aligned");
+      __ sub(r3, r3, oopDesc::base_offset_in_bytes());
+    } else {
+      __ sub(r3, r3, sizeof(oopDesc));
+    }
     __ cbz(r3, initialize_header);
 
     // Initialize object fields
     {
-      __ add(r2, r0, sizeof(oopDesc));
+      if (UseCompactObjectHeaders) {
+        assert(is_aligned(oopDesc::base_offset_in_bytes(), BytesPerLong), "oop base offset must be 8-byte-aligned");
+        __ add(r2, r0, oopDesc::base_offset_in_bytes());
+      } else {
+        __ add(r2, r0, sizeof(oopDesc));
+      }
       Label loop;
       __ bind(loop);
       __ str(zr, Address(__ post(r2, BytesPerLong)));
       __ sub(r3, r3, BytesPerLong);
       __ cbnz(r3, loop);
     }
 
     // initialize object header only.
     __ bind(initialize_header);
-    __ mov(rscratch1, (intptr_t)markWord::prototype().value());
-    __ str(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes()));
-    __ store_klass_gap(r0, zr);  // zero klass gap for compressed oops
-    __ store_klass(r0, r4);      // store klass last
-
+    if (UseCompactObjectHeaders) {
+      __ ldr(rscratch1, Address(r4, Klass::prototype_header_offset()));
+      __ str(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes()));
+    } else {
+      __ mov(rscratch1, (intptr_t)markWord::prototype().value());
+      __ str(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes()));
+      __ store_klass_gap(r0, zr);  // zero klass gap for compressed oops
+      __ store_klass(r0, r4);      // store klass last
+    }
     {
       SkipIfEqual skip(_masm, &DTraceAllocProbes, false);
       // Trigger dtrace event for fastpath
       __ push(atos); // save the return value
       __ call_VM_leaf(
diff a/src/hotspot/cpu/arm/c1_CodeStubs_arm.cpp b/src/hotspot/cpu/arm/c1_CodeStubs_arm.cpp
--- a/src/hotspot/cpu/arm/c1_CodeStubs_arm.cpp
+++ b/src/hotspot/cpu/arm/c1_CodeStubs_arm.cpp
@@ -215,11 +215,10 @@
                              Runtime1::monitorexit_nofpu_id;
   __ call(Runtime1::entry_for(exit_id), relocInfo::runtime_call_type);
   __ b(_continuation);
 }
 
-
 // Call return is directly after patch word
 int PatchingStub::_patch_info_offset = 0;
 
 void PatchingStub::align_patch_site(MacroAssembler* masm) {
 #if 0
diff a/src/hotspot/cpu/arm/interp_masm_arm.cpp b/src/hotspot/cpu/arm/interp_masm_arm.cpp
--- a/src/hotspot/cpu/arm/interp_masm_arm.cpp
+++ b/src/hotspot/cpu/arm/interp_masm_arm.cpp
@@ -988,19 +988,11 @@
     }
 
     bind(slow_case);
 
     // Call the runtime routine for slow case
-    if (LockingMode == LM_LIGHTWEIGHT) {
-      // Pass oop, not lock, in fast lock case. call_VM wants R1 though.
-      push(R1);
-      mov(R1, Robj);
-      call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter_obj), R1);
-      pop(R1);
-    } else {
-      call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), Rlock);
-    }
+    call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), Rlock);
     bind(done);
   }
 }
 
 // Unlocks an object. Used in monitorexit bytecode and remove_activation.
diff a/src/hotspot/cpu/ppc/c1_CodeStubs_ppc.cpp b/src/hotspot/cpu/ppc/c1_CodeStubs_ppc.cpp
--- a/src/hotspot/cpu/ppc/c1_CodeStubs_ppc.cpp
+++ b/src/hotspot/cpu/ppc/c1_CodeStubs_ppc.cpp
@@ -296,11 +296,10 @@
   __ mtctr(R0);
   __ bctrl();
   __ b(_continuation);
 }
 
-
 // Implementation of patching:
 // - Copy the code at given offset to an inlined buffer (first the bytes, then the number of bytes).
 // - Replace original code with a call to the stub.
 // At Runtime:
 // - call to stub, jump to runtime
diff a/src/hotspot/cpu/ppc/compressedKlass_ppc.cpp b/src/hotspot/cpu/ppc/compressedKlass_ppc.cpp
--- a/src/hotspot/cpu/ppc/compressedKlass_ppc.cpp
+++ b/src/hotspot/cpu/ppc/compressedKlass_ppc.cpp
@@ -44,5 +44,7 @@
     result = reserve_address_space_for_16bit_move(size, aslr);
   }
 
   return result;
 }
+
+bool CompressedKlassPointers::pd_initialize(address addr, size_t len) { return false; }
diff a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp
--- a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp
+++ b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp
@@ -1043,15 +1043,11 @@
     //   InterpreterRuntime::monitorenter(THREAD, monitor);
 
     // None of the above fast optimizations worked so we have to get into the
     // slow case of monitor enter.
     bind(slow_case);
-    if (LockingMode == LM_LIGHTWEIGHT) {
-      call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter_obj), object);
-    } else {
-      call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), monitor);
-    }
+    call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), monitor);
     b(done);
     // }
     align(32, 12);
     bind(count_locking);
     inc_held_monitor_count(current_header /*tmp*/);
diff a/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.cpp
--- a/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.cpp
+++ b/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.cpp
@@ -165,11 +165,11 @@
 void C1_MacroAssembler::initialize_header(Register obj, Register klass, Register len, Register tmp1, Register tmp2) {
   assert_different_registers(obj, klass, len, tmp1, tmp2);
   // This assumes that all prototype bits fitr in an int32_t
   mv(tmp1, (int32_t)(intptr_t)markWord::prototype().value());
   sd(tmp1, Address(obj, oopDesc::mark_offset_in_bytes()));
-
+// Todo UseCompactObjectHeaders
   if (UseCompressedClassPointers) { // Take care not to kill klass
     encode_klass_not_null(tmp1, klass, tmp2);
     sw(tmp1, Address(obj, oopDesc::klass_offset_in_bytes()));
   } else {
     sd(klass, Address(obj, oopDesc::klass_offset_in_bytes()));
diff a/src/hotspot/cpu/riscv/compressedKlass_riscv.cpp b/src/hotspot/cpu/riscv/compressedKlass_riscv.cpp
--- a/src/hotspot/cpu/riscv/compressedKlass_riscv.cpp
+++ b/src/hotspot/cpu/riscv/compressedKlass_riscv.cpp
@@ -54,13 +54,13 @@
   // Failing that, attempt to reserve for base=zero shift>0
   if (result == nullptr && optimize_for_zero_base) {
     result = reserve_address_space_for_zerobased_encoding(size, aslr);
   }
 
-  // Failing that, optimize for case (3) - a base with only bits set between [33-44)
+  // Failing that, optimize for case (3) - a base with only bits set between [32-44)
   if (result == nullptr) {
-    const uintptr_t from = nth_bit(32 + (optimize_for_zero_base ? LogKlassAlignmentInBytes : 0));
+    const uintptr_t from = nth_bit(32);
     constexpr uintptr_t to = nth_bit(44);
     constexpr size_t alignment = nth_bit(32);
     result = reserve_address_space_X(from, to, size, alignment, aslr);
   }
 
@@ -72,5 +72,7 @@
     result = reserve_address_space_X(from, to, size, alignment, aslr);
   }
 
   return result;
 }
+
+bool CompressedKlassPointers::pd_initialize(address addr, size_t len) { return false; }
diff a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp
--- a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp
+++ b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp
@@ -797,19 +797,13 @@
     }
 
     bind(slow_case);
 
     // Call the runtime routine for slow case
-    if (LockingMode == LM_LIGHTWEIGHT) {
-      call_VM(noreg,
-              CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter_obj),
-              obj_reg);
-    } else {
-      call_VM(noreg,
-              CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
-              lock_reg);
-    }
+    call_VM(noreg,
+            CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
+            lock_reg);
     j(done);
 
     bind(count);
     increment(Address(xthread, JavaThread::held_monitor_count_offset()));
 
diff a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp
--- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp
+++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp
@@ -2287,12 +2287,11 @@
 void MacroAssembler::decode_klass_not_null(Register dst, Register src, Register tmp) {
   assert(UseCompressedClassPointers, "should only be used for compressed headers");
 
   if (CompressedKlassPointers::base() == nullptr) {
     if (CompressedKlassPointers::shift() != 0) {
-      assert(LogKlassAlignmentInBytes == CompressedKlassPointers::shift(), "decode alg wrong");
-      slli(dst, src, LogKlassAlignmentInBytes);
+      slli(dst, src, CompressedKlassPointers::shift());
     } else {
       mv(dst, src);
     }
     return;
   }
@@ -2304,13 +2303,12 @@
 
   assert_different_registers(src, xbase);
   mv(xbase, (uintptr_t)CompressedKlassPointers::base());
 
   if (CompressedKlassPointers::shift() != 0) {
-    assert(LogKlassAlignmentInBytes == CompressedKlassPointers::shift(), "decode alg wrong");
     assert_different_registers(t0, xbase);
-    shadd(dst, src, xbase, t0, LogKlassAlignmentInBytes);
+    shadd(dst, src, xbase, t0, CompressedKlassPointers::shift());
   } else {
     add(dst, xbase, src);
   }
 }
 
@@ -2322,12 +2320,11 @@
 void MacroAssembler::encode_klass_not_null(Register dst, Register src, Register tmp) {
   assert(UseCompressedClassPointers, "should only be used for compressed headers");
 
   if (CompressedKlassPointers::base() == nullptr) {
     if (CompressedKlassPointers::shift() != 0) {
-      assert(LogKlassAlignmentInBytes == CompressedKlassPointers::shift(), "decode alg wrong");
-      srli(dst, src, LogKlassAlignmentInBytes);
+      srli(dst, src, CompressedKlassPointers::shift());
     } else {
       mv(dst, src);
     }
     return;
   }
@@ -2345,12 +2342,11 @@
 
   assert_different_registers(src, xbase);
   mv(xbase, (uintptr_t)CompressedKlassPointers::base());
   sub(dst, src, xbase);
   if (CompressedKlassPointers::shift() != 0) {
-    assert(LogKlassAlignmentInBytes == CompressedKlassPointers::shift(), "decode alg wrong");
-    srli(dst, dst, LogKlassAlignmentInBytes);
+    srli(dst, dst, CompressedKlassPointers::shift());
   }
 }
 
 void MacroAssembler::decode_heap_oop_not_null(Register r) {
   decode_heap_oop_not_null(r, r);
diff a/src/hotspot/cpu/s390/compressedKlass_s390.cpp b/src/hotspot/cpu/s390/compressedKlass_s390.cpp
--- a/src/hotspot/cpu/s390/compressedKlass_s390.cpp
+++ b/src/hotspot/cpu/s390/compressedKlass_s390.cpp
@@ -48,5 +48,7 @@
     result = reserve_address_space_for_16bit_move(size, aslr);
   }
 
   return result;
 }
+
+bool CompressedKlassPointers::pd_initialize(address addr, size_t len) { return false; }
diff a/src/hotspot/cpu/s390/interp_masm_s390.cpp b/src/hotspot/cpu/s390/interp_masm_s390.cpp
--- a/src/hotspot/cpu/s390/interp_masm_s390.cpp
+++ b/src/hotspot/cpu/s390/interp_masm_s390.cpp
@@ -1076,20 +1076,13 @@
   //   InterpreterRuntime::monitorenter(THREAD, monitor);
 
   // None of the above fast optimizations worked so we have to get into the
   // slow case of monitor enter.
   bind(slow_case);
-  if (LockingMode == LM_LIGHTWEIGHT) {
-    // for lightweight locking we need to use monitorenter_obj, see interpreterRuntime.cpp
-    call_VM(noreg,
-            CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter_obj),
-            object);
-  } else {
-    call_VM(noreg,
-            CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
-            monitor);
-  }
+  call_VM(noreg,
+          CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
+          monitor);
   // }
 
   bind(done);
 }
 
diff a/src/hotspot/cpu/s390/macroAssembler_s390.cpp b/src/hotspot/cpu/s390/macroAssembler_s390.cpp
--- a/src/hotspot/cpu/s390/macroAssembler_s390.cpp
+++ b/src/hotspot/cpu/s390/macroAssembler_s390.cpp
@@ -3525,11 +3525,11 @@
 
   BLOCK_COMMENT("cKlass encoder {");
 
 #ifdef ASSERT
   Label ok;
-  z_tmll(current, KlassAlignmentInBytes-1); // Check alignment.
+  z_tmll(current, CompressedKlassPointers::klass_alignment_in_bytes() - 1); // Check alignment.
   z_brc(Assembler::bcondAllZero, ok);
   // The plain disassembler does not recognize illtrap. It instead displays
   // a 32-bit value. Issuing two illtraps assures the disassembler finds
   // the proper beginning of the next instruction.
   z_illtrap(0xee);
@@ -3539,11 +3539,10 @@
 
   // Scale down the incoming klass pointer first.
   // We then can be sure we calculate an offset that fits into 32 bit.
   // More generally speaking: all subsequent calculations are purely 32-bit.
   if (shift != 0) {
-    assert (LogKlassAlignmentInBytes == shift, "decode alg wrong");
     z_srlg(dst, current, shift);
     current = dst;
   }
 
   if (base != nullptr) {
@@ -3669,11 +3668,11 @@
     }
   }
 
 #ifdef ASSERT
   Label ok;
-  z_tmll(dst, KlassAlignmentInBytes-1); // Check alignment.
+  z_tmll(dst, CompressedKlassPointers::klass_alignment_in_bytes() - 1); // Check alignment.
   z_brc(Assembler::bcondAllZero, ok);
   // The plain disassembler does not recognize illtrap. It instead displays
   // a 32-bit value. Issuing two illtraps assures the disassembler finds
   // the proper beginning of the next instruction.
   z_illtrap(0xd1);
@@ -3716,11 +3715,11 @@
     }
   }
 
 #ifdef ASSERT
   Label ok;
-  z_tmll(dst, KlassAlignmentInBytes-1); // Check alignment.
+  z_tmll(dst, CompressedKlassPointers::klass_alignment_in_bytes() - 1); // Check alignment.
   z_brc(Assembler::bcondAllZero, ok);
   // The plain disassembler does not recognize illtrap. It instead displays
   // a 32-bit value. Issuing two illtraps assures the disassembler finds
   // the proper beginning of the next instruction.
   z_illtrap(0xd2);
@@ -3785,11 +3784,15 @@
 
   if (UseCompressedClassPointers) {
     const int shift = CompressedKlassPointers::shift();
     address   base  = CompressedKlassPointers::base();
 
-    assert((shift == 0) || (shift == LogKlassAlignmentInBytes), "cKlass encoder detected bad shift");
+    if (CompressedKlassPointers::tiny_classpointer_mode()) {
+      assert(shift >= 3, "cKlass encoder detected bad shift");
+    } else {
+      assert((shift == 0) || (shift == 3), "cKlass encoder detected bad shift");
+    }
     assert_different_registers(Rop1, Z_R0);
     assert_different_registers(Rop1, Rbase, Z_R1);
 
     // First encode register oop and then compare with cOop in memory.
     // This sequence saves an unnecessary cOop load and decode.
diff a/src/hotspot/cpu/x86/c1_CodeStubs_x86.cpp b/src/hotspot/cpu/x86/c1_CodeStubs_x86.cpp
--- a/src/hotspot/cpu/x86/c1_CodeStubs_x86.cpp
+++ b/src/hotspot/cpu/x86/c1_CodeStubs_x86.cpp
@@ -28,10 +28,11 @@
 #include "c1/c1_LIRAssembler.hpp"
 #include "c1/c1_MacroAssembler.hpp"
 #include "c1/c1_Runtime1.hpp"
 #include "classfile/javaClasses.hpp"
 #include "nativeInst_x86.hpp"
+#include "runtime/objectMonitor.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "utilities/align.hpp"
 #include "utilities/macros.hpp"
 #include "vmreg_x86.inline.hpp"
 
@@ -276,11 +277,10 @@
   }
   __ call(RuntimeAddress(Runtime1::entry_for(exit_id)));
   __ jmp(_continuation);
 }
 
-
 // Implementation of patching:
 // - Copy the code at given offset to an inlined buffer (first the bytes, then the number of bytes)
 // - Replace original code with a call to the stub
 // At Runtime:
 // - call to stub, jump to runtime
diff a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp
--- a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp
+++ b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp
@@ -3045,10 +3045,11 @@
   Register src_pos = op->src_pos()->as_register();
   Register dst_pos = op->dst_pos()->as_register();
   Register length  = op->length()->as_register();
   Register tmp = op->tmp()->as_register();
   Register tmp_load_klass = LP64_ONLY(rscratch1) NOT_LP64(noreg);
+  Register tmp2 = UseCompactObjectHeaders ? rscratch2 : noreg;
 
   CodeStub* stub = op->stub();
   int flags = op->flags();
   BasicType basic_type = default_type != nullptr ? default_type->element_type()->basic_type() : T_ILLEGAL;
   if (is_reference_type(basic_type)) basic_type = T_OBJECT;
@@ -3169,12 +3170,10 @@
       ShouldNotReachHere();
   }
 
   Address src_length_addr = Address(src, arrayOopDesc::length_offset_in_bytes());
   Address dst_length_addr = Address(dst, arrayOopDesc::length_offset_in_bytes());
-  Address src_klass_addr = Address(src, oopDesc::klass_offset_in_bytes());
-  Address dst_klass_addr = Address(dst, oopDesc::klass_offset_in_bytes());
 
   // length and pos's are all sign extended at this point on 64bit
 
   // test for null
   if (flags & LIR_OpArrayCopy::src_null_check) {
@@ -3236,17 +3235,11 @@
 
   if (flags & LIR_OpArrayCopy::type_check) {
     // We don't know the array types are compatible
     if (basic_type != T_OBJECT) {
       // Simple test for basic type arrays
-      if (UseCompressedClassPointers) {
-        __ movl(tmp, src_klass_addr);
-        __ cmpl(tmp, dst_klass_addr);
-      } else {
-        __ movptr(tmp, src_klass_addr);
-        __ cmpptr(tmp, dst_klass_addr);
-      }
+      __ cmp_klass(src, dst, tmp, tmp2);
       __ jcc(Assembler::notEqual, *stub->entry());
     } else {
       // For object arrays, if src is a sub class of dst then we can
       // safely do the copy.
       Label cont, slow;
@@ -3301,10 +3294,11 @@
        store_parameter(length, 2);
        store_parameter(src_pos, 3);
        store_parameter(src, 4);
 
 #ifndef _LP64
+       Address dst_klass_addr = Address(dst, oopDesc::klass_offset_in_bytes());
         __ movptr(tmp, dst_klass_addr);
         __ movptr(tmp, Address(tmp, ObjArrayKlass::element_klass_offset()));
         __ push(tmp);
         __ movl(tmp, Address(tmp, Klass::super_check_offset_offset()));
         __ push(tmp);
@@ -3404,20 +3398,16 @@
       __ encode_klass_not_null(tmp, rscratch1);
     }
 #endif
 
     if (basic_type != T_OBJECT) {
-
-      if (UseCompressedClassPointers)          __ cmpl(tmp, dst_klass_addr);
-      else                   __ cmpptr(tmp, dst_klass_addr);
+      __ cmp_klass(tmp, dst, tmp2);
       __ jcc(Assembler::notEqual, halt);
-      if (UseCompressedClassPointers)          __ cmpl(tmp, src_klass_addr);
-      else                   __ cmpptr(tmp, src_klass_addr);
+      __ cmp_klass(tmp, src, tmp2);
       __ jcc(Assembler::equal, known_ok);
     } else {
-      if (UseCompressedClassPointers)          __ cmpl(tmp, dst_klass_addr);
-      else                   __ cmpptr(tmp, dst_klass_addr);
+      __ cmp_klass(tmp, dst, tmp2);
       __ jcc(Assembler::equal, known_ok);
       __ cmpptr(src, dst);
       __ jcc(Assembler::equal, known_ok);
     }
     __ bind(halt);
@@ -3509,16 +3499,26 @@
   if (info != nullptr) {
     add_debug_info_for_null_check_here(info);
   }
 
 #ifdef _LP64
-  if (UseCompressedClassPointers) {
+  if (UseCompactObjectHeaders) {
+    Register tmp = rscratch1;
+    assert_different_registers(tmp, obj);
+    assert_different_registers(tmp, result);
+
+    __ movq(result, Address(obj, oopDesc::mark_offset_in_bytes()));
+    __ shrq(result, markWord::klass_shift);
+    __ decode_klass_not_null(result, tmp);
+  } else if (UseCompressedClassPointers) {
     __ movl(result, Address(obj, oopDesc::klass_offset_in_bytes()));
     __ decode_klass_not_null(result, rscratch1);
   } else
 #endif
+  {
     __ movptr(result, Address(obj, oopDesc::klass_offset_in_bytes()));
+  }
 }
 
 void LIR_Assembler::emit_profile_call(LIR_OpProfileCall* op) {
   ciMethod* method = op->profiled_method();
   int bci          = op->profiled_bci();
diff a/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp
--- a/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp
+++ b/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp
@@ -39,10 +39,11 @@
 #include "runtime/os.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "runtime/stubRoutines.hpp"
 #include "utilities/checkedCast.hpp"
 #include "utilities/globalDefinitions.hpp"
+#include "utilities/macros.hpp"
 
 int C1_MacroAssembler::lock_object(Register hdr, Register obj, Register disp_hdr, Register tmp, Label& slow_case) {
   const int aligned_mask = BytesPerWord -1;
   const int hdr_offset = oopDesc::mark_offset_in_bytes();
   assert(hdr == rax, "hdr must be rax, for the cmpxchg instruction");
@@ -64,15 +65,17 @@
   }
 
   if (LockingMode == LM_LIGHTWEIGHT) {
 #ifdef _LP64
     const Register thread = r15_thread;
+    lightweight_lock(disp_hdr, obj, hdr, thread, tmp, slow_case);
 #else
-    const Register thread = disp_hdr;
-    get_thread(thread);
+    // Implicit null check.
+    movptr(hdr, Address(obj, oopDesc::mark_offset_in_bytes()));
+    // Lacking registers and thread on x86_32. Always take slow path.
+    jmp(slow_case);
 #endif
-    lightweight_lock(obj, hdr, thread, tmp, slow_case);
   } else  if (LockingMode == LM_LEGACY) {
     Label done;
     // Load object header
     movptr(hdr, Address(obj, hdr_offset));
     // and mark it as unlocked
@@ -169,20 +172,24 @@
   }
 }
 
 
 void C1_MacroAssembler::initialize_header(Register obj, Register klass, Register len, Register t1, Register t2) {
-  assert_different_registers(obj, klass, len);
-  movptr(Address(obj, oopDesc::mark_offset_in_bytes()), checked_cast<int32_t>(markWord::prototype().value()));
+  assert_different_registers(obj, klass, len, t1, t2);
 #ifdef _LP64
-  if (UseCompressedClassPointers) { // Take care not to kill klass
+  if (UseCompactObjectHeaders) {
+    movptr(t1, Address(klass, Klass::prototype_header_offset()));
+    movptr(Address(obj, oopDesc::mark_offset_in_bytes()), t1);
+  } else if (UseCompressedClassPointers) { // Take care not to kill klass
+    movptr(Address(obj, oopDesc::mark_offset_in_bytes()), checked_cast<int32_t>(markWord::prototype().value()));
     movptr(t1, klass);
     encode_klass_not_null(t1, rscratch1);
     movl(Address(obj, oopDesc::klass_offset_in_bytes()), t1);
   } else
 #endif
   {
+    movptr(Address(obj, oopDesc::mark_offset_in_bytes()), checked_cast<int32_t>(markWord::prototype().value()));
     movptr(Address(obj, oopDesc::klass_offset_in_bytes()), klass);
   }
 
   if (len->is_valid()) {
     movl(Address(obj, arrayOopDesc::length_offset_in_bytes()), len);
@@ -195,11 +202,11 @@
       movl(Address(obj, base_offset), t1);
     }
 #endif
   }
 #ifdef _LP64
-  else if (UseCompressedClassPointers) {
+  else if (UseCompressedClassPointers && !UseCompactObjectHeaders) {
     xorptr(t1, t1);
     store_klass_gap(obj, t1);
   }
 #endif
 }
@@ -229,11 +236,13 @@
 
 void C1_MacroAssembler::initialize_object(Register obj, Register klass, Register var_size_in_bytes, int con_size_in_bytes, Register t1, Register t2, bool is_tlab_allocated) {
   assert((con_size_in_bytes & MinObjAlignmentInBytesMask) == 0,
          "con_size_in_bytes is not multiple of alignment");
   const int hdr_size_in_bytes = instanceOopDesc::header_size() * HeapWordSize;
-
+  if (UseCompactObjectHeaders) {
+    assert(hdr_size_in_bytes == 8, "check object headers size");
+  }
   initialize_header(obj, klass, noreg, t1, t2);
 
   if (!(UseTLAB && ZeroTLAB && is_tlab_allocated)) {
     // clear rest of allocated space
     const Register t1_zero = t1;
diff a/src/hotspot/cpu/x86/c2_CodeStubs_x86.cpp b/src/hotspot/cpu/x86/c2_CodeStubs_x86.cpp
--- a/src/hotspot/cpu/x86/c2_CodeStubs_x86.cpp
+++ b/src/hotspot/cpu/x86/c2_CodeStubs_x86.cpp
@@ -94,10 +94,11 @@
   }
 
   { // Restore held monitor count and slow path.
 
     __ bind(restore_held_monitor_count_and_slow_path);
+    __ bind(_slow_path);
     // Restore held monitor count.
     __ increment(Address(_thread, JavaThread::held_monitor_count_offset()));
     // increment will always result in ZF = 0 (no overflows).
     __ jmp(slow_path_continuation());
   }
diff a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp
--- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp
+++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp
@@ -947,10 +947,13 @@
   // Finish fast lock successfully. ZF value is irrelevant.
   Label locked;
   // Finish fast lock unsuccessfully. MUST jump with ZF == 0
   Label slow_path;
 
+  // Clear box. TODO[OMWorld]: Is this necessary? May also defer this to not write twice.
+  movptr(Address(box, BasicLock::object_monitor_cache_offset_in_bytes()), 0);
+
   if (DiagnoseSyncOnValueBasedClasses != 0) {
     load_klass(rax_reg, obj, t);
     movl(rax_reg, Address(rax_reg, Klass::access_flags_offset()));
     testl(rax_reg, JVM_ACC_IS_VALUE_BASED_CLASS);
     jcc(Assembler::notZero, slow_path);
@@ -960,11 +963,11 @@
 
   { // Lightweight Lock
 
     Label push;
 
-    const Register top = box;
+    const Register top = rax_reg;
 
     // Load the mark.
     movptr(mark, Address(obj, oopDesc::mark_offset_in_bytes()));
 
     // Prefetch top.
@@ -989,31 +992,102 @@
     lock(); cmpxchgptr(mark, Address(obj, oopDesc::mark_offset_in_bytes()));
     jcc(Assembler::notEqual, slow_path);
 
     bind(push);
     // After successful lock, push object on lock-stack.
+    // TODO[OMWorld]: Was prepush better?
+    movl(top, Address(thread, JavaThread::lock_stack_top_offset()));
     movptr(Address(thread, top), obj);
     addl(Address(thread, JavaThread::lock_stack_top_offset()), oopSize);
     jmpb(locked);
   }
 
   { // Handle inflated monitor.
     bind(inflated);
 
-    const Register tagged_monitor = mark;
+    const Register monitor = t;
+
+    if (!OMUseC2Cache) {
+      jmp(slow_path);
+    } else {
+      if (OMCacheHitRate) increment(Address(thread, JavaThread::lock_lookup_offset()));
 
-    // CAS owner (null => current thread).
-    xorptr(rax_reg, rax_reg);
-    lock(); cmpxchgptr(thread, Address(tagged_monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)));
-    jccb(Assembler::equal, locked);
+      // Fetch ObjectMonitor* from the cache or take the slow-path.
+      Label monitor_found;
 
-    // Check if recursive.
-    cmpptr(thread, rax_reg);
-    jccb(Assembler::notEqual, slow_path);
+      // Load cache address
+      lea(t, Address(thread, JavaThread::om_cache_oops_offset()));
+
+      const int num_unrolled = MIN2(OMC2UnrollCacheEntries, OMCacheSize);
+      for (int i = 0; i < num_unrolled; i++) {
+        cmpptr(obj, Address(t));
+        jccb(Assembler::equal, monitor_found);
+        if (i + 1 != num_unrolled) {
+          increment(t, in_bytes(OMCache::oop_to_oop_difference()));
+        }
+      }
+
+      if (num_unrolled == 0 || (OMC2UnrollCacheLookupLoopTail && num_unrolled != OMCacheSize)) {
+        if (num_unrolled != 0) {
+          // Loop after unrolling, advance iterator.
+          increment(t, in_bytes(OMCache::oop_to_oop_difference()));
+        }
+
+        Label loop;
+
+        // Search for obj in cache.
+        bind(loop);
+
+        // Check for match.
+        cmpptr(obj, Address(t));
+        jccb(Assembler::equal, monitor_found);
+
+        // Search until null encountered, guaranteed _null_sentinel at end.
+        cmpptr(Address(t), 1);
+        jcc(Assembler::below, slow_path); // 0 check, but with ZF=0 when *t == 0
+        increment(t, in_bytes(OMCache::oop_to_oop_difference()));
+        jmpb(loop);
+      } else {
+        jmp(slow_path);
+      }
+
+      // Cache hit.
+      bind(monitor_found);
+      movptr(monitor, Address(t, OMCache::oop_to_monitor_difference()));
+      if (OMCacheHitRate) increment(Address(thread, JavaThread::lock_hit_offset()));
+
+      Label monitor_locked;
+      // Lock the monitor.
+      Label recursion;
+      if (OMRecursiveFastPath) {
+        // Check owner for recursion first.
+        cmpptr(thread, Address(monitor, ObjectMonitor::owner_offset()));
+        jccb(Assembler::equal, recursion);
+      }
+
+      // CAS owner (null => current thread).
+      xorptr(rax, rax);
+      lock(); cmpxchgptr(thread, Address(monitor, ObjectMonitor::owner_offset()));
+      jccb(Assembler::equal, monitor_locked);
+
+      if (OMRecursiveFastPath) {
+        // Recursion already checked.
+        jmpb(slow_path);
+      } else {
+        // Check if recursive.
+        cmpptr(thread, rax);
+        jccb(Assembler::notEqual, slow_path);
+      }
 
-    // Recursive.
-    increment(Address(tagged_monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(recursions)));
+      // Recursive.
+      bind(recursion);
+      increment(Address(monitor, ObjectMonitor::recursions_offset()));
+
+      bind(monitor_locked);
+      // Cache the monitor for unlock
+      movptr(Address(box, BasicLock::object_monitor_cache_offset_in_bytes()), monitor);
+    }
   }
 
   bind(locked);
   increment(Address(thread, JavaThread::held_monitor_count_offset()));
   // Set ZF = 1
@@ -1051,11 +1125,13 @@
 
   // Assume success.
   decrement(Address(thread, JavaThread::held_monitor_count_offset()));
 
   const Register mark = t;
-  const Register top = reg_rax;
+  const Register monitor = t;
+  const Register top = t;
+  const Register box = reg_rax;
 
   Label dummy;
   C2FastUnlockLightweightStub* stub = nullptr;
 
   if (!Compile::current()->output()->in_scratch_emit_size()) {
@@ -1063,19 +1139,17 @@
     Compile::current()->output()->add_stub(stub);
   }
 
   Label& push_and_slow_path = stub == nullptr ? dummy : stub->push_and_slow_path();
   Label& check_successor = stub == nullptr ? dummy : stub->check_successor();
+  Label& slow_path = stub == nullptr ? dummy : stub->slow_path();
 
   { // Lightweight Unlock
 
     // Load top.
     movl(top, Address(thread, JavaThread::lock_stack_top_offset()));
 
-    // Prefetch mark.
-    movptr(mark, Address(obj, oopDesc::mark_offset_in_bytes()));
-
     // Check if obj is top of lock-stack.
     cmpptr(obj, Address(thread, top, Address::times_1, -oopSize));
     // Top of lock stack was not obj. Must be monitor.
     jcc(Assembler::notEqual, inflated_check_lock_stack);
 
@@ -1087,10 +1161,13 @@
     cmpptr(obj, Address(thread, top, Address::times_1, -2 * oopSize));
     jcc(Assembler::equal, unlocked);
 
     // We elide the monitor check, let the CAS fail instead.
 
+    // Load mark.
+    movptr(mark, Address(obj, oopDesc::mark_offset_in_bytes()));
+
     // Try to unlock. Transition lock bits 0b00 => 0b01
     movptr(reg_rax, mark);
     andptr(reg_rax, ~(int32_t)markWord::lock_mask);
     orptr(mark, markWord::unlocked_value);
     lock(); cmpxchgptr(mark, Address(obj, oopDesc::mark_offset_in_bytes()));
@@ -1108,54 +1185,52 @@
     jcc(Assembler::below, check_done);
     cmpptr(obj, Address(thread, top));
     jccb(Assembler::notEqual, inflated_check_lock_stack);
     stop("Fast Unlock lock on stack");
     bind(check_done);
+    const Register mark = t;
+    movptr(mark, Address(obj, oopDesc::mark_offset_in_bytes()));
     testptr(mark, markWord::monitor_value);
     jccb(Assembler::notZero, inflated);
     stop("Fast Unlock not monitor");
 #endif
 
     bind(inflated);
 
-    // mark contains the tagged ObjectMonitor*.
-    const Register monitor = mark;
-
-#ifndef _LP64
-    // Check if recursive.
-    xorptr(reg_rax, reg_rax);
-    orptr(reg_rax, Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(recursions)));
-    jcc(Assembler::notZero, check_successor);
-
-    // Check if the entry lists are empty.
-    movptr(reg_rax, Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(EntryList)));
-    orptr(reg_rax, Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(cxq)));
-    jcc(Assembler::notZero, check_successor);
-
-    // Release lock.
-    movptr(Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)), NULL_WORD);
-#else // _LP64
-    Label recursive;
-
-    // Check if recursive.
-    cmpptr(Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(recursions)), 0);
-    jccb(Assembler::notEqual, recursive);
-
-    // Check if the entry lists are empty.
-    movptr(reg_rax, Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(cxq)));
-    orptr(reg_rax, Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(EntryList)));
-    jcc(Assembler::notZero, check_successor);
-
-    // Release lock.
-    movptr(Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)), NULL_WORD);
-    jmpb(unlocked);
-
-    // Recursive unlock.
-    bind(recursive);
-    decrement(Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(recursions)));
-    xorl(t, t);
-#endif
+    if (!OMUseC2Cache) {
+      jmp(slow_path);
+    } else {
+      if (OMCacheHitRate) increment(Address(thread, JavaThread::unlock_lookup_offset()));
+      movptr(monitor, Address(box, BasicLock::object_monitor_cache_offset_in_bytes()));
+      // TODO[OMWorld]: Figure out the correctness surrounding the owner field here. Obj is not on the lock stack
+      //                but this means this thread must have locked on the inflated monitor at some point. So it
+      //                should not be anonymous.
+      cmpptr(monitor, 2);
+      jcc(Assembler::below, slow_path);
+
+      if (OMCacheHitRate) increment(Address(thread, JavaThread::unlock_hit_offset()));
+
+      Label recursive;
+
+      // Check if recursive.
+      cmpptr(Address(monitor,ObjectMonitor::recursions_offset()), 0);
+      jccb(Assembler::notEqual, recursive);
+
+      // Check if the entry lists are empty.
+      movptr(reg_rax, Address(monitor, ObjectMonitor::cxq_offset()));
+      orptr(reg_rax, Address(monitor, ObjectMonitor::EntryList_offset()));
+      jcc(Assembler::notZero, check_successor);
+
+      // Release lock.
+      movptr(Address(monitor, ObjectMonitor::owner_offset()), NULL_WORD);
+      jmpb(unlocked);
+
+      // Recursive unlock.
+      bind(recursive);
+      decrement(Address(monitor, ObjectMonitor::recursions_offset()));
+      xorl(t, t);
+    }
   }
 
   bind(unlocked);
   if (stub != nullptr) {
     bind(stub->unlocked_continuation());
@@ -6495,5 +6570,21 @@
   } else {
     assert(bt == T_FLOAT, "");
     vpermps(dst, shuffle, src, vlen_enc);
   }
 }
+
+#ifdef _LP64
+void C2_MacroAssembler::load_nklass_compact_c2(Register dst, Register obj, Register index, Address::ScaleFactor scale, int disp) {
+  // Note: Don't clobber obj anywhere in that method!
+
+  // The incoming address is pointing into obj-start + klass_offset_in_bytes. We need to extract
+  // obj-start, so that we can load from the object's mark-word instead. Usually the address
+  // comes as obj-start in obj and klass_offset_in_bytes in disp. However, sometimes C2
+  // emits code that pre-computes obj-start + klass_offset_in_bytes into a register, and
+  // then passes that register as obj and 0 in disp. The following code extracts the base
+  // and offset to load the mark-word.
+  int offset = oopDesc::mark_offset_in_bytes() + disp - oopDesc::klass_offset_in_bytes();
+  movq(dst, Address(obj, index, scale, offset));
+  shrq(dst, markWord::klass_shift);
+}
+#endif
diff a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp
--- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp
+++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp
@@ -498,6 +498,8 @@
                        XMMRegister xtmp2, XMMRegister xtmp3, Register rtmp, KRegister ktmp, int vlen_enc);
 
   void vector_rearrange_int_float(BasicType bt, XMMRegister dst, XMMRegister shuffle,
                                   XMMRegister src, int vlen_enc);
 
+  void load_nklass_compact_c2(Register dst, Register obj, Register index, Address::ScaleFactor scale, int disp);
+
 #endif // CPU_X86_C2_MACROASSEMBLER_X86_HPP
diff a/src/hotspot/cpu/x86/compressedKlass_x86.cpp b/src/hotspot/cpu/x86/compressedKlass_x86.cpp
--- a/src/hotspot/cpu/x86/compressedKlass_x86.cpp
+++ b/src/hotspot/cpu/x86/compressedKlass_x86.cpp
@@ -44,6 +44,8 @@
 
   // Nothing more to optimize for on x64. If base != 0, we will always emit the full 64-bit immediate.
   return result;
 }
 
+bool CompressedKlassPointers::pd_initialize(address addr, size_t len) { return false; }
+
 #endif // _LP64
diff a/src/hotspot/cpu/x86/interp_masm_x86.cpp b/src/hotspot/cpu/x86/interp_masm_x86.cpp
--- a/src/hotspot/cpu/x86/interp_masm_x86.cpp
+++ b/src/hotspot/cpu/x86/interp_masm_x86.cpp
@@ -1186,15 +1186,15 @@
     }
 
     if (LockingMode == LM_LIGHTWEIGHT) {
 #ifdef _LP64
       const Register thread = r15_thread;
+      lightweight_lock(lock_reg, obj_reg, swap_reg, thread, tmp_reg, slow_case);
 #else
-      const Register thread = lock_reg;
-      get_thread(thread);
+      // Lacking registers and thread on x86_32. Always take slow path.
+      jmp(slow_case);
 #endif
-      lightweight_lock(obj_reg, swap_reg, thread, tmp_reg, slow_case);
     } else if (LockingMode == LM_LEGACY) {
       // Load immediate 1 into swap_reg %rax
       movl(swap_reg, 1);
 
       // Load (object->mark() | 1) into swap_reg %rax
@@ -1252,19 +1252,13 @@
     jmp(done);
 
     bind(slow_case);
 
     // Call the runtime routine for slow case
-    if (LockingMode == LM_LIGHTWEIGHT) {
-      call_VM(noreg,
-              CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter_obj),
-              obj_reg);
-    } else {
-      call_VM(noreg,
-              CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
-              lock_reg);
-    }
+    call_VM(noreg,
+            CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
+            lock_reg);
     bind(done);
   }
 }
 
 
diff a/src/hotspot/cpu/x86/macroAssembler_x86.cpp b/src/hotspot/cpu/x86/macroAssembler_x86.cpp
--- a/src/hotspot/cpu/x86/macroAssembler_x86.cpp
+++ b/src/hotspot/cpu/x86/macroAssembler_x86.cpp
@@ -1348,11 +1348,12 @@
 #endif
   call(AddressLiteral(entry, rh));
 }
 
 int MacroAssembler::ic_check_size() {
-  return LP64_ONLY(14) NOT_LP64(12);
+  return
+      LP64_ONLY(UseCompactObjectHeaders ? 17 : 14) NOT_LP64(12);
 }
 
 int MacroAssembler::ic_check(int end_alignment) {
   Register receiver = LP64_ONLY(j_rarg0) NOT_LP64(rcx);
   Register data = rax;
@@ -1364,21 +1365,27 @@
   // before the inline cache check here, and not after
   align(end_alignment, offset() + ic_check_size());
 
   int uep_offset = offset();
 
+#ifdef _LP64
+  if (UseCompactObjectHeaders) {
+    load_nklass_compact(temp, receiver);
+    cmpl(temp, Address(data, CompiledICData::speculated_klass_offset()));
+  } else
+#endif
   if (UseCompressedClassPointers) {
     movl(temp, Address(receiver, oopDesc::klass_offset_in_bytes()));
     cmpl(temp, Address(data, CompiledICData::speculated_klass_offset()));
   } else {
     movptr(temp, Address(receiver, oopDesc::klass_offset_in_bytes()));
     cmpptr(temp, Address(data, CompiledICData::speculated_klass_offset()));
   }
 
   // if inline cache check fails, then jump to runtime routine
   jump_cc(Assembler::notEqual, RuntimeAddress(SharedRuntime::get_ic_miss_stub()));
-  assert((offset() % end_alignment) == 0, "Misaligned verified entry point");
+  assert((offset() % end_alignment) == 0, "Misaligned verified entry point (%d, %d, %d)", uep_offset, offset(), end_alignment);
 
   return uep_offset;
 }
 
 void MacroAssembler::emit_static_call_stub() {
@@ -5323,23 +5330,38 @@
   movptr(holder, Address(method, Method::const_offset()));                      // ConstMethod*
   movptr(holder, Address(holder, ConstMethod::constants_offset()));             // ConstantPool*
   movptr(holder, Address(holder, ConstantPool::pool_holder_offset()));          // InstanceKlass*
 }
 
+#ifdef _LP64
+void MacroAssembler::load_nklass_compact(Register dst, Register src) {
+  assert(UseCompactObjectHeaders, "expect compact object headers");
+  movq(dst, Address(src, oopDesc::mark_offset_in_bytes()));
+  shrq(dst, markWord::klass_shift);
+}
+#endif
+
 void MacroAssembler::load_klass(Register dst, Register src, Register tmp) {
+  BLOCK_COMMENT("load_klass");
   assert_different_registers(src, tmp);
   assert_different_registers(dst, tmp);
 #ifdef _LP64
-  if (UseCompressedClassPointers) {
+  if (UseCompactObjectHeaders) {
+    load_nklass_compact(dst, src);
+    decode_klass_not_null(dst, tmp);
+  } else if (UseCompressedClassPointers) {
     movl(dst, Address(src, oopDesc::klass_offset_in_bytes()));
     decode_klass_not_null(dst, tmp);
   } else
 #endif
+  {
     movptr(dst, Address(src, oopDesc::klass_offset_in_bytes()));
+  }
 }
 
 void MacroAssembler::store_klass(Register dst, Register src, Register tmp) {
+  assert(!UseCompactObjectHeaders, "not with compact headers");
   assert_different_registers(src, tmp);
   assert_different_registers(dst, tmp);
 #ifdef _LP64
   if (UseCompressedClassPointers) {
     encode_klass_not_null(src, tmp);
@@ -5347,10 +5369,45 @@
   } else
 #endif
     movptr(Address(dst, oopDesc::klass_offset_in_bytes()), src);
 }
 
+void MacroAssembler::cmp_klass(Register klass, Register obj, Register tmp) {
+  BLOCK_COMMENT("cmp_klass 1");
+#ifdef _LP64
+  if (UseCompactObjectHeaders) {
+    load_nklass_compact(tmp, obj);
+    cmpl(klass, tmp);
+  } else if (UseCompressedClassPointers) {
+    cmpl(klass, Address(obj, oopDesc::klass_offset_in_bytes()));
+  } else
+#endif
+  {
+    cmpptr(klass, Address(obj, oopDesc::klass_offset_in_bytes()));
+  }
+}
+
+void MacroAssembler::cmp_klass(Register src, Register dst, Register tmp1, Register tmp2) {
+  BLOCK_COMMENT("cmp_klass 2");
+#ifdef _LP64
+  if (UseCompactObjectHeaders) {
+    assert(tmp2 != noreg, "need tmp2");
+    assert_different_registers(src, dst, tmp1, tmp2);
+    load_nklass_compact(tmp1, src);
+    load_nklass_compact(tmp2, dst);
+    cmpl(tmp1, tmp2);
+  } else if (UseCompressedClassPointers) {
+    movl(tmp1, Address(src, oopDesc::klass_offset_in_bytes()));
+    cmpl(tmp1, Address(dst, oopDesc::klass_offset_in_bytes()));
+  } else
+#endif
+  {
+    movptr(tmp1, Address(src, oopDesc::klass_offset_in_bytes()));
+    cmpptr(tmp1, Address(dst, oopDesc::klass_offset_in_bytes()));
+  }
+}
+
 void MacroAssembler::access_load_at(BasicType type, DecoratorSet decorators, Register dst, Address src,
                                     Register tmp1, Register thread_tmp) {
   BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler();
   decorators = AccessInternal::decorator_fixup(decorators, type);
   bool as_raw = (decorators & AS_RAW) != 0;
@@ -5394,10 +5451,11 @@
   access_store_at(T_OBJECT, IN_HEAP, dst, noreg, noreg, noreg, noreg);
 }
 
 #ifdef _LP64
 void MacroAssembler::store_klass_gap(Register dst, Register src) {
+  assert(!UseCompactObjectHeaders, "Don't use with compact headers");
   if (UseCompressedClassPointers) {
     // Store to klass gap in destination
     movl(Address(dst, oopDesc::klass_gap_offset_in_bytes()), src);
   }
 }
@@ -5558,12 +5616,11 @@
   if (CompressedKlassPointers::base() != nullptr) {
     mov64(tmp, (int64_t)CompressedKlassPointers::base());
     subq(r, tmp);
   }
   if (CompressedKlassPointers::shift() != 0) {
-    assert (LogKlassAlignmentInBytes == CompressedKlassPointers::shift(), "decode alg wrong");
-    shrq(r, LogKlassAlignmentInBytes);
+    shrq(r, CompressedKlassPointers::shift());
   }
 }
 
 void MacroAssembler::encode_and_move_klass_not_null(Register dst, Register src) {
   assert_different_registers(src, dst);
@@ -5572,12 +5629,11 @@
     addq(dst, src);
   } else {
     movptr(dst, src);
   }
   if (CompressedKlassPointers::shift() != 0) {
-    assert (LogKlassAlignmentInBytes == CompressedKlassPointers::shift(), "decode alg wrong");
-    shrq(dst, LogKlassAlignmentInBytes);
+    shrq(dst, CompressedKlassPointers::shift());
   }
 }
 
 void  MacroAssembler::decode_klass_not_null(Register r, Register tmp) {
   assert_different_registers(r, tmp);
@@ -5585,12 +5641,11 @@
   assert(UseCompressedClassPointers, "should only be used for compressed headers");
   // Cannot assert, unverified entry point counts instructions (see .ad file)
   // vtableStubs also counts instructions in pd_code_size_limit.
   // Also do not verify_oop as this is called by verify_oop.
   if (CompressedKlassPointers::shift() != 0) {
-    assert(LogKlassAlignmentInBytes == CompressedKlassPointers::shift(), "decode alg wrong");
-    shlq(r, LogKlassAlignmentInBytes);
+    shlq(r, CompressedKlassPointers::shift());
   }
   if (CompressedKlassPointers::base() != nullptr) {
     mov64(tmp, (int64_t)CompressedKlassPointers::base());
     addq(r, tmp);
   }
@@ -5608,21 +5663,32 @@
       CompressedKlassPointers::shift() == 0) {
     // The best case scenario is that there is no base or shift. Then it is already
     // a pointer that needs nothing but a register rename.
     movl(dst, src);
   } else {
-    if (CompressedKlassPointers::base() != nullptr) {
-      mov64(dst, (int64_t)CompressedKlassPointers::base());
-    } else {
-      xorq(dst, dst);
-    }
-    if (CompressedKlassPointers::shift() != 0) {
-      assert(LogKlassAlignmentInBytes == CompressedKlassPointers::shift(), "decode alg wrong");
-      assert(LogKlassAlignmentInBytes == Address::times_8, "klass not aligned on 64bits?");
-      leaq(dst, Address(dst, src, Address::times_8, 0));
+    if (CompressedKlassPointers::shift() <= Address::times_8) {
+      if (CompressedKlassPointers::base() != nullptr) {
+        mov64(dst, (int64_t)CompressedKlassPointers::base());
+      } else {
+        xorq(dst, dst);
+      }
+      if (CompressedKlassPointers::shift() != 0) {
+        assert(CompressedKlassPointers::shift() == Address::times_8, "klass not aligned on 64bits?");
+        leaq(dst, Address(dst, src, Address::times_8, 0));
+      } else {
+        addq(dst, src);
+      }
     } else {
+      if (CompressedKlassPointers::base() != nullptr) {
+        const uint64_t base_right_shifted =
+            (uint64_t)CompressedKlassPointers::base() >> CompressedKlassPointers::shift();
+        mov64(dst, base_right_shifted);
+      } else {
+        xorq(dst, dst);
+      }
       addq(dst, src);
+      shlq(dst, CompressedKlassPointers::shift());
     }
   }
 }
 
 void  MacroAssembler::set_narrow_oop(Register dst, jobject obj) {
@@ -9925,21 +9991,31 @@
 //
 // obj: the object to be locked
 // reg_rax: rax
 // thread: the thread which attempts to lock obj
 // tmp: a temporary register
-void MacroAssembler::lightweight_lock(Register obj, Register reg_rax, Register thread, Register tmp, Label& slow) {
+//
+// x86_32 Note: basic_lock and thread may alias each other due to limited register
+//              availiability.
+void MacroAssembler::lightweight_lock(Register basic_lock, Register obj, Register reg_rax, Register thread, Register tmp, Label& slow) {
   assert(reg_rax == rax, "");
   assert_different_registers(obj, reg_rax, thread, tmp);
 
   Label push;
   const Register top = tmp;
 
   // Preload the markWord. It is important that this is the first
   // instruction emitted as it is part of C1's null check semantics.
   movptr(reg_rax, Address(obj, oopDesc::mark_offset_in_bytes()));
 
+  movptr(Address(basic_lock, BasicObjectLock::lock_offset() + in_ByteSize((BasicLock::object_monitor_cache_offset_in_bytes()))), 0);
+
+#ifndef _LP64
+  if (thread == basic_lock) {
+    get_thread(thread);
+  }
+#endif // !_LP64
   // Load top.
   movl(top, Address(thread, JavaThread::lock_stack_top_offset()));
 
   // Check if the lock-stack is full.
   cmpl(top, LockStack::end_offset());
diff a/src/hotspot/cpu/x86/macroAssembler_x86.hpp b/src/hotspot/cpu/x86/macroAssembler_x86.hpp
--- a/src/hotspot/cpu/x86/macroAssembler_x86.hpp
+++ b/src/hotspot/cpu/x86/macroAssembler_x86.hpp
@@ -362,13 +362,24 @@
   void load_method_holder_cld(Register rresult, Register rmethod);
 
   void load_method_holder(Register holder, Register method);
 
   // oop manipulations
+#ifdef _LP64
+  void load_nklass_compact(Register dst, Register src);
+#endif
   void load_klass(Register dst, Register src, Register tmp);
   void store_klass(Register dst, Register src, Register tmp);
 
+  // Compares the Klass pointer of an object to a given Klass (which might be narrow,
+  // depending on UseCompressedClassPointers).
+  void cmp_klass(Register klass, Register dst, Register tmp);
+
+  // Compares the Klass pointer of two objects o1 and o2. Result is in the condition flags.
+  // Uses tmp1 and tmp2 as temporary registers.
+  void cmp_klass(Register src, Register dst, Register tmp1, Register tmp2);
+
   void access_load_at(BasicType type, DecoratorSet decorators, Register dst, Address src,
                       Register tmp1, Register thread_tmp);
   void access_store_at(BasicType type, DecoratorSet decorators, Address dst, Register val,
                        Register tmp1, Register tmp2, Register tmp3);
 
@@ -2034,11 +2045,11 @@
 
   void vallones(XMMRegister dst, int vector_len);
 
   void check_stack_alignment(Register sp, const char* msg, unsigned bias = 0, Register tmp = noreg);
 
-  void lightweight_lock(Register obj, Register reg_rax, Register thread, Register tmp, Label& slow);
+  void lightweight_lock(Register basic_lock, Register obj, Register reg_rax, Register thread, Register tmp, Label& slow);
   void lightweight_unlock(Register obj, Register reg_rax, Register thread, Register tmp, Label& slow);
 };
 
 /**
  * class SkipIfEqual:
diff a/src/hotspot/cpu/x86/matcher_x86.hpp b/src/hotspot/cpu/x86/matcher_x86.hpp
--- a/src/hotspot/cpu/x86/matcher_x86.hpp
+++ b/src/hotspot/cpu/x86/matcher_x86.hpp
@@ -91,11 +91,11 @@
   }
 
   static bool narrow_klass_use_complex_address() {
     NOT_LP64(ShouldNotCallThis();)
     assert(UseCompressedClassPointers, "only for compressed klass code");
-    return (LogKlassAlignmentInBytes <= 3);
+    return (CompressedKlassPointers::shift() <= 3);
   }
 
   // Prefer ConN+DecodeN over ConP.
   static bool const_oop_prefer_decode() {
     NOT_LP64(ShouldNotCallThis();)
diff a/src/hotspot/cpu/x86/sharedRuntime_x86.cpp b/src/hotspot/cpu/x86/sharedRuntime_x86.cpp
--- a/src/hotspot/cpu/x86/sharedRuntime_x86.cpp
+++ b/src/hotspot/cpu/x86/sharedRuntime_x86.cpp
@@ -74,12 +74,18 @@
   // get hash
 #ifdef _LP64
   // Read the header and build a mask to get its hash field.
   // Depend on hash_mask being at most 32 bits and avoid the use of hash_mask_in_place
   // because it could be larger than 32 bits in a 64-bit vm. See markWord.hpp.
-  __ shrptr(result, markWord::hash_shift);
-  __ andptr(result, markWord::hash_mask);
+  if (UseCompactObjectHeaders) {
+    STATIC_ASSERT(markWord::hash_mask_compact < nth_bit(32));
+    __ shrptr(result, markWord::hash_shift_compact);
+    __ andptr(result, markWord::hash_mask_compact);
+  } else {
+    __ shrptr(result, markWord::hash_shift);
+    __ andptr(result, markWord::hash_mask);
+  }
 #else
   __ andptr(result, markWord::hash_mask_in_place);
 #endif //_LP64
 
   // test if hashCode exists
diff a/src/hotspot/cpu/x86/sharedRuntime_x86_32.cpp b/src/hotspot/cpu/x86/sharedRuntime_x86_32.cpp
--- a/src/hotspot/cpu/x86/sharedRuntime_x86_32.cpp
+++ b/src/hotspot/cpu/x86/sharedRuntime_x86_32.cpp
@@ -44,10 +44,11 @@
 #include "runtime/signature.hpp"
 #include "runtime/stubRoutines.hpp"
 #include "runtime/vframeArray.hpp"
 #include "runtime/vm_version.hpp"
 #include "utilities/align.hpp"
+#include "utilities/globalDefinitions.hpp"
 #include "vmreg_x86.inline.hpp"
 #ifdef COMPILER1
 #include "c1/c1_Runtime1.hpp"
 #endif
 #ifdef COMPILER2
@@ -1692,11 +1693,11 @@
       // Save the test result, for recursive case, the result is zero
       __ movptr(Address(lock_reg, mark_word_offset), swap_reg);
       __ jcc(Assembler::notEqual, slow_path_lock);
     } else {
       assert(LockingMode == LM_LIGHTWEIGHT, "must be");
-      __ lightweight_lock(obj_reg, swap_reg, thread, lock_reg, slow_path_lock);
+      __ lightweight_lock(lock_reg, obj_reg, swap_reg, thread, lock_reg, slow_path_lock);
     }
     __ bind(count_mon);
     __ inc_held_monitor_count();
 
     // Slow path will re-enter here
@@ -1931,10 +1932,15 @@
 
     // BEGIN Slow path lock
 
     __ bind(slow_path_lock);
 
+    if (LockingMode == LM_LIGHTWEIGHT) {
+      // Reload the lock addr. Clobbered by lightweight_lock.
+      __ lea(lock_reg, Address(rbp, lock_slot_rbp_offset));
+    }
+
     // has last_Java_frame setup. No exceptions so do vanilla call not call_VM
     // args are (oop obj, BasicLock* lock, JavaThread* thread)
     __ push(thread);
     __ push(lock_reg);
     __ push(obj_reg);
diff a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp
--- a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp
+++ b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp
@@ -2166,11 +2166,11 @@
       // Save the test result, for recursive case, the result is zero
       __ movptr(Address(lock_reg, mark_word_offset), swap_reg);
       __ jcc(Assembler::notEqual, slow_path_lock);
     } else {
       assert(LockingMode == LM_LIGHTWEIGHT, "must be");
-      __ lightweight_lock(obj_reg, swap_reg, r15_thread, rscratch1, slow_path_lock);
+      __ lightweight_lock(lock_reg, obj_reg, swap_reg, r15_thread, rscratch1, slow_path_lock);
     }
     __ bind(count_mon);
     __ inc_held_monitor_count();
 
     // Slow path will re-enter here
diff a/src/hotspot/cpu/x86/templateTable_x86.cpp b/src/hotspot/cpu/x86/templateTable_x86.cpp
--- a/src/hotspot/cpu/x86/templateTable_x86.cpp
+++ b/src/hotspot/cpu/x86/templateTable_x86.cpp
@@ -4083,11 +4083,16 @@
       __ jmp(initialize_header);
     }
 
     // The object is initialized before the header.  If the object size is
     // zero, go directly to the header initialization.
-    __ decrement(rdx, sizeof(oopDesc));
+    if (UseCompactObjectHeaders) {
+      assert(is_aligned(oopDesc::base_offset_in_bytes(), BytesPerLong), "oop base offset must be 8-byte-aligned");
+      __ decrement(rdx, oopDesc::base_offset_in_bytes());
+    } else {
+      __ decrement(rdx, sizeof(oopDesc));
+    }
     __ jcc(Assembler::zero, initialize_header);
 
     // Initialize topmost object field, divide rdx by 8, check if odd and
     // test if zero.
     __ xorl(rcx, rcx);    // use zero reg to clear memory (shorter code)
@@ -4105,26 +4110,39 @@
 #endif
 
     // initialize remaining object fields: rdx was a multiple of 8
     { Label loop;
     __ bind(loop);
-    __ movptr(Address(rax, rdx, Address::times_8, sizeof(oopDesc) - 1*oopSize), rcx);
-    NOT_LP64(__ movptr(Address(rax, rdx, Address::times_8, sizeof(oopDesc) - 2*oopSize), rcx));
+    if (UseCompactObjectHeaders) {
+      assert(is_aligned(oopDesc::base_offset_in_bytes(), BytesPerLong), "oop base offset must be 8-byte-aligned");
+      int header_size = oopDesc::base_offset_in_bytes();
+      __ movptr(Address(rax, rdx, Address::times_8, header_size - 1*oopSize), rcx);
+      NOT_LP64(__ movptr(Address(rax, rdx, Address::times_8, header_size - 2*oopSize), rcx));
+    } else {
+      __ movptr(Address(rax, rdx, Address::times_8, sizeof(oopDesc) - 1*oopSize), rcx);
+      NOT_LP64(__ movptr(Address(rax, rdx, Address::times_8, sizeof(oopDesc) - 2*oopSize), rcx));
+    }
     __ decrement(rdx);
     __ jcc(Assembler::notZero, loop);
     }
 
     // initialize object header only.
     __ bind(initialize_header);
-    __ movptr(Address(rax, oopDesc::mark_offset_in_bytes()),
-              (intptr_t)markWord::prototype().value()); // header
-    __ pop(rcx);   // get saved klass back in the register.
+    if (UseCompactObjectHeaders) {
+      __ pop(rcx);   // get saved klass back in the register.
+      __ movptr(rbx, Address(rcx, Klass::prototype_header_offset()));
+      __ movptr(Address(rax, oopDesc::mark_offset_in_bytes()), rbx);
+    } else {
+      __ movptr(Address(rax, oopDesc::mark_offset_in_bytes()),
+                (intptr_t)markWord::prototype().value()); // header
+      __ pop(rcx);   // get saved klass back in the register.
 #ifdef _LP64
-    __ xorl(rsi, rsi); // use zero reg to clear memory (shorter code)
-    __ store_klass_gap(rax, rsi);  // zero klass gap for compressed oops
+      __ xorl(rsi, rsi); // use zero reg to clear memory (shorter code)
+      __ store_klass_gap(rax, rsi);  // zero klass gap for compressed oops
 #endif
-    __ store_klass(rax, rcx, rscratch1);  // klass
+      __ store_klass(rax, rcx, rscratch1);  // klass
+    }
 
     {
       SkipIfEqual skip_if(_masm, &DTraceAllocProbes, 0, rscratch1);
       // Trigger dtrace event for fastpath
       __ push(atos);
diff a/src/hotspot/cpu/x86/x86_64.ad b/src/hotspot/cpu/x86/x86_64.ad
--- a/src/hotspot/cpu/x86/x86_64.ad
+++ b/src/hotspot/cpu/x86/x86_64.ad
@@ -4244,20 +4244,36 @@
 %}
 
 // Load narrow Klass Pointer
 instruct loadNKlass(rRegN dst, memory mem)
 %{
+  predicate(!UseCompactObjectHeaders);
   match(Set dst (LoadNKlass mem));
 
   ins_cost(125); // XXX
   format %{ "movl    $dst, $mem\t# compressed klass ptr" %}
   ins_encode %{
     __ movl($dst$$Register, $mem$$Address);
   %}
   ins_pipe(ialu_reg_mem); // XXX
 %}
 
+instruct loadNKlassCompactHeaders(rRegN dst, memory mem, rFlagsReg cr)
+%{
+  predicate(UseCompactObjectHeaders);
+  match(Set dst (LoadNKlass mem));
+  effect(KILL cr);
+  ins_cost(125); // XXX
+  format %{ "movl    $dst, $mem\t# compressed klass ptr" %}
+  ins_encode %{
+    Register index = $mem$$index != 4 ? $mem$$index$$Register : noreg;
+    Address::ScaleFactor sf = (index != noreg) ? static_cast<Address::ScaleFactor>($mem$$scale) : Address::no_scale;
+    __ load_nklass_compact_c2($dst$$Register, $mem$$base$$Register, index, sf, $mem$$disp);
+  %}
+  ins_pipe(pipe_slow); // XXX
+%}
+
 // Load Float
 instruct loadF(regF dst, memory mem)
 %{
   match(Set dst (LoadF mem));
 
@@ -11591,10 +11607,11 @@
   ins_pipe(ialu_cr_reg_imm);
 %}
 
 instruct compN_mem_imm_klass(rFlagsRegU cr, memory mem, immNKlass src)
 %{
+  predicate(!UseCompactObjectHeaders);
   match(Set cr (CmpN src (LoadNKlass mem)));
 
   format %{ "cmpl    $mem, $src\t# compressed klass ptr" %}
   ins_encode %{
     __ cmp_narrow_klass($mem$$Address, (Klass*)$src$$constant);
diff a/src/hotspot/cpu/zero/zeroInterpreter_zero.cpp b/src/hotspot/cpu/zero/zeroInterpreter_zero.cpp
--- a/src/hotspot/cpu/zero/zeroInterpreter_zero.cpp
+++ b/src/hotspot/cpu/zero/zeroInterpreter_zero.cpp
@@ -35,17 +35,19 @@
 #include "oops/klass.inline.hpp"
 #include "oops/methodData.hpp"
 #include "oops/method.hpp"
 #include "oops/oop.inline.hpp"
 #include "prims/jvmtiExport.hpp"
+#include "runtime/basicLock.inline.hpp"
 #include "runtime/frame.inline.hpp"
 #include "runtime/handles.inline.hpp"
 #include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/jniHandles.inline.hpp"
 #include "runtime/timer.hpp"
 #include "runtime/timerTrace.hpp"
 #include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
 #include "utilities/macros.hpp"
 
 #include "entry_zero.hpp"
 #include "stack_zero.inline.hpp"
 
@@ -329,27 +331,31 @@
   BasicObjectLock *monitor;
   monitor = nullptr;
   if (method->is_synchronized()) {
     monitor = (BasicObjectLock*) istate->stack_base();
     oop lockee = monitor->obj();
-    markWord disp = lockee->mark().set_unlocked();
-    monitor->lock()->set_displaced_header(disp);
-    bool call_vm = (LockingMode == LM_MONITOR);
-    bool inc_monitor_count = true;
-    if (call_vm || lockee->cas_set_mark(markWord::from_pointer(monitor), disp) != disp) {
-      // Is it simple recursive case?
-      if (!call_vm && thread->is_lock_owned((address) disp.clear_lock_bits().to_pointer())) {
-        monitor->lock()->set_displaced_header(markWord::from_pointer(nullptr));
-      } else {
-        inc_monitor_count = false;
-        CALL_VM_NOCHECK(InterpreterRuntime::monitorenter(thread, monitor));
-        if (HAS_PENDING_EXCEPTION)
-          goto unwind_and_return;
+    bool success = false;
+    if (LockingMode == LM_LEGACY) {
+      markWord disp = lockee->mark().set_unlocked();
+      monitor->lock()->set_displaced_header(disp);
+      success = true;
+      if (lockee->cas_set_mark(markWord::from_pointer(monitor), disp) != disp) {
+        // Is it simple recursive case?
+        if (thread->is_lock_owned((address) disp.clear_lock_bits().to_pointer())) {
+          monitor->lock()->set_displaced_header(markWord::from_pointer(nullptr));
+        } else {
+          success = false;
+        }
+      }
+      if (success) {
+        THREAD->inc_held_monitor_count();
       }
     }
-    if (inc_monitor_count) {
-      THREAD->inc_held_monitor_count();
+    if (!success) {
+      CALL_VM_NOCHECK(InterpreterRuntime::monitorenter(thread, monitor));
+          if (HAS_PENDING_EXCEPTION)
+            goto unwind_and_return;
     }
   }
 
   // Get the signature handler
   InterpreterRuntime::SignatureHandler *handler; {
diff a/src/hotspot/share/c1/c1_Runtime1.cpp b/src/hotspot/share/c1/c1_Runtime1.cpp
--- a/src/hotspot/share/c1/c1_Runtime1.cpp
+++ b/src/hotspot/share/c1/c1_Runtime1.cpp
@@ -71,10 +71,11 @@
 #include "runtime/vframe.inline.hpp"
 #include "runtime/vframeArray.hpp"
 #include "runtime/vm_version.hpp"
 #include "utilities/copy.hpp"
 #include "utilities/events.hpp"
+#include "utilities/macros.hpp"
 
 
 // Implementation of StubAssembler
 
 StubAssembler::StubAssembler(CodeBuffer* code, const char * name, int stub_id) : C1_MacroAssembler(code) {
@@ -755,12 +756,12 @@
   }
 #endif
   if (LockingMode == LM_MONITOR) {
     lock->set_obj(obj);
   }
-  assert(LockingMode == LM_LIGHTWEIGHT || obj == lock->obj(), "must match");
-  SharedRuntime::monitor_enter_helper(obj, LockingMode == LM_LIGHTWEIGHT ? nullptr : lock->lock(), current);
+  assert(obj == lock->obj(), "must match: " PTR_FORMAT, p2i(lock));
+  SharedRuntime::monitor_enter_helper(obj, lock->lock(), current);
 JRT_END
 
 
 JRT_LEAF(void, Runtime1::monitorexit(JavaThread* current, BasicObjectLock* lock))
   assert(current == JavaThread::current(), "pre-condition");
diff a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp
--- a/src/hotspot/share/cds/archiveBuilder.cpp
+++ b/src/hotspot/share/cds/archiveBuilder.cpp
@@ -212,12 +212,14 @@
     Klass* klass = (Klass*)ref->obj();
     assert(klass->is_klass(), "must be");
     if (!is_excluded(klass)) {
       _klasses->append(klass);
     }
-    // See RunTimeClassInfo::get_for()
-    _estimated_metaspaceobj_bytes += align_up(BytesPerWord, SharedSpaceObjectAlignment);
+    // See RunTimeClassInfo::get_for(): make sure we have enough space for both maximum
+    // Klass alignment as well as the RuntimeInfo* pointer we will embed in front of a Klass.
+    _estimated_metaspaceobj_bytes += align_up(BytesPerWord, CompressedKlassPointers::klass_alignment_in_bytes()) +
+        align_up(sizeof(void*), SharedSpaceObjectAlignment);
   } else if (ref->msotype() == MetaspaceObj::SymbolType) {
     // Make sure the symbol won't be GC'ed while we are dumping the archive.
     Symbol* sym = (Symbol*)ref->obj();
     sym->increment_refcount();
     _symbols->append(sym);
@@ -626,21 +628,32 @@
   char* oldtop;
   char* newtop;
 
   oldtop = dump_region->top();
   if (src_info->msotype() == MetaspaceObj::ClassType) {
-    // Save a pointer immediate in front of an InstanceKlass, so
+    // Allocate space for a pointer directly in front of the future InstanceKlass, so
     // we can do a quick lookup from InstanceKlass* -> RunTimeClassInfo*
     // without building another hashtable. See RunTimeClassInfo::get_for()
     // in systemDictionaryShared.cpp.
     Klass* klass = (Klass*)src;
     if (klass->is_instance_klass()) {
       SystemDictionaryShared::validate_before_archiving(InstanceKlass::cast(klass));
       dump_region->allocate(sizeof(address));
     }
+    // Allocate space for the future InstanceKlass with proper alignment
+    const size_t alignment =
+#ifdef _LP64
+      UseCompressedClassPointers ?
+        nth_bit(ArchiveBuilder::precomputed_narrow_klass_shift()) :
+        SharedSpaceObjectAlignment;
+#else
+    SharedSpaceObjectAlignment;
+#endif
+    dest = dump_region->allocate(bytes, alignment);
+  } else {
+    dest = dump_region->allocate(bytes);
   }
-  dest = dump_region->allocate(bytes);
   newtop = dump_region->top();
 
   memcpy(dest, src, bytes);
 
   // Update the hash of buffered sorted symbols for static dump so that the symbols have deterministic contents
@@ -667,10 +680,12 @@
 
   log_trace(cds)("Copy: " PTR_FORMAT " ==> " PTR_FORMAT " %d", p2i(src), p2i(dest), bytes);
   src_info->set_buffered_addr((address)dest);
 
   _alloc_stats.record(src_info->msotype(), int(newtop - oldtop), src_info->read_only());
+
+  DEBUG_ONLY(_alloc_stats.verify((int)dump_region->used(), src_info->read_only()));
 }
 
 // This is used by code that hand-assembles data structures, such as the LambdaProxyClassKey, that are
 // not handled by MetaspaceClosure.
 void ArchiveBuilder::write_pointer_in_buffer(address* ptr_location, address src_addr) {
@@ -727,10 +742,19 @@
     const char* unlinked = "";
     const char* hidden = "";
     const char* generated = "";
     Klass* k = get_buffered_addr(klasses()->at(i));
     k->remove_java_mirror();
+#ifdef _LP64
+    if (UseCompactObjectHeaders) {
+      Klass* requested_k = to_requested(k);
+      address narrow_klass_base = _requested_static_archive_bottom; // runtime encoding base == runtime mapping start
+      const int narrow_klass_shift = precomputed_narrow_klass_shift();
+      narrowKlass nk = CompressedKlassPointers::encode_not_null_without_asserts(requested_k, narrow_klass_base, narrow_klass_shift);
+      k->set_prototype_header(markWord::prototype().set_narrow_klass(nk));
+    }
+#endif //_LP64
     if (k->is_objArray_klass()) {
       // InstanceKlass and TypeArrayKlass will in turn call remove_unshareable_info
       // on their array classes.
       num_obj_array_klasses ++;
       type = "array";
@@ -824,13 +848,19 @@
 #if INCLUDE_CDS_JAVA_HEAP
 narrowKlass ArchiveBuilder::get_requested_narrow_klass(Klass* k) {
   assert(CDSConfig::is_dumping_heap(), "sanity");
   k = get_buffered_klass(k);
   Klass* requested_k = to_requested(k);
+  const int narrow_klass_shift = ArchiveBuilder::precomputed_narrow_klass_shift();
+#ifdef ASSERT
+  const size_t klass_alignment = MAX2(SharedSpaceObjectAlignment, (size_t)nth_bit(narrow_klass_shift));
+  assert(is_aligned(k, klass_alignment), "Klass " PTR_FORMAT " misaligned.", p2i(k));
+#endif
   address narrow_klass_base = _requested_static_archive_bottom; // runtime encoding base == runtime mapping start
-  const int narrow_klass_shift = ArchiveHeapWriter::precomputed_narrow_klass_shift;
-  return CompressedKlassPointers::encode_not_null(requested_k, narrow_klass_base, narrow_klass_shift);
+  // Note: use the "raw" version of encode that takes explicit narrow klass base and shift. Don't use any
+  // of the variants that do sanity checks, nor any of those that use the current - dump - JVM's encoding setting.
+  return CompressedKlassPointers::encode_not_null_without_asserts(requested_k, narrow_klass_base, narrow_klass_shift);
 }
 #endif // INCLUDE_CDS_JAVA_HEAP
 
 // RelocateBufferToRequested --- Relocate all the pointers in rw/ro,
 // so that the archive can be mapped to the "requested" location without runtime relocation.
@@ -1360,10 +1390,24 @@
 
   log_error(cds)("Unable to allocate from '%s' region: Please reduce the number of shared classes.", name);
   MetaspaceShared::unrecoverable_writing_error();
 }
 
+#ifdef _LP64
+int ArchiveBuilder::precomputed_narrow_klass_shift() {
+  // Legacy Mode:
+  //    We use 32 bits for narrowKlass, which should cover the full 4G Klass range. Shift can be 0.
+  // CompactObjectHeader Mode:
+  //    narrowKlass is much smaller, and we use the highest possible shift value to later get the maximum
+  //    Klass encoding range.
+  //
+  // Note that all of this may change in the future, if we decide to correct the pre-calculated
+  // narrow Klass IDs at archive load time.
+  assert(UseCompressedClassPointers, "Only needed for compressed class pointers");
+  return CompressedKlassPointers::tiny_classpointer_mode() ?  CompressedKlassPointers::max_shift() : 0;
+}
+#endif // _LP64
 
 #ifndef PRODUCT
 void ArchiveBuilder::assert_is_vm_thread() {
   assert(Thread::current()->is_VM_thread(), "ArchiveBuilder should be used only inside the VMThread");
 }
diff a/src/hotspot/share/cds/archiveBuilder.hpp b/src/hotspot/share/cds/archiveBuilder.hpp
--- a/src/hotspot/share/cds/archiveBuilder.hpp
+++ b/src/hotspot/share/cds/archiveBuilder.hpp
@@ -25,10 +25,11 @@
 #ifndef SHARE_CDS_ARCHIVEBUILDER_HPP
 #define SHARE_CDS_ARCHIVEBUILDER_HPP
 
 #include "cds/archiveUtils.hpp"
 #include "cds/dumpAllocStats.hpp"
+#include "memory/metaspace.hpp"
 #include "memory/metaspaceClosure.hpp"
 #include "oops/array.hpp"
 #include "oops/klass.hpp"
 #include "runtime/os.hpp"
 #include "utilities/bitMap.hpp"
@@ -41,13 +42,13 @@
 class FileMapInfo;
 class Klass;
 class MemRegion;
 class Symbol;
 
-// Metaspace::allocate() requires that all blocks must be aligned with KlassAlignmentInBytes.
-// We enforce the same alignment rule in blocks allocated from the shared space.
-const int SharedSpaceObjectAlignment = KlassAlignmentInBytes;
+// The minimum alignment for non-Klass objects inside the CDS archive. Klass objects need
+// to follow CompressedKlassPointers::klass_alignment_in_bytes().
+constexpr size_t SharedSpaceObjectAlignment = Metaspace::min_allocation_alignment_bytes;
 
 // Overview of CDS archive creation (for both static and dynamic dump):
 //
 // [1] Load all classes (static dump: from the classlist, dynamic dump: as part of app execution)
 // [2] Allocate "output buffer"
@@ -445,8 +446,27 @@
     return (Symbol*)current()->get_buffered_addr((address)src_symbol);
   }
 
   void print_stats();
   void report_out_of_space(const char* name, size_t needed_bytes);
+
+#ifdef _LP64
+  // Archived heap object headers (and soon, with Lilliput, markword prototypes) carry pre-computed
+  // narrow Klass ids calculated with the following scheme:
+  // 1) the encoding base must be the mapping start address.
+  // 2) shift must be large enough to result in an encoding range that covers the runtime Klass range.
+  //    That Klass range is defined by CDS archive size and runtime class space size. Luckily, the maximum
+  //    size can be predicted: archive size is assumed to be <1G, class space size capped at 3G, and at
+  //    runtime we put both regions adjacent to each other. Therefore, runtime Klass range size < 4G.
+  // The value of this precomputed shift depends on the class pointer mode at dump time.
+  // Legacy Mode:
+  //    Since nKlass itself is 32 bit, our encoding range len is 4G, and since we set the base directly
+  //    at mapping start, these 4G are enough. Therefore, we don't need to shift at all (shift=0).
+  // TinyClassPointer Mode:
+  //    To cover the 4G, we need the highest possible shift value. That may change in the future, if
+  //    we decide to correct the pre-calculated narrow Klass IDs at load time.
+  static int precomputed_narrow_klass_shift();
+#endif // _LP64
+
 };
 
 #endif // SHARE_CDS_ARCHIVEBUILDER_HPP
diff a/src/hotspot/share/cds/archiveHeapWriter.cpp b/src/hotspot/share/cds/archiveHeapWriter.cpp
--- a/src/hotspot/share/cds/archiveHeapWriter.cpp
+++ b/src/hotspot/share/cds/archiveHeapWriter.cpp
@@ -201,12 +201,17 @@
 
   HeapWord* mem = offset_to_buffered_address<HeapWord*>(_buffer_used);
   memset(mem, 0, byte_size);
   {
     // This is copied from MemAllocator::finish
-    oopDesc::set_mark(mem, markWord::prototype());
-    oopDesc::release_set_klass(mem, k);
+    if (UseCompactObjectHeaders) {
+      narrowKlass nk = ArchiveBuilder::current()->get_requested_narrow_klass(k);
+      oopDesc::release_set_mark(mem, markWord::prototype().set_narrow_klass(nk));
+    } else {
+      oopDesc::set_mark(mem, markWord::prototype());
+      oopDesc::release_set_klass(mem, k);
+    }
   }
   {
     // This is copied from ObjArrayAllocator::initialize
     arrayOopDesc::set_length(mem, length);
   }
@@ -323,13 +328,17 @@
 HeapWord* ArchiveHeapWriter::init_filler_array_at_buffer_top(int array_length, size_t fill_bytes) {
   assert(UseCompressedClassPointers, "Archived heap only supported for compressed klasses");
   Klass* oak = Universe::objectArrayKlass(); // already relocated to point to archived klass
   HeapWord* mem = offset_to_buffered_address<HeapWord*>(_buffer_used);
   memset(mem, 0, fill_bytes);
-  oopDesc::set_mark(mem, markWord::prototype());
   narrowKlass nk = ArchiveBuilder::current()->get_requested_narrow_klass(oak);
-  cast_to_oop(mem)->set_narrow_klass(nk);
+  if (UseCompactObjectHeaders) {
+    oopDesc::release_set_mark(mem, markWord::prototype().set_narrow_klass(nk));
+  } else {
+    oopDesc::set_mark(mem, markWord::prototype());
+    cast_to_oop(mem)->set_narrow_klass(nk);
+  }
   arrayOopDesc::set_length(mem, array_length);
   return mem;
 }
 
 void ArchiveHeapWriter::maybe_fill_gc_region_gap(size_t required_byte_size) {
@@ -525,19 +534,25 @@
   assert(UseCompressedClassPointers, "Archived heap only supported for compressed klasses");
   narrowKlass nk = ArchiveBuilder::current()->get_requested_narrow_klass(src_klass);
   address buffered_addr = requested_addr_to_buffered_addr(cast_from_oop<address>(requested_obj));
 
   oop fake_oop = cast_to_oop(buffered_addr);
-  fake_oop->set_narrow_klass(nk);
+  if (!UseCompactObjectHeaders) {
+    fake_oop->set_narrow_klass(nk);
+  }
 
   // We need to retain the identity_hash, because it may have been used by some hashtables
   // in the shared heap. This also has the side effect of pre-initializing the
   // identity_hash for all shared objects, so they are less likely to be written
   // into during run time, increasing the potential of memory sharing.
   if (src_obj != nullptr) {
     intptr_t src_hash = src_obj->identity_hash();
-    fake_oop->set_mark(markWord::prototype().copy_set_hash(src_hash));
+    if (UseCompactObjectHeaders) {
+      fake_oop->set_mark(markWord::prototype().set_narrow_klass(nk).copy_set_hash(src_hash));
+    } else {
+      fake_oop->set_mark(markWord::prototype().copy_set_hash(src_hash));
+    }
     assert(fake_oop->mark().is_unlocked(), "sanity");
 
     DEBUG_ONLY(intptr_t archived_hash = fake_oop->identity_hash());
     assert(src_hash == archived_hash, "Different hash codes: original " INTPTR_FORMAT ", archived " INTPTR_FORMAT, src_hash, archived_hash);
   }
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
@@ -247,19 +247,8 @@
   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);
   static address buffered_addr_to_requested_addr(address buffered_addr);
 
-  // Archived heap object headers carry pre-computed narrow Klass ids calculated with the
-  // following scheme:
-  // 1) the encoding base must be the mapping start address.
-  // 2) shift must be large enough to result in an encoding range that covers the runtime Klass range.
-  //    That Klass range is defined by CDS archive size and runtime class space size. Luckily, the maximum
-  //    size can be predicted: archive size is assumed to be <1G, class space size capped at 3G, and at
-  //    runtime we put both regions adjacent to each other. Therefore, runtime Klass range size < 4G.
-  //    Since nKlass itself is 32 bit, our encoding range len is 4G, and since we set the base directly
-  //    at mapping start, these 4G are enough. Therefore, we don't need to shift at all (shift=0).
-  static constexpr int precomputed_narrow_klass_shift = 0;
-
 };
 #endif // INCLUDE_CDS_JAVA_HEAP
 #endif // SHARE_CDS_ARCHIVEHEAPWRITER_HPP
diff a/src/hotspot/share/cds/archiveUtils.cpp b/src/hotspot/share/cds/archiveUtils.cpp
--- a/src/hotspot/share/cds/archiveUtils.cpp
+++ b/src/hotspot/share/cds/archiveUtils.cpp
@@ -239,13 +239,14 @@
   }
   log_debug(cds)("Expanding %s spaces by " SIZE_FORMAT_W(7) " bytes [total " SIZE_FORMAT_W(9)  " bytes ending at %p]",
                  which, commit, _vs->actual_committed_size(), _vs->high());
 }
 
-
-char* DumpRegion::allocate(size_t num_bytes) {
-  char* p = (char*)align_up(_top, (size_t)SharedSpaceObjectAlignment);
+char* DumpRegion::allocate(size_t num_bytes, size_t alignment) {
+  // Always align to at least minimum alignment
+  alignment = MAX2(SharedSpaceObjectAlignment, alignment);
+  char* p = (char*)align_up(_top, alignment);
   char* newtop = p + align_up(num_bytes, (size_t)SharedSpaceObjectAlignment);
   expand_top_to(newtop);
   memset(p, 0, newtop - p);
   return p;
 }
@@ -349,11 +350,11 @@
 
 void ReadClosure::do_tag(int tag) {
   int old_tag;
   old_tag = (int)(intptr_t)nextPtr();
   // do_int(&old_tag);
-  assert(tag == old_tag, "old tag doesn't match");
+  assert(tag == old_tag, "tag doesn't match (%d, expected %d)", old_tag, tag);
   FileMapInfo::assert_mark(tag == old_tag);
 }
 
 void ReadClosure::do_region(u_char* start, size_t size) {
   assert((intptr_t)start % sizeof(intptr_t) == 0, "bad alignment");
diff a/src/hotspot/share/cds/archiveUtils.hpp b/src/hotspot/share/cds/archiveUtils.hpp
--- a/src/hotspot/share/cds/archiveUtils.hpp
+++ b/src/hotspot/share/cds/archiveUtils.hpp
@@ -154,14 +154,15 @@
   void commit_to(char* newtop);
 
 public:
   DumpRegion(const char* name, uintx max_delta = 0)
     : _name(name), _base(nullptr), _top(nullptr), _end(nullptr),
-      _max_delta(max_delta), _is_packed(false) {}
+      _max_delta(max_delta), _is_packed(false),
+      _rs(NULL), _vs(NULL) {}
 
   char* expand_top_to(char* newtop);
-  char* allocate(size_t num_bytes);
+  char* allocate(size_t num_bytes, size_t alignment = 0);
 
   void append_intptr_t(intptr_t n, bool need_to_mark = false) NOT_CDS_RETURN;
 
   char* base()      const { return _base;        }
   char* top()       const { return _top;         }
diff a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp
--- a/src/hotspot/share/cds/cdsConfig.cpp
+++ b/src/hotspot/share/cds/cdsConfig.cpp
@@ -80,17 +80,24 @@
   if (_default_archive_path == nullptr) {
     char jvm_path[JVM_MAXPATHLEN];
     os::jvm_path(jvm_path, sizeof(jvm_path));
     char *end = strrchr(jvm_path, *os::file_separator());
     if (end != nullptr) *end = '\0';
-    size_t jvm_path_len = strlen(jvm_path);
-    size_t file_sep_len = strlen(os::file_separator());
-    const size_t len = jvm_path_len + file_sep_len + 20;
-    _default_archive_path = NEW_C_HEAP_ARRAY(char, len, mtArguments);
-    jio_snprintf(_default_archive_path, len,
-                LP64_ONLY(!UseCompressedOops ? "%s%sclasses_nocoops.jsa":) "%s%sclasses.jsa",
-                jvm_path, os::file_separator());
+    stringStream tmp;
+    tmp.print("%s%sclasses", jvm_path, os::file_separator());
+#ifdef _LP64
+    if (!UseCompressedOops) {
+      tmp.print_raw("_nocoops");
+    }
+    if (UseCompactObjectHeaders) {
+      // Note that generation of xxx_coh.jsa variants require
+      // --enable-cds-archive-coh at build time
+      tmp.print_raw("_coh");
+    }
+#endif
+    tmp.print_raw(".jsa");
+    _default_archive_path = os::strdup(tmp.base());
   }
   return _default_archive_path;
 }
 
 int CDSConfig::num_archives(const char* archive_path) {
diff a/src/hotspot/share/cds/dumpAllocStats.cpp b/src/hotspot/share/cds/dumpAllocStats.cpp
--- a/src/hotspot/share/cds/dumpAllocStats.cpp
+++ b/src/hotspot/share/cds/dumpAllocStats.cpp
@@ -105,5 +105,18 @@
   msg.debug("Class CP entries = %d, archived = %d (%3.1f%%)",
             _num_klass_cp_entries, _num_klass_cp_entries_archived,
             percent_of(_num_klass_cp_entries_archived, _num_klass_cp_entries));
 
 }
+
+#ifdef ASSERT
+void DumpAllocStats::verify(int expected_byte_size, bool read_only) const {
+  int bytes = 0;
+  const int what = (int)(read_only ? RO : RW);
+  for (int type = 0; type < int(_number_of_types); type ++) {
+    bytes += _bytes[what][type];
+  }
+  assert(bytes == expected_byte_size, "counter mismatch (%s: %d vs %d)",
+         (read_only ? "RO" : "RW"), bytes, expected_byte_size);
+}
+#endif // ASSERT
+
diff a/src/hotspot/share/cds/dumpAllocStats.hpp b/src/hotspot/share/cds/dumpAllocStats.hpp
--- a/src/hotspot/share/cds/dumpAllocStats.hpp
+++ b/src/hotspot/share/cds/dumpAllocStats.hpp
@@ -106,8 +106,11 @@
     _num_klass_cp_entries ++;
     _num_klass_cp_entries_archived += archived ? 1 : 0;
   }
 
   void print_stats(int ro_all, int rw_all);
+
+  DEBUG_ONLY(void verify(int expected_byte_size, bool read_only) const);
+
 };
 
 #endif // SHARE_CDS_DUMPALLOCSTATS_HPP
diff a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp
--- a/src/hotspot/share/cds/filemap.cpp
+++ b/src/hotspot/share/cds/filemap.cpp
@@ -53,10 +53,11 @@
 #include "memory/oopFactory.hpp"
 #include "memory/universe.hpp"
 #include "nmt/memTracker.hpp"
 #include "oops/compressedOops.hpp"
 #include "oops/compressedOops.inline.hpp"
+#include "oops/compressedKlass.hpp"
 #include "oops/objArrayOop.hpp"
 #include "oops/oop.inline.hpp"
 #include "prims/jvmtiExport.hpp"
 #include "runtime/arguments.hpp"
 #include "runtime/globals_extension.hpp"
@@ -202,17 +203,26 @@
     copy_base_archive_name(CDSConfig::static_archive_path());
   }
   _core_region_alignment = core_region_alignment;
   _obj_alignment = ObjectAlignmentInBytes;
   _compact_strings = CompactStrings;
+  _compact_headers = UseCompactObjectHeaders;
   if (CDSConfig::is_dumping_heap()) {
     _narrow_oop_mode = CompressedOops::mode();
     _narrow_oop_base = CompressedOops::base();
     _narrow_oop_shift = CompressedOops::shift();
   }
   _compressed_oops = UseCompressedOops;
   _compressed_class_ptrs = UseCompressedClassPointers;
+  if (UseCompressedClassPointers) {
+#ifdef _LP64
+    _narrow_klass_pointer_bits = CompressedKlassPointers::narrow_klass_pointer_bits();
+    _narrow_klass_shift = ArchiveBuilder::precomputed_narrow_klass_shift();
+#endif
+  } else {
+    _narrow_klass_pointer_bits = _narrow_klass_shift = -1;
+  }
   _max_heap_size = MaxHeapSize;
   _use_optimized_module_handling = CDSConfig::is_using_optimized_module_handling();
   _has_full_module_graph = CDSConfig::is_dumping_full_module_graph();
 
   // The following fields are for sanity checks for whether this archive
@@ -268,14 +278,17 @@
   st->print_cr("- obj_alignment:                  %d", _obj_alignment);
   st->print_cr("- narrow_oop_base:                " INTPTR_FORMAT, p2i(_narrow_oop_base));
   st->print_cr("- narrow_oop_base:                " INTPTR_FORMAT, p2i(_narrow_oop_base));
   st->print_cr("- narrow_oop_shift                %d", _narrow_oop_shift);
   st->print_cr("- compact_strings:                %d", _compact_strings);
+  st->print_cr("- compact_headers:                %d", _compact_headers);
   st->print_cr("- max_heap_size:                  " UINTX_FORMAT, _max_heap_size);
   st->print_cr("- narrow_oop_mode:                %d", _narrow_oop_mode);
   st->print_cr("- compressed_oops:                %d", _compressed_oops);
   st->print_cr("- compressed_class_ptrs:          %d", _compressed_class_ptrs);
+  st->print_cr("- narrow_klass_pointer_bits:      %d", _narrow_klass_pointer_bits);
+  st->print_cr("- narrow_klass_shift:             %d", _narrow_klass_shift);
   st->print_cr("- cloned_vtables_offset:          " SIZE_FORMAT_X, _cloned_vtables_offset);
   st->print_cr("- serialized_data_offset:         " SIZE_FORMAT_X, _serialized_data_offset);
   st->print_cr("- jvm_ident:                      %s", _jvm_ident);
   st->print_cr("- shared_path_table_offset:       " SIZE_FORMAT_X, _shared_path_table_offset);
   st->print_cr("- app_class_paths_start_index:    %d", _app_class_paths_start_index);
@@ -2043,38 +2056,64 @@
     // because we won't install an archived object subgraph if the klass of any of the
     // referenced objects are replaced. See HeapShared::initialize_from_archived_subgraph().
   }
 
   // We pre-compute narrow Klass IDs with the runtime mapping start intended to be the base, and a shift of
-  // ArchiveHeapWriter::precomputed_narrow_klass_shift. We enforce this encoding at runtime (see
+  // ArchiveBuilder::precomputed_narrow_klass_shift. We enforce this encoding at runtime (see
   // CompressedKlassPointers::initialize_for_given_encoding()). Therefore, the following assertions must
   // hold:
   address archive_narrow_klass_base = (address)header()->mapped_base_address();
-  const int archive_narrow_klass_shift = ArchiveHeapWriter::precomputed_narrow_klass_shift;
+  const int archive_narrow_klass_pointer_bits = header()->narrow_klass_pointer_bits();
+  const int archive_narrow_klass_shift = header()->narrow_klass_shift();
 
   log_info(cds)("CDS archive was created with max heap size = " SIZE_FORMAT "M, and the following configuration:",
                 max_heap_size()/M);
-  log_info(cds)("    narrow_klass_base at mapping start address, narrow_klass_shift = %d",
-                archive_narrow_klass_shift);
+  log_info(cds)("    narrow_klass_base at mapping start address, narrow_klass_pointer_bits = %d, narrow_klass_shift = %d",
+                archive_narrow_klass_pointer_bits, archive_narrow_klass_shift);
   log_info(cds)("    narrow_oop_mode = %d, narrow_oop_base = " PTR_FORMAT ", narrow_oop_shift = %d",
                 narrow_oop_mode(), p2i(narrow_oop_base()), narrow_oop_shift());
   log_info(cds)("The current max heap size = " SIZE_FORMAT "M, HeapRegion::GrainBytes = " SIZE_FORMAT,
                 MaxHeapSize/M, HeapRegion::GrainBytes);
-  log_info(cds)("    narrow_klass_base = " PTR_FORMAT ", narrow_klass_shift = %d",
-                p2i(CompressedKlassPointers::base()), CompressedKlassPointers::shift());
+  log_info(cds)("    narrow_klass_base = " PTR_FORMAT ", arrow_klass_pointer_bits = %d, narrow_klass_shift = %d",
+                p2i(CompressedKlassPointers::base()), CompressedKlassPointers::narrow_klass_pointer_bits(), CompressedKlassPointers::shift());
   log_info(cds)("    narrow_oop_mode = %d, narrow_oop_base = " PTR_FORMAT ", narrow_oop_shift = %d",
                 CompressedOops::mode(), p2i(CompressedOops::base()), CompressedOops::shift());
   log_info(cds)("    heap range = [" PTR_FORMAT " - "  PTR_FORMAT "]",
                 UseCompressedOops ? p2i(CompressedOops::begin()) :
                                     UseG1GC ? p2i((address)G1CollectedHeap::heap()->reserved().start()) : 0L,
                 UseCompressedOops ? p2i(CompressedOops::end()) :
                                     UseG1GC ? p2i((address)G1CollectedHeap::heap()->reserved().end()) : 0L);
 
-  assert(archive_narrow_klass_base == CompressedKlassPointers::base(), "Unexpected encoding base encountered "
-         "(" PTR_FORMAT ", expected " PTR_FORMAT ")", p2i(CompressedKlassPointers::base()), p2i(archive_narrow_klass_base));
-  assert(archive_narrow_klass_shift == CompressedKlassPointers::shift(), "Unexpected encoding shift encountered "
-         "(%d, expected %d)", CompressedKlassPointers::shift(), archive_narrow_klass_shift);
+  int err = 0;
+  if ( archive_narrow_klass_base != CompressedKlassPointers::base() ||
+       (err = 1, archive_narrow_klass_pointer_bits != CompressedKlassPointers::narrow_klass_pointer_bits()) ||
+       (err = 2, archive_narrow_klass_shift != CompressedKlassPointers::shift()) ) {
+    stringStream ss;
+    switch (err) {
+    case 0:
+      ss.print("Unexpected encoding base encountered (" PTR_FORMAT ", expected " PTR_FORMAT ")",
+               p2i(CompressedKlassPointers::base()), p2i(archive_narrow_klass_base));
+      break;
+    case 1:
+      ss.print("Unexpected narrow Klass bit length encountered (%d, expected %d)",
+               CompressedKlassPointers::narrow_klass_pointer_bits(), archive_narrow_klass_pointer_bits);
+      break;
+    case 2:
+      ss.print("Unexpected narrow Klass shift encountered (%d, expected %d)",
+               CompressedKlassPointers::shift(), archive_narrow_klass_shift);
+      break;
+    default:
+      ShouldNotReachHere();
+    };
+    LogTarget(Info, cds) lt;
+    if (lt.is_enabled()) {
+      LogStream ls(lt);
+      ls.print_raw(ss.base());
+      header()->print(&ls);
+    }
+    assert(false, "%s", ss.base());
+  }
 
   return true;
 }
 
 // The actual address of this region during dump time.
@@ -2436,10 +2475,18 @@
     log_info(cds)("Unable to use shared archive.\nThe saved state of UseCompressedOops and UseCompressedClassPointers is "
                                "different from runtime, CDS will be disabled.");
     return false;
   }
 
+  if (compact_headers() != UseCompactObjectHeaders) {
+    log_info(cds)("The shared archive file's UseCompactObjectHeaders setting (%s)"
+                  " does not equal the current UseCompactObjectHeaders setting (%s).",
+                  _compact_headers          ? "enabled" : "disabled",
+                  UseCompactObjectHeaders   ? "enabled" : "disabled");
+    return false;
+  }
+
   if (!_use_optimized_module_handling) {
     CDSConfig::stop_using_optimized_module_handling();
     log_info(cds)("optimized module handling: disabled because archive was created without optimized module handling");
   }
 
diff a/src/hotspot/share/cds/filemap.hpp b/src/hotspot/share/cds/filemap.hpp
--- a/src/hotspot/share/cds/filemap.hpp
+++ b/src/hotspot/share/cds/filemap.hpp
@@ -185,14 +185,17 @@
   size_t _core_region_alignment;                  // how shared archive should be aligned
   int    _obj_alignment;                          // value of ObjectAlignmentInBytes
   address _narrow_oop_base;                       // compressed oop encoding base
   int    _narrow_oop_shift;                       // compressed oop encoding shift
   bool   _compact_strings;                        // value of CompactStrings
+  bool   _compact_headers;                        // value of UseCompactObjectHeaders
   uintx  _max_heap_size;                          // java max heap size during dumping
   CompressedOops::Mode _narrow_oop_mode;          // compressed oop encoding mode
   bool    _compressed_oops;                       // save the flag UseCompressedOops
   bool    _compressed_class_ptrs;                 // save the flag UseCompressedClassPointers
+  int     _narrow_klass_pointer_bits;             // save number of bits in narrowKlass
+  int     _narrow_klass_shift;                    // save shift width used to pre-compute narrowKlass IDs in archived heap objects
   size_t  _cloned_vtables_offset;                 // The address of the first cloned vtable
   size_t  _serialized_data_offset;                // Data accessed using {ReadClosure,WriteClosure}::serialize()
   bool _has_non_jar_in_classpath;                 // non-jar file entry exists in classpath
   unsigned int _common_app_classpath_prefix_size; // size of the common prefix of app class paths
                                                   //    0 if no common prefix exists
@@ -254,10 +257,11 @@
   size_t core_region_alignment()           const { return _core_region_alignment; }
   int obj_alignment()                      const { return _obj_alignment; }
   address narrow_oop_base()                const { return _narrow_oop_base; }
   int narrow_oop_shift()                   const { return _narrow_oop_shift; }
   bool compact_strings()                   const { return _compact_strings; }
+  bool compact_headers()                   const { return _compact_headers; }
   uintx max_heap_size()                    const { return _max_heap_size; }
   CompressedOops::Mode narrow_oop_mode()   const { return _narrow_oop_mode; }
   char* cloned_vtables()                   const { return from_mapped_offset(_cloned_vtables_offset); }
   char* serialized_data()                  const { return from_mapped_offset(_serialized_data_offset); }
   const char* jvm_ident()                  const { return _jvm_ident; }
@@ -265,10 +269,12 @@
   char* mapped_base_address()              const { return _mapped_base_address; }
   bool has_platform_or_app_classes()       const { return _has_platform_or_app_classes; }
   bool has_non_jar_in_classpath()          const { return _has_non_jar_in_classpath; }
   bool compressed_oops()                   const { return _compressed_oops; }
   bool compressed_class_pointers()         const { return _compressed_class_ptrs; }
+  int narrow_klass_pointer_bits()          const { return _narrow_klass_pointer_bits; }
+  int narrow_klass_shift()                 const { return _narrow_klass_shift; }
   size_t heap_roots_offset()               const { return _heap_roots_offset; }
   size_t heap_oopmap_start_pos()           const { return _heap_oopmap_start_pos;}
   size_t heap_ptrmap_start_pos()           const { return _heap_ptrmap_start_pos;}
   // FIXME: These should really return int
   jshort max_used_path_index()             const { return _max_used_path_index; }
diff a/src/hotspot/share/cds/metaspaceShared.cpp b/src/hotspot/share/cds/metaspaceShared.cpp
--- a/src/hotspot/share/cds/metaspaceShared.cpp
+++ b/src/hotspot/share/cds/metaspaceShared.cpp
@@ -84,10 +84,11 @@
 #include "runtime/vmThread.hpp"
 #include "sanitizers/leak.hpp"
 #include "utilities/align.hpp"
 #include "utilities/bitMap.inline.hpp"
 #include "utilities/defaultStream.hpp"
+#include "utilities/macros.hpp"
 #include "utilities/ostream.hpp"
 #include "utilities/resourceHash.hpp"
 
 ReservedSpace MetaspaceShared::_symbol_rs;
 VirtualSpace MetaspaceShared::_symbol_vs;
@@ -1156,23 +1157,29 @@
           // Set up compressed Klass pointer encoding: the encoding range must
           //  cover both archive and class space.
           address cds_base = (address)static_mapinfo->mapped_base();
           address ccs_end = (address)class_space_rs.end();
           assert(ccs_end > cds_base, "Sanity check");
-#if INCLUDE_CDS_JAVA_HEAP
-          // We archived objects with pre-computed narrow Klass id. Set up encoding such that these Ids stay valid.
-          address precomputed_narrow_klass_base = cds_base;
-          const int precomputed_narrow_klass_shift = ArchiveHeapWriter::precomputed_narrow_klass_shift;
-          CompressedKlassPointers::initialize_for_given_encoding(
-            cds_base, ccs_end - cds_base, // Klass range
-            precomputed_narrow_klass_base, precomputed_narrow_klass_shift // precomputed encoding, see ArchiveHeapWriter
-            );
-#else
-          CompressedKlassPointers::initialize (
-            cds_base, ccs_end - cds_base // Klass range
+          if (INCLUDE_CDS_JAVA_HEAP || UseCompactObjectHeaders) {
+            // The CDS archive may contain narrow Klass IDs that were precomputed at archive generation time:
+            // - every archived java object header (only if INCLUDE_CDS_JAVA_HEAP)
+            // - every archived Klass' prototype   (only if +UseCompactObjectHeaders)
+            //
+            // In order for those IDs to still be valid, we need to dictate base and shift: base should be the
+            // mapping start, shift the shift used at archive generation time.
+            address precomputed_narrow_klass_base = cds_base;
+            const int precomputed_narrow_klass_shift = ArchiveBuilder::precomputed_narrow_klass_shift();
+            CompressedKlassPointers::initialize_for_given_encoding(
+              cds_base, ccs_end - cds_base, // Klass range
+              precomputed_narrow_klass_base, precomputed_narrow_klass_shift // precomputed encoding, see ArchiveBuilder
             );
-#endif // INCLUDE_CDS_JAVA_HEAP
+          } else {
+            // Let JVM freely chose encoding base and shift
+            CompressedKlassPointers::initialize (
+              cds_base, ccs_end - cds_base // Klass range
+              );
+          }
           // map_or_load_heap_region() compares the current narrow oop and klass encodings
           // with the archived ones, so it must be done after all encodings are determined.
           static_mapinfo->map_or_load_heap_region();
         }
 #endif // _LP64
@@ -1223,11 +1230,11 @@
 //  use_archive_base_addr address is false, this base address is determined
 //  by the platform.
 //
 // If UseCompressedClassPointers=1, the range encompassing both spaces will be
 //  suitable to en/decode narrow Klass pointers: the base will be valid for
-//  encoding, the range [Base, End) not surpass KlassEncodingMetaspaceMax.
+//  encoding, the range [Base, End) and not surpass the max. range for that encoding.
 //
 // Return:
 //
 // - On success:
 //    - total_space_rs will be reserved as whole for archive_space_rs and
diff a/src/hotspot/share/ci/ciKlass.cpp b/src/hotspot/share/ci/ciKlass.cpp
--- a/src/hotspot/share/ci/ciKlass.cpp
+++ b/src/hotspot/share/ci/ciKlass.cpp
@@ -247,5 +247,25 @@
 const char* ciKlass::external_name() const {
   GUARDED_VM_ENTRY(
     return get_Klass()->external_name();
   )
 }
+
+// ------------------------------------------------------------------
+// ciKlass::prototype_header_offset
+juint ciKlass::prototype_header_offset() {
+  assert(is_loaded(), "must be loaded");
+
+  VM_ENTRY_MARK;
+  Klass* this_klass = get_Klass();
+  return in_bytes(this_klass->prototype_header_offset());
+}
+
+// ------------------------------------------------------------------
+// ciKlass::prototype_header
+uintptr_t ciKlass::prototype_header() {
+  assert(is_loaded(), "must be loaded");
+
+  VM_ENTRY_MARK;
+  Klass* this_klass = get_Klass();
+  return (uintptr_t)this_klass->prototype_header().to_pointer();
+}
diff a/src/hotspot/share/ci/ciKlass.hpp b/src/hotspot/share/ci/ciKlass.hpp
--- a/src/hotspot/share/ci/ciKlass.hpp
+++ b/src/hotspot/share/ci/ciKlass.hpp
@@ -127,8 +127,11 @@
   virtual ciKlass* exact_klass() = 0;
 
   void print_name_on(outputStream* st);
 
   const char* external_name() const;
+
+  juint prototype_header_offset();
+  uintptr_t prototype_header();
 };
 
 #endif // SHARE_CI_CIKLASS_HPP
diff a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp
--- a/src/hotspot/share/classfile/systemDictionaryShared.cpp
+++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp
@@ -57,10 +57,11 @@
 #include "memory/metadataFactory.hpp"
 #include "memory/metaspaceClosure.hpp"
 #include "memory/oopFactory.hpp"
 #include "memory/resourceArea.hpp"
 #include "memory/universe.hpp"
+#include "oops/compressedKlass.inline.hpp"
 #include "oops/instanceKlass.hpp"
 #include "oops/klass.inline.hpp"
 #include "oops/objArrayKlass.hpp"
 #include "oops/objArrayOop.inline.hpp"
 #include "oops/oop.inline.hpp"
@@ -419,10 +420,17 @@
         SharedClassLoadingMark slm(THREAD, k);
         k = find_or_define_instance_class(name, class_loader, k, CHECK_NULL);
       }
     }
   }
+
+#ifdef ASSERT
+    if (UseCompressedClassPointers && k != nullptr) {
+      CompressedKlassPointers::check_valid_klass(k);
+    }
+#endif
+
   return k;
 }
 
 class UnregisteredClassesTable : public ResourceHashtable<
   Symbol*, InstanceKlass*,
@@ -1328,11 +1336,15 @@
   const RunTimeClassInfo* record = find_record(&_static_archive._builtin_dictionary,
                                                &_dynamic_archive._builtin_dictionary,
                                                name);
   if (record != nullptr) {
     assert(!record->_klass->is_hidden(), "hidden class cannot be looked up by name");
-    assert(check_alignment(record->_klass), "Address not aligned");
+#ifdef _LP64
+    if (UseCompressedClassPointers) {
+      DEBUG_ONLY(CompressedKlassPointers::check_valid_klass(record->_klass);)
+    }
+#endif
     // We did not save the classfile data of the generated LambdaForm invoker classes,
     // so we cannot support CLFH for such classes.
     if (record->_klass->is_generated_shared_class() && JvmtiExport::should_post_class_file_load_hook()) {
        return nullptr;
     }
diff a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp
--- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp
+++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp
@@ -82,12 +82,12 @@
 #include "gc/shared/gcTimer.hpp"
 #include "gc/shared/gcTraceTime.inline.hpp"
 #include "gc/shared/isGCActiveMark.hpp"
 #include "gc/shared/locationPrinter.inline.hpp"
 #include "gc/shared/oopStorageParState.hpp"
-#include "gc/shared/preservedMarks.inline.hpp"
 #include "gc/shared/referenceProcessor.inline.hpp"
+#include "gc/shared/slidingForwarding.hpp"
 #include "gc/shared/suspendibleThreadSet.hpp"
 #include "gc/shared/taskqueue.inline.hpp"
 #include "gc/shared/taskTerminator.hpp"
 #include "gc/shared/tlab_globals.hpp"
 #include "gc/shared/workerPolicy.hpp"
@@ -1438,10 +1438,12 @@
   CPUTimeCounters::create_counter(CPUTimeGroups::CPUTimeType::gc_conc_refine);
   CPUTimeCounters::create_counter(CPUTimeGroups::CPUTimeType::gc_service);
 
   G1InitLogger::print();
 
+  SlidingForwarding::initialize(heap_rs.region(), HeapRegion::GrainWords);
+
   return JNI_OK;
 }
 
 bool G1CollectedHeap::concurrent_mark_is_terminating() const {
   return _cm_thread->should_terminate();
diff a/src/hotspot/share/gc/g1/g1FullCollector.cpp b/src/hotspot/share/gc/g1/g1FullCollector.cpp
--- a/src/hotspot/share/gc/g1/g1FullCollector.cpp
+++ b/src/hotspot/share/gc/g1/g1FullCollector.cpp
@@ -38,10 +38,11 @@
 #include "gc/g1/g1RegionMarkStatsCache.inline.hpp"
 #include "gc/shared/gcTraceTime.inline.hpp"
 #include "gc/shared/preservedMarks.inline.hpp"
 #include "gc/shared/classUnloadingContext.hpp"
 #include "gc/shared/referenceProcessor.hpp"
+#include "gc/shared/slidingForwarding.hpp"
 #include "gc/shared/verifyOption.hpp"
 #include "gc/shared/weakProcessor.inline.hpp"
 #include "gc/shared/workerPolicy.hpp"
 #include "logging/log.hpp"
 #include "runtime/handles.inline.hpp"
@@ -210,10 +211,12 @@
   verify_after_marking();
 
   // Don't add any more derived pointers during later phases
   deactivate_derived_pointers();
 
+  SlidingForwarding::begin();
+
   phase2_prepare_compaction();
 
   if (has_compaction_targets()) {
     phase3_adjust_pointers();
 
@@ -222,10 +225,12 @@
     // All regions have a high live ratio thus will not be compacted.
     // The live ratio is only considered if do_maximal_compaction is false.
     log_info(gc, phases) ("No Regions selected for compaction. Skipping Phase 3: Adjust pointers and Phase 4: Compact heap");
   }
 
+  SlidingForwarding::end();
+
   phase5_reset_metadata();
 
   G1CollectedHeap::finish_codecache_marking_cycle();
 }
 
diff a/src/hotspot/share/gc/g1/g1FullGCCompactTask.cpp b/src/hotspot/share/gc/g1/g1FullGCCompactTask.cpp
--- a/src/hotspot/share/gc/g1/g1FullGCCompactTask.cpp
+++ b/src/hotspot/share/gc/g1/g1FullGCCompactTask.cpp
@@ -28,10 +28,11 @@
 #include "gc/g1/g1FullCollector.inline.hpp"
 #include "gc/g1/g1FullGCCompactionPoint.hpp"
 #include "gc/g1/g1FullGCCompactTask.hpp"
 #include "gc/g1/g1HeapRegion.inline.hpp"
 #include "gc/shared/gcTraceTime.inline.hpp"
+#include "gc/shared/slidingForwarding.inline.hpp"
 #include "logging/log.hpp"
 #include "oops/oop.inline.hpp"
 #include "utilities/ticks.hpp"
 
 void G1FullGCCompactTask::G1CompactRegionClosure::clear_in_bitmap(oop obj) {
@@ -39,28 +40,28 @@
   _bitmap->clear(obj);
 }
 
 size_t G1FullGCCompactTask::G1CompactRegionClosure::apply(oop obj) {
   size_t size = obj->size();
-  if (obj->is_forwarded()) {
+  if (SlidingForwarding::is_forwarded(obj)) {
     G1FullGCCompactTask::copy_object_to_new_location(obj);
   }
 
   // Clear the mark for the compacted object to allow reuse of the
   // bitmap without an additional clearing step.
   clear_in_bitmap(obj);
   return size;
 }
 
 void G1FullGCCompactTask::copy_object_to_new_location(oop obj) {
-  assert(obj->is_forwarded(), "Sanity!");
-  assert(obj->forwardee() != obj, "Object must have a new location");
+  assert(SlidingForwarding::is_forwarded(obj), "Sanity!");
+  assert(SlidingForwarding::forwardee(obj) != obj, "Object must have a new location");
 
   size_t size = obj->size();
   // Copy object and reinit its mark.
   HeapWord* obj_addr = cast_from_oop<HeapWord*>(obj);
-  HeapWord* destination = cast_from_oop<HeapWord*>(obj->forwardee());
+  HeapWord* destination = cast_from_oop<HeapWord*>(SlidingForwarding::forwardee(obj));
   Copy::aligned_conjoint_words(obj_addr, destination, size);
 
   // There is no need to transform stack chunks - marking already did that.
   cast_to_oop(destination)->init_mark();
   assert(cast_to_oop(destination)->klass() != nullptr, "should have a class");
@@ -119,11 +120,11 @@
 
   oop obj = cast_to_oop(src_hr->bottom());
   size_t word_size = obj->size();
 
   uint num_regions = (uint)G1CollectedHeap::humongous_obj_size_in_regions(word_size);
-  HeapWord* destination = cast_from_oop<HeapWord*>(obj->forwardee());
+  HeapWord* destination = cast_from_oop<HeapWord*>(SlidingForwarding::forwardee(obj));
 
   assert(collector()->mark_bitmap()->is_marked(obj), "Should only compact marked objects");
   collector()->mark_bitmap()->clear(obj);
 
   copy_object_to_new_location(obj);
diff a/src/hotspot/share/gc/g1/g1FullGCCompactionPoint.cpp b/src/hotspot/share/gc/g1/g1FullGCCompactionPoint.cpp
--- a/src/hotspot/share/gc/g1/g1FullGCCompactionPoint.cpp
+++ b/src/hotspot/share/gc/g1/g1FullGCCompactionPoint.cpp
@@ -25,10 +25,11 @@
 #include "precompiled.hpp"
 #include "gc/g1/g1FullCollector.inline.hpp"
 #include "gc/g1/g1FullGCCompactionPoint.hpp"
 #include "gc/g1/g1HeapRegion.hpp"
 #include "gc/shared/preservedMarks.inline.hpp"
+#include "gc/shared/slidingForwarding.inline.hpp"
 #include "oops/oop.inline.hpp"
 #include "utilities/debug.hpp"
 
 G1FullGCCompactionPoint::G1FullGCCompactionPoint(G1FullCollector* collector, PreservedMarks* preserved_stack) :
     _collector(collector),
@@ -104,14 +105,14 @@
   // Store a forwarding pointer if the object should be moved.
   if (cast_from_oop<HeapWord*>(object) != _compaction_top) {
     if (!object->is_forwarded()) {
       preserved_stack()->push_if_necessary(object, object->mark());
     }
-    object->forward_to(cast_to_oop(_compaction_top));
-    assert(object->is_forwarded(), "must be forwarded");
+    SlidingForwarding::forward_to(object, cast_to_oop(_compaction_top));
+    assert(SlidingForwarding::is_forwarded(object), "must be forwarded");
   } else {
-    assert(!object->is_forwarded(), "must not be forwarded");
+    assert(SlidingForwarding::is_not_forwarded(object), "must not be forwarded");
   }
 
   // Update compaction values.
   _compaction_top += size;
   _current_region->update_bot_for_block(_compaction_top - size, _compaction_top);
@@ -170,12 +171,12 @@
 
   // Preserve the mark for the humongous object as the region was initially not compacting.
   preserved_stack()->push_if_necessary(obj, obj->mark());
 
   HeapRegion* dest_hr = _compaction_regions->at(range_begin);
-  obj->forward_to(cast_to_oop(dest_hr->bottom()));
-  assert(obj->is_forwarded(), "Object must be forwarded!");
+  SlidingForwarding::forward_to(obj, cast_to_oop(dest_hr->bottom()));
+  assert(SlidingForwarding::is_forwarded(obj), "Object must be forwarded!");
 
   // Add the humongous object regions to the compaction point.
   add_humongous(hr);
 
   // Remove covered regions from compaction target candidates.
diff a/src/hotspot/share/gc/g1/g1FullGCOopClosures.inline.hpp b/src/hotspot/share/gc/g1/g1FullGCOopClosures.inline.hpp
--- a/src/hotspot/share/gc/g1/g1FullGCOopClosures.inline.hpp
+++ b/src/hotspot/share/gc/g1/g1FullGCOopClosures.inline.hpp
@@ -30,10 +30,11 @@
 #include "gc/g1/g1Allocator.inline.hpp"
 #include "gc/g1/g1ConcurrentMarkBitMap.inline.hpp"
 #include "gc/g1/g1FullCollector.inline.hpp"
 #include "gc/g1/g1FullGCMarker.inline.hpp"
 #include "gc/g1/g1HeapRegionRemSet.inline.hpp"
+#include "gc/shared/slidingForwarding.inline.hpp"
 #include "memory/iterator.inline.hpp"
 #include "memory/universe.hpp"
 #include "oops/access.inline.hpp"
 #include "oops/compressedOops.inline.hpp"
 #include "oops/oop.inline.hpp"
@@ -63,12 +64,12 @@
     // We never forward objects in non-compacting regions so there is no need to
     // process them further.
     return;
   }
 
-  if (obj->is_forwarded()) {
-    oop forwardee = obj->forwardee();
+  if (SlidingForwarding::is_forwarded(obj)) {
+    oop forwardee = SlidingForwarding::forwardee(obj);
     // Forwarded, just update.
     assert(G1CollectedHeap::heap()->is_in_reserved(forwardee), "should be in object space");
     RawAccess<IS_NOT_NULL>::oop_store(p, forwardee);
   }
 
diff a/src/hotspot/share/gc/g1/g1FullGCPrepareTask.inline.hpp b/src/hotspot/share/gc/g1/g1FullGCPrepareTask.inline.hpp
--- a/src/hotspot/share/gc/g1/g1FullGCPrepareTask.inline.hpp
+++ b/src/hotspot/share/gc/g1/g1FullGCPrepareTask.inline.hpp
@@ -30,10 +30,11 @@
 #include "gc/g1/g1CollectedHeap.inline.hpp"
 #include "gc/g1/g1FullCollector.hpp"
 #include "gc/g1/g1FullGCCompactionPoint.hpp"
 #include "gc/g1/g1FullGCScope.hpp"
 #include "gc/g1/g1HeapRegion.inline.hpp"
+#include "gc/shared/slidingForwarding.inline.hpp"
 
 void G1DetermineCompactionQueueClosure::free_empty_humongous_region(HeapRegion* hr) {
   _g1h->free_humongous_region(hr, nullptr);
   _collector->set_free(hr->hrm_index());
   add_to_compaction_queue(hr);
@@ -112,14 +113,14 @@
 
   return false;
 }
 
 inline size_t G1SerialRePrepareClosure::apply(oop obj) {
-  if (obj->is_forwarded()) {
+  if (SlidingForwarding::is_forwarded(obj)) {
     // We skip objects compiled into the first region or
     // into regions not part of the serial compaction point.
-    if (cast_from_oop<HeapWord*>(obj->forwardee()) < _dense_prefix_top) {
+    if (cast_from_oop<HeapWord*>(SlidingForwarding::forwardee(obj)) < _dense_prefix_top) {
       return obj->size();
     }
   }
 
   // Get size and forward.
diff a/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp b/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp
--- a/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp
+++ b/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp
@@ -103,11 +103,10 @@
   _gc_par_phases[RecalculateUsed] = new WorkerDataArray<double>("RecalculateUsed", "Recalculate Used Memory (ms):", max_gc_threads);
 #if COMPILER2_OR_JVMCI
   _gc_par_phases[UpdateDerivedPointers] = new WorkerDataArray<double>("UpdateDerivedPointers", "Update Derived Pointers (ms):", max_gc_threads);
 #endif
   _gc_par_phases[EagerlyReclaimHumongousObjects] = new WorkerDataArray<double>("EagerlyReclaimHumongousObjects", "Eagerly Reclaim Humongous Objects (ms):", max_gc_threads);
-  _gc_par_phases[RestorePreservedMarks] = new WorkerDataArray<double>("RestorePreservedMarks", "Restore Preserved Marks (ms):", max_gc_threads);
   _gc_par_phases[ProcessEvacuationFailedRegions] = new WorkerDataArray<double>("ProcessEvacuationFailedRegions", "Process Evacuation Failed Regions (ms):", max_gc_threads);
 
   _gc_par_phases[ScanHR]->create_thread_work_items("Scanned Cards:", ScanHRScannedCards);
   _gc_par_phases[ScanHR]->create_thread_work_items("Scanned Blocks:", ScanHRScannedBlocks);
   _gc_par_phases[ScanHR]->create_thread_work_items("Claimed Chunks:", ScanHRClaimedChunks);
@@ -510,11 +509,10 @@
   }
 
   debug_time("Post Evacuate Cleanup 2", _cur_post_evacuate_cleanup_2_time_ms);
   if (evacuation_failed) {
     debug_phase(_gc_par_phases[RecalculateUsed], 1);
-    debug_phase(_gc_par_phases[RestorePreservedMarks], 1);
     debug_phase(_gc_par_phases[ProcessEvacuationFailedRegions], 1);
   }
 #if COMPILER2_OR_JVMCI
   debug_phase(_gc_par_phases[UpdateDerivedPointers], 1);
 #endif
diff a/src/hotspot/share/gc/g1/g1GCPhaseTimes.hpp b/src/hotspot/share/gc/g1/g1GCPhaseTimes.hpp
--- a/src/hotspot/share/gc/g1/g1GCPhaseTimes.hpp
+++ b/src/hotspot/share/gc/g1/g1GCPhaseTimes.hpp
@@ -85,11 +85,10 @@
     RecalculateUsed,
 #if COMPILER2_OR_JVMCI
     UpdateDerivedPointers,
 #endif
     EagerlyReclaimHumongousObjects,
-    RestorePreservedMarks,
     ProcessEvacuationFailedRegions,
     ResetMarkingState,
     NoteStartOfMark,
     GCParPhasesSentinel
   };
diff a/src/hotspot/share/gc/g1/g1OopClosures.inline.hpp b/src/hotspot/share/gc/g1/g1OopClosures.inline.hpp
--- a/src/hotspot/share/gc/g1/g1OopClosures.inline.hpp
+++ b/src/hotspot/share/gc/g1/g1OopClosures.inline.hpp
@@ -225,12 +225,12 @@
 
   const G1HeapRegionAttr state = _g1h->region_attr(obj);
   if (state.is_in_cset()) {
     oop forwardee;
     markWord m = obj->mark();
-    if (m.is_marked()) {
-      forwardee = cast_to_oop(m.decode_pointer());
+    if (m.is_forwarded()) {
+      forwardee = obj->forwardee(m);
     } else {
       forwardee = _par_scan_state->copy_to_survivor_space(state, obj, m);
     }
     assert(forwardee != nullptr, "forwardee should not be null");
     RawAccess<IS_NOT_NULL>::oop_store(p, forwardee);
diff a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp
--- a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp
+++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp
@@ -33,11 +33,10 @@
 #include "gc/g1/g1StringDedup.hpp"
 #include "gc/g1/g1Trace.hpp"
 #include "gc/g1/g1YoungGCAllocationFailureInjector.inline.hpp"
 #include "gc/shared/continuationGCSupport.inline.hpp"
 #include "gc/shared/partialArrayTaskStepper.inline.hpp"
-#include "gc/shared/preservedMarks.inline.hpp"
 #include "gc/shared/stringdedup/stringDedup.hpp"
 #include "gc/shared/taskqueue.inline.hpp"
 #include "memory/allocation.inline.hpp"
 #include "oops/access.inline.hpp"
 #include "oops/oop.inline.hpp"
@@ -54,11 +53,10 @@
 // Explicit NOINLINE to block ATTRIBUTE_FLATTENing.
 #define MAYBE_INLINE_EVACUATION NOT_DEBUG(inline) DEBUG_ONLY(NOINLINE)
 
 G1ParScanThreadState::G1ParScanThreadState(G1CollectedHeap* g1h,
                                            G1RedirtyCardsQueueSet* rdcqs,
-                                           PreservedMarks* preserved_marks,
                                            uint worker_id,
                                            uint num_workers,
                                            G1CollectionSet* collection_set,
                                            G1EvacFailureRegions* evac_failure_regions)
   : _g1h(g1h),
@@ -84,11 +82,10 @@
     _string_dedup_requests(),
     _max_num_optional_regions(collection_set->optional_region_length()),
     _numa(g1h->numa()),
     _obj_alloc_stat(nullptr),
     ALLOCATION_FAILURE_INJECTOR_ONLY(_allocation_failure_inject_counter(0) COMMA)
-    _preserved_marks(preserved_marks),
     _evacuation_failed_info(),
     _evac_failure_regions(evac_failure_regions),
     _evac_failure_enqueued_cards(0)
 {
   // We allocate number of young gen regions in the collection set plus one
@@ -209,12 +206,12 @@
     // In this case somebody else already did all the work.
     return;
   }
 
   markWord m = obj->mark();
-  if (m.is_marked()) {
-    obj = cast_to_oop(m.decode_pointer());
+  if (m.is_forwarded()) {
+    obj = obj->forwardee(m);
   } else {
     obj = do_copy_to_survivor_space(region_attr, obj, m);
   }
   RawAccess<IS_NOT_NULL>::oop_store(p, obj);
 
@@ -224,11 +221,11 @@
 MAYBE_INLINE_EVACUATION
 void G1ParScanThreadState::do_partial_array(PartialArrayScanTask task) {
   oop from_obj = task.to_source_array();
 
   assert(_g1h->is_in_reserved(from_obj), "must be in heap.");
-  assert(from_obj->is_objArray(), "must be obj array");
+  assert(from_obj->forward_safe_klass()->is_objArray_klass(), "must be obj array");
   assert(from_obj->is_forwarded(), "must be forwarded");
 
   oop to_obj = from_obj->forwardee();
   assert(from_obj != to_obj, "should not be chunking self-forwarded objects");
   assert(to_obj->is_objArray(), "must be obj array");
@@ -254,11 +251,11 @@
 
 MAYBE_INLINE_EVACUATION
 void G1ParScanThreadState::start_partial_objarray(G1HeapRegionAttr dest_attr,
                                                   oop from_obj,
                                                   oop to_obj) {
-  assert(from_obj->is_objArray(), "precondition");
+  assert(from_obj->forward_safe_klass()->is_objArray_klass(), "precondition");
   assert(from_obj->is_forwarded(), "precondition");
   assert(from_obj->forwardee() == to_obj, "precondition");
   assert(from_obj != to_obj, "should not be scanning self-forwarded objects");
   assert(to_obj->is_objArray(), "precondition");
 
@@ -381,26 +378,26 @@
   // young-to-old (promotion) or old-to-old; destination is old in both cases.
   return G1HeapRegionAttr::Old;
 }
 
 void G1ParScanThreadState::report_promotion_event(G1HeapRegionAttr const dest_attr,
-                                                  oop const old, size_t word_sz, uint age,
+                                                  Klass* klass, size_t word_sz, uint age,
                                                   HeapWord * const obj_ptr, uint node_index) const {
   PLAB* alloc_buf = _plab_allocator->alloc_buffer(dest_attr, node_index);
   if (alloc_buf->contains(obj_ptr)) {
-    _g1h->gc_tracer_stw()->report_promotion_in_new_plab_event(old->klass(), word_sz * HeapWordSize, age,
+    _g1h->gc_tracer_stw()->report_promotion_in_new_plab_event(klass, word_sz * HeapWordSize, age,
                                                               dest_attr.type() == G1HeapRegionAttr::Old,
                                                               alloc_buf->word_sz() * HeapWordSize);
   } else {
-    _g1h->gc_tracer_stw()->report_promotion_outside_plab_event(old->klass(), word_sz * HeapWordSize, age,
+    _g1h->gc_tracer_stw()->report_promotion_outside_plab_event(klass, word_sz * HeapWordSize, age,
                                                                dest_attr.type() == G1HeapRegionAttr::Old);
   }
 }
 
 NOINLINE
 HeapWord* G1ParScanThreadState::allocate_copy_slow(G1HeapRegionAttr* dest_attr,
-                                                   oop old,
+                                                   Klass* klass,
                                                    size_t word_sz,
                                                    uint age,
                                                    uint node_index) {
   HeapWord* obj_ptr = nullptr;
   // Try slow-path allocation unless we're allocating old and old is already full.
@@ -419,11 +416,11 @@
   }
   if (obj_ptr != nullptr) {
     update_numa_stats(node_index);
     if (_g1h->gc_tracer_stw()->should_report_promotion_events()) {
       // The events are checked individually as part of the actual commit
-      report_promotion_event(*dest_attr, old, word_sz, age, obj_ptr, node_index);
+      report_promotion_event(*dest_attr, klass, word_sz, age, obj_ptr, node_index);
     }
   }
   return obj_ptr;
 }
 
@@ -456,11 +453,17 @@
   assert(region_attr.is_in_cset(),
          "Unexpected region attr type: %s", region_attr.get_type_str());
 
   // Get the klass once.  We'll need it again later, and this avoids
   // re-decoding when it's compressed.
-  Klass* klass = old->klass();
+  // NOTE: With compact headers, it is not safe to load the Klass* from o, because
+  // that would access the mark-word, and the mark-word might change at any time by
+  // concurrent promotion. The promoted mark-word would point to the forwardee, which
+  // may not yet have completed copying. Therefore we must load the Klass* from
+  // the mark-word that we have already loaded. This is safe, because we have checked
+  // that this is not yet forwarded in the caller.
+  Klass* klass = old->forward_safe_klass(old_mark);
   const size_t word_sz = old->size_given_klass(klass);
 
   // JNI only allows pinning of typeArrays, so we only need to keep those in place.
   if (region_attr.is_pinned() && klass->is_typeArray_klass()) {
     return handle_evacuation_failure_par(old, old_mark, word_sz, true /* cause_pinned */);
@@ -474,11 +477,11 @@
   HeapWord* obj_ptr = _plab_allocator->plab_allocate(dest_attr, word_sz, node_index);
 
   // PLAB allocations should succeed most of the time, so we'll
   // normally check against null once and that's it.
   if (obj_ptr == nullptr) {
-    obj_ptr = allocate_copy_slow(&dest_attr, old, word_sz, age, node_index);
+    obj_ptr = allocate_copy_slow(&dest_attr, klass, word_sz, age, node_index);
     if (obj_ptr == nullptr) {
       // This will either forward-to-self, or detect that someone else has
       // installed a forwarding pointer.
       return handle_evacuation_failure_par(old, old_mark, word_sz, false /* cause_pinned */);
     }
@@ -575,11 +578,10 @@
 G1ParScanThreadState* G1ParScanThreadStateSet::state_for_worker(uint worker_id) {
   assert(worker_id < _num_workers, "out of bounds access");
   if (_states[worker_id] == nullptr) {
     _states[worker_id] =
       new G1ParScanThreadState(_g1h, rdcqs(),
-                               _preserved_marks_set.get(worker_id),
                                worker_id,
                                _num_workers,
                                _collection_set,
                                _evac_failure_regions);
   }
@@ -634,11 +636,11 @@
 
 NOINLINE
 oop G1ParScanThreadState::handle_evacuation_failure_par(oop old, markWord m, size_t word_sz, bool cause_pinned) {
   assert(_g1h->is_in_cset(old), "Object " PTR_FORMAT " should be in the CSet", p2i(old));
 
-  oop forward_ptr = old->forward_to_atomic(old, m, memory_order_relaxed);
+  oop forward_ptr = old->forward_to_self_atomic(m, memory_order_relaxed);
   if (forward_ptr == nullptr) {
     // Forward-to-self succeeded. We are the "owner" of the object.
     HeapRegion* r = _g1h->heap_region_containing(old);
 
     if (_evac_failure_regions->record(_worker_id, r->hrm_index(), cause_pinned)) {
@@ -647,12 +649,10 @@
 
     // Mark the failing object in the marking bitmap and later use the bitmap to handle
     // evacuation failure recovery.
     _g1h->mark_evac_failure_object(_worker_id, old, word_sz);
 
-    _preserved_marks->push_if_necessary(old, m);
-
     ContinuationGCSupport::transform_stack_chunk(old);
 
     _evacuation_failed_info.register_copy_failure(word_sz);
 
     // For iterating objects that failed evacuation currently we can reuse the
@@ -706,18 +706,16 @@
                                                  G1CollectionSet* collection_set,
                                                  G1EvacFailureRegions* evac_failure_regions) :
     _g1h(g1h),
     _collection_set(collection_set),
     _rdcqs(G1BarrierSet::dirty_card_queue_set().allocator()),
-    _preserved_marks_set(true /* in_c_heap */),
     _states(NEW_C_HEAP_ARRAY(G1ParScanThreadState*, num_workers, mtGC)),
     _rdc_buffers(NEW_C_HEAP_ARRAY(BufferNodeList, num_workers, mtGC)),
     _surviving_young_words_total(NEW_C_HEAP_ARRAY(size_t, collection_set->young_region_length() + 1, mtGC)),
     _num_workers(num_workers),
     _flushed(false),
     _evac_failure_regions(evac_failure_regions) {
-  _preserved_marks_set.init(num_workers);
   for (uint i = 0; i < num_workers; ++i) {
     _states[i] = nullptr;
     _rdc_buffers[i] = BufferNodeList();
   }
   memset(_surviving_young_words_total, 0, (collection_set->young_region_length() + 1) * sizeof(size_t));
@@ -726,7 +724,6 @@
 G1ParScanThreadStateSet::~G1ParScanThreadStateSet() {
   assert(_flushed, "thread local state from the per thread states should have been flushed");
   FREE_C_HEAP_ARRAY(G1ParScanThreadState*, _states);
   FREE_C_HEAP_ARRAY(size_t, _surviving_young_words_total);
   FREE_C_HEAP_ARRAY(BufferNodeList, _rdc_buffers);
-  _preserved_marks_set.reclaim();
 }
diff a/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp
--- a/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp
+++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp
@@ -31,11 +31,10 @@
 #include "gc/g1/g1YoungGCAllocationFailureInjector.hpp"
 #include "gc/g1/g1_globals.hpp"
 #include "gc/shared/ageTable.hpp"
 #include "gc/shared/copyFailedInfo.hpp"
 #include "gc/shared/partialArrayTaskStepper.hpp"
-#include "gc/shared/preservedMarks.hpp"
 #include "gc/shared/stringdedup/stringDedup.hpp"
 #include "gc/shared/taskqueue.hpp"
 #include "memory/allocation.hpp"
 #include "oops/oop.hpp"
 #include "utilities/ticks.hpp"
@@ -45,12 +44,10 @@
 class G1EvacFailureRegions;
 class G1EvacuationRootClosures;
 class G1OopStarChunkedList;
 class G1PLABAllocator;
 class HeapRegion;
-class PreservedMarks;
-class PreservedMarksSet;
 class outputStream;
 
 class G1ParScanThreadState : public CHeapObj<mtGC> {
   G1CollectedHeap* _g1h;
   G1ScannerTasksQueue* _task_queue;
@@ -104,11 +101,10 @@
   size_t* _obj_alloc_stat;
 
   // Per-thread evacuation failure data structures.
   ALLOCATION_FAILURE_INJECTOR_ONLY(size_t _allocation_failure_inject_counter;)
 
-  PreservedMarks* _preserved_marks;
   EvacuationFailedInfo _evacuation_failed_info;
   G1EvacFailureRegions* _evac_failure_regions;
   // Number of additional cards into evacuation failed regions enqueued into
   // the local DCQS. This is an approximation, as cards that would be added later
   // outside of evacuation failure will not be subtracted again.
@@ -123,11 +119,10 @@
   bool inject_allocation_failure(uint region_idx) ALLOCATION_FAILURE_INJECTOR_RETURN_( return false; );
 
 public:
   G1ParScanThreadState(G1CollectedHeap* g1h,
                        G1RedirtyCardsQueueSet* rdcqs,
-                       PreservedMarks* preserved_marks,
                        uint worker_id,
                        uint num_workers,
                        G1CollectionSet* collection_set,
                        G1EvacFailureRegions* evac_failure_regions);
   virtual ~G1ParScanThreadState();
@@ -171,11 +166,11 @@
 private:
   void do_partial_array(PartialArrayScanTask task);
   void start_partial_objarray(G1HeapRegionAttr dest_dir, oop from, oop to);
 
   HeapWord* allocate_copy_slow(G1HeapRegionAttr* dest_attr,
-                               oop old,
+                               Klass* klass,
                                size_t word_sz,
                                uint age,
                                uint node_index);
 
   void undo_allocation(G1HeapRegionAttr dest_addr,
@@ -206,11 +201,11 @@
                                   uint node_index);
 
   inline G1HeapRegionAttr next_region_attr(G1HeapRegionAttr const region_attr, markWord const m, uint& age);
 
   void report_promotion_event(G1HeapRegionAttr const dest_attr,
-                              oop const old, size_t word_sz, uint age,
+                              Klass* klass, size_t word_sz, uint age,
                               HeapWord * const obj_ptr, uint node_index) const;
 
   void trim_queue_to_threshold(uint threshold);
 
   inline bool needs_partial_trimming() const;
@@ -243,11 +238,10 @@
 
 class G1ParScanThreadStateSet : public StackObj {
   G1CollectedHeap* _g1h;
   G1CollectionSet* _collection_set;
   G1RedirtyCardsQueueSet _rdcqs;
-  PreservedMarksSet _preserved_marks_set;
   G1ParScanThreadState** _states;
   BufferNodeList* _rdc_buffers;
   size_t* _surviving_young_words_total;
   uint _num_workers;
   bool _flushed;
@@ -260,11 +254,10 @@
                           G1EvacFailureRegions* evac_failure_regions);
   ~G1ParScanThreadStateSet();
 
   G1RedirtyCardsQueueSet* rdcqs() { return &_rdcqs; }
   BufferNodeList* rdc_buffers() { return _rdc_buffers; }
-  PreservedMarksSet* preserved_marks_set() { return &_preserved_marks_set; }
 
   void flush_stats();
   void record_unused_optional_region(HeapRegion* hr);
 
   G1ParScanThreadState* state_for_worker(uint worker_id);
diff a/src/hotspot/share/gc/g1/g1YoungCollector.cpp b/src/hotspot/share/gc/g1/g1YoungCollector.cpp
--- a/src/hotspot/share/gc/g1/g1YoungCollector.cpp
+++ b/src/hotspot/share/gc/g1/g1YoungCollector.cpp
@@ -51,11 +51,10 @@
 #include "gc/g1/g1YoungGCPreEvacuateTasks.hpp"
 #include "gc/g1/g1_globals.hpp"
 #include "gc/shared/concurrentGCBreakpoints.hpp"
 #include "gc/shared/gcTraceTime.inline.hpp"
 #include "gc/shared/gcTimer.hpp"
-#include "gc/shared/preservedMarks.hpp"
 #include "gc/shared/referenceProcessor.hpp"
 #include "gc/shared/weakProcessor.inline.hpp"
 #include "gc/shared/workerPolicy.hpp"
 #include "gc/shared/workerThread.hpp"
 #include "jfr/jfrEvents.hpp"
diff a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp
--- a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp
+++ b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp
@@ -39,11 +39,10 @@
 #include "gc/g1/g1OopClosures.inline.hpp"
 #include "gc/g1/g1ParScanThreadState.hpp"
 #include "gc/g1/g1RemSet.hpp"
 #include "gc/g1/g1YoungGCPostEvacuateTasks.hpp"
 #include "gc/shared/bufferNode.hpp"
-#include "gc/shared/preservedMarks.inline.hpp"
 #include "jfr/jfrEvents.hpp"
 #include "oops/access.inline.hpp"
 #include "oops/compressedOops.inline.hpp"
 #include "oops/oop.inline.hpp"
 #include "runtime/prefetch.hpp"
@@ -249,11 +248,11 @@
       HeapWord* const obj_end_addr = obj_addr + obj_size;
 
       {
         // Process marked object.
         assert(obj->is_forwarded() && obj->forwardee() == obj, "must be self-forwarded");
-        obj->init_mark();
+        obj->unset_self_forwarded();
         hr->update_bot_for_block(obj_addr, obj_end_addr);
 
         // Statistics
         num_marked_objs++;
         marked_words += obj_size;
@@ -474,31 +473,10 @@
     _humongous_regions_reclaimed = cl.humongous_regions_reclaimed();
     _bytes_freed = cl.bytes_freed();
   }
 };
 
-class G1PostEvacuateCollectionSetCleanupTask2::RestorePreservedMarksTask : public G1AbstractSubTask {
-  PreservedMarksSet* _preserved_marks;
-  WorkerTask* _task;
-
-public:
-  RestorePreservedMarksTask(PreservedMarksSet* preserved_marks) :
-    G1AbstractSubTask(G1GCPhaseTimes::RestorePreservedMarks),
-    _preserved_marks(preserved_marks),
-    _task(preserved_marks->create_task()) { }
-
-  virtual ~RestorePreservedMarksTask() {
-    delete _task;
-  }
-
-  double worker_cost() const override {
-    return _preserved_marks->num();
-  }
-
-  void do_work(uint worker_id) override { _task->work(worker_id); }
-};
-
 class RedirtyLoggedCardTableEntryClosure : public G1CardTableEntryClosure {
   size_t _num_dirtied;
   G1CollectedHeap* _g1h;
   G1CardTable* _g1_ct;
   G1EvacFailureRegions* _evac_failure_regions;
@@ -969,11 +947,10 @@
   if (G1CollectedHeap::heap()->has_humongous_reclaim_candidates()) {
     add_serial_task(new EagerlyReclaimHumongousObjectsTask());
   }
 
   if (evac_failure_regions->has_regions_evac_failed()) {
-    add_parallel_task(new RestorePreservedMarksTask(per_thread_states->preserved_marks_set()));
     add_parallel_task(new ProcessEvacuationFailedRegionsTask(evac_failure_regions));
   }
   add_parallel_task(new RedirtyLoggedCardsTask(evac_failure_regions,
                                                per_thread_states->rdc_buffers(),
                                                per_thread_states->num_workers()));
diff a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.hpp b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.hpp
--- a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.hpp
+++ b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.hpp
@@ -54,22 +54,20 @@
 // Second set of post evacuate collection set tasks containing (s means serial):
 // - Eagerly Reclaim Humongous Objects (s)
 // - Update Derived Pointers (s)
 // - Clear Retained Region Data (on evacuation failure)
 // - Redirty Logged Cards
-// - Restore Preserved Marks (on evacuation failure)
 // - Free Collection Set
 // - Resize TLABs
 class G1PostEvacuateCollectionSetCleanupTask2 : public G1BatchedTask {
   class EagerlyReclaimHumongousObjectsTask;
 #if COMPILER2_OR_JVMCI
   class UpdateDerivedPointersTask;
 #endif
 
   class ProcessEvacuationFailedRegionsTask;
   class RedirtyLoggedCardsTask;
-  class RestorePreservedMarksTask;
   class FreeCollectionSetTask;
   class ResizeTLABsTask;
 
 public:
   G1PostEvacuateCollectionSetCleanupTask2(G1ParScanThreadStateSet* per_thread_states,
diff a/src/hotspot/share/gc/parallel/mutableSpace.cpp b/src/hotspot/share/gc/parallel/mutableSpace.cpp
--- a/src/hotspot/share/gc/parallel/mutableSpace.cpp
+++ b/src/hotspot/share/gc/parallel/mutableSpace.cpp
@@ -231,26 +231,40 @@
   while (obj_addr < t) {
     obj_addr += cast_to_oop(obj_addr)->oop_iterate_size(cl);
   }
 }
 
-void MutableSpace::object_iterate(ObjectClosure* cl) {
+template<bool COMPACT_HEADERS>
+void MutableSpace::object_iterate_impl(ObjectClosure* cl) {
   HeapWord* p = bottom();
   while (p < top()) {
     oop obj = cast_to_oop(p);
     // When promotion-failure occurs during Young GC, eden/from space is not cleared,
     // so we can encounter objects with "forwarded" markword.
     // They are essentially dead, so skipping them
     if (!obj->is_forwarded()) {
       cl->do_object(obj);
-    }
-#ifdef ASSERT
-    else {
+      p += obj->size();
+    } else {
       assert(obj->forwardee() != obj, "must not be self-forwarded");
+      if (COMPACT_HEADERS) {
+        // It is safe to use the forwardee here. Parallel GC only uses
+        // header-based forwarding during promotion. Full GC doesn't
+        // use the object header for forwarding at all.
+        p += obj->forwardee()->size();
+      } else {
+        p += obj->size();
+      }
     }
-#endif
-    p += obj->size();
+  }
+}
+
+void MutableSpace::object_iterate(ObjectClosure* cl) {
+  if (UseCompactObjectHeaders) {
+    object_iterate_impl<true>(cl);
+  } else {
+    object_iterate_impl<false>(cl);
   }
 }
 
 void MutableSpace::print_short() const { print_short_on(tty); }
 void MutableSpace::print_short_on( outputStream* st) const {
diff a/src/hotspot/share/gc/parallel/mutableSpace.hpp b/src/hotspot/share/gc/parallel/mutableSpace.hpp
--- a/src/hotspot/share/gc/parallel/mutableSpace.hpp
+++ b/src/hotspot/share/gc/parallel/mutableSpace.hpp
@@ -65,10 +65,13 @@
   void numa_setup_pages(MemRegion mr, size_t page_size, bool clear_space);
 
   void set_last_setup_region(MemRegion mr) { _last_setup_region = mr;   }
   MemRegion last_setup_region() const      { return _last_setup_region; }
 
+  template<bool COMPACT_HEADERS>
+  void object_iterate_impl(ObjectClosure* cl);
+
  public:
   virtual ~MutableSpace();
   MutableSpace(size_t page_size);
 
   // Accessors
diff a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp
--- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp
+++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp
@@ -1110,10 +1110,14 @@
   //
   // Need to fill in the gap only if it's smaller than min-obj-size, and the
   // filler obj will extend to next region.
 
   // Note: If min-fill-size decreases to 1, this whole method becomes redundant.
+  if (UseCompactObjectHeaders) {
+    // The gap is always equal to min-fill-size, so nothing to do.
+    return;
+  }
   assert(CollectedHeap::min_fill_size() >= 2, "inv");
 #ifndef _LP64
   // In 32-bit system, each heap word is 4 bytes, so MinObjAlignment == 2.
   // The gap is always equal to min-fill-size, so nothing to do.
   return;
diff a/src/hotspot/share/gc/parallel/psPromotionManager.cpp b/src/hotspot/share/gc/parallel/psPromotionManager.cpp
--- a/src/hotspot/share/gc/parallel/psPromotionManager.cpp
+++ b/src/hotspot/share/gc/parallel/psPromotionManager.cpp
@@ -279,11 +279,11 @@
 
 void PSPromotionManager::process_array_chunk(PartialArrayScanTask task) {
   assert(PSChunkLargeArrays, "invariant");
 
   oop old = task.to_source_array();
-  assert(old->is_objArray(), "invariant");
+  assert(old->forward_safe_klass()->is_objArray_klass(), "invariant");
   assert(old->is_forwarded(), "invariant");
 
   TASKQUEUE_STATS_ONLY(++_array_chunks_processed);
 
   oop const obj = old->forwardee();
@@ -317,11 +317,11 @@
   // Attempt to CAS in the header.
   // This tests if the header is still the same as when
   // this started.  If it is the same (i.e., no forwarding
   // pointer has been installed), then this thread owns
   // it.
-  if (obj->forward_to_atomic(obj, obj_mark) == nullptr) {
+  if (obj->forward_to_self_atomic(obj_mark) == nullptr) {
     // We won any races, we "own" this object.
     assert(obj == obj->forwardee(), "Sanity");
 
     _promotion_failed_info.register_copy_failure(obj->size());
 
diff a/src/hotspot/share/gc/parallel/psPromotionManager.hpp b/src/hotspot/share/gc/parallel/psPromotionManager.hpp
--- a/src/hotspot/share/gc/parallel/psPromotionManager.hpp
+++ b/src/hotspot/share/gc/parallel/psPromotionManager.hpp
@@ -103,11 +103,11 @@
                                                     int start, int end);
   void process_array_chunk(PartialArrayScanTask task);
 
   void push_depth(ScannerTask task);
 
-  inline void promotion_trace_event(oop new_obj, oop old_obj, size_t obj_size,
+  inline void promotion_trace_event(oop new_obj, Klass* klass, size_t obj_size,
                                     uint age, bool tenured,
                                     const PSPromotionLAB* lab);
 
   static PSScannerTasksQueueSet* stack_array_depth() { return _stack_array_depth; }
 
diff a/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp b/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp
--- a/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp
+++ b/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp
@@ -64,11 +64,11 @@
     Prefetch::write(obj->mark_addr(), 0);
     push_depth(ScannerTask(p));
   }
 }
 
-inline void PSPromotionManager::promotion_trace_event(oop new_obj, oop old_obj,
+inline void PSPromotionManager::promotion_trace_event(oop new_obj, Klass* klass,
                                                       size_t obj_size,
                                                       uint age, bool tenured,
                                                       const PSPromotionLAB* lab) {
   // Skip if memory allocation failed
   if (new_obj != nullptr) {
@@ -77,18 +77,18 @@
     if (lab != nullptr) {
       // Promotion of object through newly allocated PLAB
       if (gc_tracer->should_report_promotion_in_new_plab_event()) {
         size_t obj_bytes = obj_size * HeapWordSize;
         size_t lab_size = lab->capacity();
-        gc_tracer->report_promotion_in_new_plab_event(old_obj->klass(), obj_bytes,
+        gc_tracer->report_promotion_in_new_plab_event(klass, obj_bytes,
                                                       age, tenured, lab_size);
       }
     } else {
       // Promotion of object directly to heap
       if (gc_tracer->should_report_promotion_outside_plab_event()) {
         size_t obj_bytes = obj_size * HeapWordSize;
-        gc_tracer->report_promotion_outside_plab_event(old_obj->klass(), obj_bytes,
+        gc_tracer->report_promotion_outside_plab_event(klass, obj_bytes,
                                                        age, tenured);
       }
     }
   }
 }
@@ -143,19 +143,19 @@
 
   // NOTE! We must be very careful with any methods that access the mark
   // in o. There may be multiple threads racing on it, and it may be forwarded
   // at any time.
   markWord m = o->mark();
-  if (!m.is_marked()) {
+  if (!m.is_forwarded()) {
     return copy_unmarked_to_survivor_space<promote_immediately>(o, m);
   } else {
     // Ensure any loads from the forwardee follow all changes that precede
     // the release-cmpxchg that performed the forwarding, possibly in some
     // other thread.
     OrderAccess::acquire();
     // Return the already installed forwardee.
-    return cast_to_oop(m.decode_pointer());
+    return o->forwardee(m);
   }
 }
 
 //
 // This method is pretty bulky. It would be nice to split it up
@@ -167,11 +167,18 @@
                                                                markWord test_mark) {
   assert(should_scavenge(&o), "Sanity");
 
   oop new_obj = nullptr;
   bool new_obj_is_tenured = false;
-  size_t new_obj_size = o->size();
+  // NOTE: With compact headers, it is not safe to load the Klass* from o, because
+  // that would access the mark-word, and the mark-word might change at any time by
+  // concurrent promotion. The promoted mark-word would point to the forwardee, which
+  // may not yet have completed copying. Therefore we must load the Klass* from
+  // the mark-word that we have already loaded. This is safe, because we have checked
+  // that this is not yet forwarded in the caller.
+  Klass* klass = o->forward_safe_klass(test_mark);
+  size_t new_obj_size = o->size_given_klass(klass);
 
   // Find the objects age, MT safe.
   uint age = (test_mark.has_displaced_mark_helper() /* o->has_displaced_mark() */) ?
       test_mark.displaced_mark_helper().age() : test_mark.age();
 
@@ -182,21 +189,21 @@
       if (new_obj == nullptr && !_young_gen_is_full) {
         // Do we allocate directly, or flush and refill?
         if (new_obj_size > (YoungPLABSize / 2)) {
           // Allocate this object directly
           new_obj = cast_to_oop(young_space()->cas_allocate(new_obj_size));
-          promotion_trace_event(new_obj, o, new_obj_size, age, false, nullptr);
+          promotion_trace_event(new_obj, klass, new_obj_size, age, false, nullptr);
         } else {
           // Flush and fill
           _young_lab.flush();
 
           HeapWord* lab_base = young_space()->cas_allocate(YoungPLABSize);
           if (lab_base != nullptr) {
             _young_lab.initialize(MemRegion(lab_base, YoungPLABSize));
             // Try the young lab allocation again.
             new_obj = cast_to_oop(_young_lab.allocate(new_obj_size));
-            promotion_trace_event(new_obj, o, new_obj_size, age, false, &_young_lab);
+            promotion_trace_event(new_obj, klass, new_obj_size, age, false, &_young_lab);
           } else {
             _young_gen_is_full = true;
           }
         }
       }
@@ -218,21 +225,21 @@
       if (!_old_gen_is_full) {
         // Do we allocate directly, or flush and refill?
         if (new_obj_size > (OldPLABSize / 2)) {
           // Allocate this object directly
           new_obj = cast_to_oop(old_gen()->allocate(new_obj_size));
-          promotion_trace_event(new_obj, o, new_obj_size, age, true, nullptr);
+          promotion_trace_event(new_obj, klass, new_obj_size, age, true, nullptr);
         } else {
           // Flush and fill
           _old_lab.flush();
 
           HeapWord* lab_base = old_gen()->allocate(OldPLABSize);
           if(lab_base != nullptr) {
             _old_lab.initialize(MemRegion(lab_base, OldPLABSize));
             // Try the old lab allocation again.
             new_obj = cast_to_oop(_old_lab.allocate(new_obj_size));
-            promotion_trace_event(new_obj, o, new_obj_size, age, true, &_old_lab);
+            promotion_trace_event(new_obj, klass, new_obj_size, age, true, &_old_lab);
           }
         }
       }
 
       // This is the promotion failed test, and code handling.
@@ -251,13 +258,25 @@
   assert(new_obj != nullptr, "allocation should have succeeded");
 
   // Copy obj
   Copy::aligned_disjoint_words(cast_from_oop<HeapWord*>(o), cast_from_oop<HeapWord*>(new_obj), new_obj_size);
 
-  // Parallel GC claims with a release - so other threads might access this object
-  // after claiming and they should see the "completed" object.
-  ContinuationGCSupport::transform_stack_chunk(new_obj);
+  if (UseCompactObjectHeaders) {
+    // The copy above is not atomic. Make sure we have seen the proper mark
+    // and re-install it into the copy, so that Klass* is guaranteed to be correct.
+    markWord mark = o->mark();
+    if (!mark.is_forwarded()) {
+      new_obj->set_mark(mark);
+      ContinuationGCSupport::transform_stack_chunk(new_obj);
+    } else {
+      // If we copied a mark-word that indicates 'forwarded' state, the object
+      // installation would not succeed. We cannot access Klass* anymore either.
+      // Skip the transformation.
+    }
+  } else {
+    ContinuationGCSupport::transform_stack_chunk(new_obj);
+  }
 
   // Now we have to CAS in the header.
   // Make copy visible to threads reading the forwardee.
   oop forwardee = o->forward_to_atomic(new_obj, test_mark, memory_order_release);
   if (forwardee == nullptr) {  // forwardee is null when forwarding is successful
diff a/src/hotspot/share/gc/serial/defNewGeneration.cpp b/src/hotspot/share/gc/serial/defNewGeneration.cpp
--- a/src/hotspot/share/gc/serial/defNewGeneration.cpp
+++ b/src/hotspot/share/gc/serial/defNewGeneration.cpp
@@ -38,11 +38,10 @@
 #include "gc/shared/gcLocker.hpp"
 #include "gc/shared/gcPolicyCounters.hpp"
 #include "gc/shared/gcTimer.hpp"
 #include "gc/shared/gcTrace.hpp"
 #include "gc/shared/gcTraceTime.inline.hpp"
-#include "gc/shared/preservedMarks.inline.hpp"
 #include "gc/shared/referencePolicy.hpp"
 #include "gc/shared/referenceProcessorPhaseTimes.hpp"
 #include "gc/shared/space.inline.hpp"
 #include "gc/shared/spaceDecorator.inline.hpp"
 #include "gc/shared/strongRootsScope.hpp"
@@ -225,11 +224,10 @@
                                    size_t initial_size,
                                    size_t min_size,
                                    size_t max_size,
                                    const char* policy)
   : Generation(rs, initial_size),
-    _preserved_marks_set(false /* in_c_heap */),
     _promo_failure_drain_in_progress(false),
     _should_allocate_from_space(false),
     _string_dedup_requests()
 {
   MemRegion cmr((HeapWord*)_virtual_space.low(),
@@ -673,12 +671,10 @@
   // These can be shared for all code paths
   IsAliveClosure is_alive(this);
 
   age_table()->clear();
   to()->clear(SpaceDecorator::Mangle);
-  // The preserved marks should be empty at the start of the GC.
-  _preserved_marks_set.init(1);
 
   assert(heap->no_allocs_since_save_marks(),
          "save marks have not been newly set.");
 
   YoungGenScanClosure young_gen_cl(this);
@@ -773,12 +769,10 @@
     _gc_tracer->report_promotion_failed(_promotion_failed_info);
 
     // Reset the PromotionFailureALot counters.
     NOT_PRODUCT(heap->reset_promotion_should_fail();)
   }
-  // We should have processed and cleared all the preserved marks.
-  _preserved_marks_set.reclaim();
 
   heap->trace_heap_after_gc(_gc_tracer);
 
   _gc_timer->register_gc_end();
 
@@ -796,36 +790,31 @@
   // Will enter Full GC soon due to failed promotion. Must reset the mark word
   // of objs in young-gen so that no objs are marked (forwarded) when Full GC
   // starts. (The mark word is overloaded: `is_marked()` == `is_forwarded()`.)
   struct ResetForwardedMarkWord : ObjectClosure {
     void do_object(oop obj) override {
-      if (obj->is_forwarded()) {
-        obj->init_mark();
+      if (obj->is_self_forwarded()) {
+        obj->unset_self_forwarded();
+      } else if (obj->is_forwarded()) {
+        obj->forward_safe_init_mark();
       }
     }
   } cl;
   eden()->object_iterate(&cl);
   from()->object_iterate(&cl);
-
-  restore_preserved_marks();
-}
-
-void DefNewGeneration::restore_preserved_marks() {
-  _preserved_marks_set.restore(nullptr);
 }
 
 void DefNewGeneration::handle_promotion_failure(oop old) {
   log_debug(gc, promotion)("Promotion failure size = " SIZE_FORMAT ") ", old->size());
 
   _promotion_failed = true;
   _promotion_failed_info.register_copy_failure(old->size());
-  _preserved_marks_set.get()->push_if_necessary(old, old->mark());
 
   ContinuationGCSupport::transform_stack_chunk(old);
 
   // forward to self
-  old->forward_to(old);
+  old->forward_to_self();
 
   _promo_failure_scan_stack.push(old);
 
   if (!_promo_failure_drain_in_progress) {
     // prevent recursion in copy_to_survivor_space()
diff a/src/hotspot/share/gc/serial/defNewGeneration.hpp b/src/hotspot/share/gc/serial/defNewGeneration.hpp
--- a/src/hotspot/share/gc/serial/defNewGeneration.hpp
+++ b/src/hotspot/share/gc/serial/defNewGeneration.hpp
@@ -30,11 +30,10 @@
 #include "gc/serial/tenuredGeneration.hpp"
 #include "gc/shared/ageTable.hpp"
 #include "gc/shared/copyFailedInfo.hpp"
 #include "gc/shared/gc_globals.hpp"
 #include "gc/shared/generationCounters.hpp"
-#include "gc/shared/preservedMarks.hpp"
 #include "gc/shared/stringdedup/stringDedup.hpp"
 #include "gc/shared/tlab_globals.hpp"
 #include "utilities/align.hpp"
 #include "utilities/stack.hpp"
 
@@ -98,15 +97,10 @@
   // objects after a young-gen collection.  When promotion fails, however,
   // the subsequent full collection will look at from-space objects:
   // therefore we must remove their forwarding pointers.
   void remove_forwarding_pointers();
 
-  virtual void restore_preserved_marks();
-
-  // Preserved marks
-  PreservedMarksSet _preserved_marks_set;
-
   Stack<oop, mtGC> _promo_failure_scan_stack;
   void drain_promo_failure_scan_stack(void);
   bool _promo_failure_drain_in_progress;
 
   // Performance Counters
diff a/src/hotspot/share/gc/serial/serialFullGC.cpp b/src/hotspot/share/gc/serial/serialFullGC.cpp
--- a/src/hotspot/share/gc/serial/serialFullGC.cpp
+++ b/src/hotspot/share/gc/serial/serialFullGC.cpp
@@ -49,10 +49,11 @@
 #include "gc/shared/gc_globals.hpp"
 #include "gc/shared/modRefBarrierSet.hpp"
 #include "gc/shared/preservedMarks.inline.hpp"
 #include "gc/shared/referencePolicy.hpp"
 #include "gc/shared/referenceProcessorPhaseTimes.hpp"
+#include "gc/shared/slidingForwarding.inline.hpp"
 #include "gc/shared/space.inline.hpp"
 #include "gc/shared/strongRootsScope.hpp"
 #include "gc/shared/weakProcessor.hpp"
 #include "memory/iterator.inline.hpp"
 #include "memory/universe.hpp"
@@ -226,11 +227,11 @@
   }
 
   static void forward_obj(oop obj, HeapWord* new_addr) {
     prefetch_write_scan(obj);
     if (cast_from_oop<HeapWord*>(obj) != new_addr) {
-      obj->forward_to(cast_to_oop(new_addr));
+      SlidingForwarding::forward_to(obj, cast_to_oop(new_addr));
     } else {
       assert(obj->is_gc_marked(), "inv");
       // This obj will stay in-place. Fix the markword.
       obj->init_mark();
     }
@@ -251,11 +252,11 @@
   static size_t relocate(HeapWord* addr) {
     // Prefetch source and destination
     prefetch_read_scan(addr);
 
     oop obj = cast_to_oop(addr);
-    oop new_obj = obj->forwardee();
+    oop new_obj = SlidingForwarding::forwardee(obj);
     HeapWord* new_addr = cast_from_oop<HeapWord*>(new_obj);
     assert(addr != new_addr, "inv");
     prefetch_write_copy(new_addr);
 
     size_t obj_size = obj->size();
@@ -347,17 +348,17 @@
       ContiguousSpace* space = get_space(i);
       HeapWord* cur_addr = space->bottom();
       HeapWord* top = space->top();
 
       // Check if the first obj inside this space is forwarded.
-      if (!cast_to_oop(cur_addr)->is_forwarded()) {
+      if (SlidingForwarding::is_not_forwarded(cast_to_oop(cur_addr))) {
         // Jump over consecutive (in-place) live-objs-chunk
         cur_addr = get_first_dead(i);
       }
 
       while (cur_addr < top) {
-        if (!cast_to_oop(cur_addr)->is_forwarded()) {
+        if (SlidingForwarding::is_not_forwarded(cast_to_oop(cur_addr))) {
           cur_addr = *(HeapWord**) cur_addr;
           continue;
         }
         cur_addr += relocate(cur_addr);
       }
@@ -584,16 +585,18 @@
       java_lang_String::is_instance(obj) &&
       SerialStringDedup::is_candidate_from_mark(obj)) {
     _string_dedup_requests->add(obj);
   }
 
+  // Do the transform while we still have the header intact,
+  // which might include important class information.
+  ContinuationGCSupport::transform_stack_chunk(obj);
+
   // some marks may contain information we need to preserve so we store them away
   // and overwrite the mark.  We'll restore it at the end of serial full GC.
   markWord mark = obj->mark();
-  obj->set_mark(markWord::prototype().set_marked());
-
-  ContinuationGCSupport::transform_stack_chunk(obj);
+  obj->set_mark(obj->prototype_mark().set_marked());
 
   if (obj->mark_must_be_preserved(mark)) {
     preserve_mark(obj, mark);
   }
 }
@@ -618,12 +621,12 @@
   T heap_oop = RawAccess<>::oop_load(p);
   if (!CompressedOops::is_null(heap_oop)) {
     oop obj = CompressedOops::decode_not_null(heap_oop);
     assert(Universe::heap()->is_in(obj), "should be in heap");
 
-    if (obj->is_forwarded()) {
-      oop new_obj = obj->forwardee();
+    if (SlidingForwarding::is_forwarded(obj)) {
+      oop new_obj = SlidingForwarding::forwardee(obj);
       assert(is_object_aligned(new_obj), "oop must be aligned");
       RawAccess<IS_NOT_NULL>::oop_store(p, new_obj);
     }
   }
 }
@@ -698,10 +701,12 @@
 
   allocate_stacks();
 
   phase1_mark(clear_all_softrefs);
 
+  SlidingForwarding::begin();
+
   Compacter compacter{gch};
 
   {
     // Now all live objects are marked, compute the new object addresses.
     GCTraceTime(Info, gc, phases) tm("Phase 2: Compute new object addresses", _gc_timer);
@@ -745,10 +750,12 @@
 
   // Set saved marks for allocation profiler (and other things? -- dld)
   // (Should this be in general part?)
   gch->save_marks();
 
+  SlidingForwarding::end();
+
   deallocate_stacks();
 
   SerialFullGC::_string_dedup_requests->flush();
 
   bool is_young_gen_empty = (gch->young_gen()->used() == 0);
diff a/src/hotspot/share/gc/serial/serialHeap.cpp b/src/hotspot/share/gc/serial/serialHeap.cpp
--- a/src/hotspot/share/gc/serial/serialHeap.cpp
+++ b/src/hotspot/share/gc/serial/serialHeap.cpp
@@ -53,10 +53,11 @@
 #include "gc/shared/locationPrinter.inline.hpp"
 #include "gc/shared/oopStorage.inline.hpp"
 #include "gc/shared/oopStorageParState.inline.hpp"
 #include "gc/shared/oopStorageSet.inline.hpp"
 #include "gc/shared/scavengableNMethods.hpp"
+#include "gc/shared/slidingForwarding.hpp"
 #include "gc/shared/space.hpp"
 #include "gc/shared/strongRootsScope.hpp"
 #include "gc/shared/suspendibleThreadSet.hpp"
 #include "gc/shared/weakProcessor.hpp"
 #include "gc/shared/workerThread.hpp"
@@ -200,10 +201,12 @@
   _young_gen = new DefNewGeneration(young_rs, NewSize, MinNewSize, MaxNewSize);
   _old_gen = new TenuredGeneration(old_rs, OldSize, MinOldSize, MaxOldSize, rem_set());
 
   GCInitLogger::print();
 
+  SlidingForwarding::initialize(_reserved, SpaceAlignment / HeapWordSize);
+
   return JNI_OK;
 }
 
 ReservedHeapSpace SerialHeap::allocate(size_t alignment) {
   // Now figure out the total size.
diff a/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp b/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp
--- a/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp
+++ b/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp
@@ -656,10 +656,11 @@
   // 8  - 32-bit VM
   // 12 - 64-bit VM, compressed klass
   // 16 - 64-bit VM, normal klass
   if (base_off % BytesPerLong != 0) {
     assert(UseCompressedClassPointers, "");
+    assert(!UseCompactObjectHeaders, "");
     if (is_array) {
       // Exclude length to copy by 8 bytes words.
       base_off += sizeof(int);
     } else {
       // Include klass to copy by 8 bytes words.
diff a/src/hotspot/share/gc/shared/collectedHeap.cpp b/src/hotspot/share/gc/shared/collectedHeap.cpp
--- a/src/hotspot/share/gc/shared/collectedHeap.cpp
+++ b/src/hotspot/share/gc/shared/collectedHeap.cpp
@@ -227,11 +227,13 @@
 
   if (!is_in(object)) {
     return false;
   }
 
-  if (!Metaspace::contains(object->klass_without_asserts())) {
+  // With compact headers, we can't safely access the class, due
+  // to possibly forwarded objects.
+  if (!UseCompactObjectHeaders && !Metaspace::contains(object->klass_without_asserts())) {
     return false;
   }
 
   return true;
 }
diff a/src/hotspot/share/gc/shared/collectedHeap.hpp b/src/hotspot/share/gc/shared/collectedHeap.hpp
--- a/src/hotspot/share/gc/shared/collectedHeap.hpp
+++ b/src/hotspot/share/gc/shared/collectedHeap.hpp
@@ -306,11 +306,11 @@
   static void fill_with_object(HeapWord* start, HeapWord* end, bool zap = true) {
     fill_with_object(start, pointer_delta(end, start), zap);
   }
 
   virtual void fill_with_dummy_object(HeapWord* start, HeapWord* end, bool zap);
-  static constexpr size_t min_dummy_object_size() {
+  static size_t min_dummy_object_size() {
     return oopDesc::header_size();
   }
 
   static size_t lab_alignment_reserve() {
     assert(_lab_alignment_reserve != SIZE_MAX, "uninitialized");
diff a/src/hotspot/share/gc/shared/memAllocator.cpp b/src/hotspot/share/gc/shared/memAllocator.cpp
--- a/src/hotspot/share/gc/shared/memAllocator.cpp
+++ b/src/hotspot/share/gc/shared/memAllocator.cpp
@@ -377,22 +377,27 @@
 
 void MemAllocator::mem_clear(HeapWord* mem) const {
   assert(mem != nullptr, "cannot initialize null object");
   const size_t hs = oopDesc::header_size();
   assert(_word_size >= hs, "unexpected object size");
-  oopDesc::set_klass_gap(mem, 0);
+  if (!UseCompactObjectHeaders) {
+    oopDesc::set_klass_gap(mem, 0);
+  }
   Copy::fill_to_aligned_words(mem + hs, _word_size - hs);
 }
 
 oop MemAllocator::finish(HeapWord* mem) const {
   assert(mem != nullptr, "null object pointer");
-  // May be bootstrapping
-  oopDesc::set_mark(mem, markWord::prototype());
   // Need a release store to ensure array/class length, mark word, and
   // object zeroing are visible before setting the klass non-null, for
   // concurrent collectors.
-  oopDesc::release_set_klass(mem, _klass);
+  if (UseCompactObjectHeaders) {
+    oopDesc::release_set_mark(mem, _klass->prototype_header());
+  } else {
+    oopDesc::set_mark(mem, markWord::prototype());
+    oopDesc::release_set_klass(mem, _klass);
+  }
   return cast_to_oop(mem);
 }
 
 oop ObjAllocator::initialize(HeapWord* mem) const {
   mem_clear(mem);
diff a/src/hotspot/share/gc/shared/preservedMarks.cpp b/src/hotspot/share/gc/shared/preservedMarks.cpp
--- a/src/hotspot/share/gc/shared/preservedMarks.cpp
+++ b/src/hotspot/share/gc/shared/preservedMarks.cpp
@@ -22,10 +22,11 @@
  *
  */
 
 #include "precompiled.hpp"
 #include "gc/shared/preservedMarks.inline.hpp"
+#include "gc/shared/slidingForwarding.inline.hpp"
 #include "gc/shared/workerThread.hpp"
 #include "gc/shared/workerUtils.hpp"
 #include "memory/allocation.inline.hpp"
 #include "memory/resourceArea.hpp"
 #include "oops/oop.inline.hpp"
@@ -40,12 +41,12 @@
   assert_empty();
 }
 
 void PreservedMarks::adjust_preserved_mark(PreservedMark* elem) {
   oop obj = elem->get_oop();
-  if (obj->is_forwarded()) {
-    elem->set_oop(obj->forwardee());
+  if (SlidingForwarding::is_forwarded(obj)) {
+    elem->set_oop(SlidingForwarding::forwardee(obj));
   }
 }
 
 void PreservedMarks::adjust_during_full_gc() {
   StackIterator<PreservedMark, mtGC> iter(_stack);
diff a/src/hotspot/share/gc/shared/slidingForwarding.cpp b/src/hotspot/share/gc/shared/slidingForwarding.cpp
--- /dev/null
+++ b/src/hotspot/share/gc/shared/slidingForwarding.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2021, Red Hat, Inc. All rights reserved.
+ * Copyright Amazon.com Inc. 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+#include "gc/shared/gc_globals.hpp"
+#include "gc/shared/slidingForwarding.hpp"
+#include "utilities/ostream.hpp"
+#include "utilities/powerOfTwo.hpp"
+
+// We cannot use 0, because that may already be a valid base address in zero-based heaps.
+// 0x1 is safe because heap base addresses must be aligned by much larger alignment
+HeapWord* const SlidingForwarding::UNUSED_BASE = reinterpret_cast<HeapWord*>(0x1);
+
+HeapWord* SlidingForwarding::_heap_start = nullptr;
+size_t SlidingForwarding::_region_size_words = 0;
+size_t SlidingForwarding::_heap_start_region_bias = 0;
+size_t SlidingForwarding::_num_regions = 0;
+uint SlidingForwarding::_region_size_bytes_shift = 0;
+uintptr_t SlidingForwarding::_region_mask = 0;
+HeapWord** SlidingForwarding::_biased_bases[SlidingForwarding::NUM_TARGET_REGIONS] = { nullptr, nullptr };
+HeapWord** SlidingForwarding::_bases_table = nullptr;
+SlidingForwarding::FallbackTable* SlidingForwarding::_fallback_table = nullptr;
+
+void SlidingForwarding::initialize(MemRegion heap, size_t region_size_words) {
+#ifdef _LP64
+  _heap_start = heap.start();
+
+  // If the heap is small enough to fit directly into the available offset bits,
+  // and we are running Serial GC, we can treat the whole heap as a single region
+  // if it happens to be aligned to allow biasing.
+  size_t rounded_heap_size = round_up_power_of_2(heap.byte_size());
+
+  if (UseSerialGC && (heap.word_size() <= (1 << NUM_OFFSET_BITS)) &&
+      is_aligned((uintptr_t)_heap_start, rounded_heap_size)) {
+    _num_regions = 1;
+    _region_size_words = heap.word_size();
+    _region_size_bytes_shift = log2i_exact(rounded_heap_size);
+  } else {
+    _num_regions = align_up(pointer_delta(heap.end(), heap.start()), region_size_words) / region_size_words;
+    _region_size_words = region_size_words;
+    _region_size_bytes_shift = log2i_exact(_region_size_words) + LogHeapWordSize;
+  }
+  _heap_start_region_bias = (uintptr_t)_heap_start >> _region_size_bytes_shift;
+  _region_mask = ~((uintptr_t(1) << _region_size_bytes_shift) - 1);
+
+  guarantee((_heap_start_region_bias << _region_size_bytes_shift) == (uintptr_t)_heap_start, "must be aligned: _heap_start_region_bias: " SIZE_FORMAT ", _region_size_byte_shift: %u, _heap_start: " PTR_FORMAT, _heap_start_region_bias, _region_size_bytes_shift, p2i(_heap_start));
+
+  assert(_region_size_words >= 1, "regions must be at least a word large");
+  assert(_bases_table == nullptr, "should not be initialized yet");
+  assert(_fallback_table == nullptr, "should not be initialized yet");
+#endif
+}
+
+void SlidingForwarding::begin() {
+#ifdef _LP64
+  assert(_bases_table == nullptr, "should not be initialized yet");
+  assert(_fallback_table == nullptr, "should not be initialized yet");
+
+  size_t max = _num_regions * NUM_TARGET_REGIONS;
+  _bases_table = NEW_C_HEAP_ARRAY(HeapWord*, max, mtGC);
+  HeapWord** biased_start = _bases_table - _heap_start_region_bias;
+  _biased_bases[0] = biased_start;
+  _biased_bases[1] = biased_start + _num_regions;
+  for (size_t i = 0; i < max; i++) {
+    _bases_table[i] = UNUSED_BASE;
+  }
+#endif
+}
+
+void SlidingForwarding::end() {
+#ifdef _LP64
+  assert(_bases_table != nullptr, "should be initialized");
+  FREE_C_HEAP_ARRAY(HeapWord*, _bases_table);
+  _bases_table = nullptr;
+  delete _fallback_table;
+  _fallback_table = nullptr;
+#endif
+}
+
+void SlidingForwarding::fallback_forward_to(HeapWord* from, HeapWord* to) {
+  if (_fallback_table == nullptr) {
+    _fallback_table = new (mtGC) FallbackTable();
+  }
+  _fallback_table->put_when_absent(from, to);
+}
+
+HeapWord* SlidingForwarding::fallback_forwardee(HeapWord* from) {
+  assert(_fallback_table != nullptr, "fallback table must be present");
+  HeapWord** found = _fallback_table->get(from);
+  if (found != nullptr) {
+    return *found;
+  } else {
+    return nullptr;
+  }
+}
diff a/src/hotspot/share/gc/shared/slidingForwarding.hpp b/src/hotspot/share/gc/shared/slidingForwarding.hpp
--- /dev/null
+++ b/src/hotspot/share/gc/shared/slidingForwarding.hpp
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2021, Red Hat, Inc. All rights reserved.
+ * Copyright Amazon.com Inc. 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef SHARE_GC_SHARED_SLIDINGFORWARDING_HPP
+#define SHARE_GC_SHARED_SLIDINGFORWARDING_HPP
+
+#include "memory/allocation.hpp"
+#include "memory/memRegion.hpp"
+#include "oops/markWord.hpp"
+#include "oops/oopsHierarchy.hpp"
+#include "utilities/fastHash.hpp"
+#include "utilities/resourceHash.hpp"
+
+/**
+ * SlidingForwarding is a method to store forwarding information in a compressed form into the object header,
+ * that has been specifically designed for sliding compaction GCs and compact object headers. With compact object
+ * headers, we store the compressed class pointer in the header, which would be overwritten by full forwarding
+ * pointer, if we allow the legacy forwarding code to act. This would lose the class information for the object,
+ * which is required later in GC cycle to iterate the reference fields and get the object size for copying.
+ *
+ * SlidingForwarding requires only small side tables and guarantees constant-time access and modification.
+ *
+ * The idea is to use a pointer compression scheme very similar to the one that is used for compressed oops.
+ * We divide the heap into number of logical regions. Each region spans maximum of 2^NUM_OFFSET_BITS words.
+ *
+ * The key advantage of sliding compaction for encoding efficiency: it can forward objects from one region to a
+ * maximum of two regions. This is an intuitive property: when we slide the compact region full of data, it can
+ * only span two adjacent regions. This property allows us to use the off-side table to record the addresses of
+ * two target regions. The table holds N*2 entries for N logical regions. For each region, it gives the base
+ * address of the two target regions, or a special placeholder if not used. A single bit in forwarding would
+ * indicate to which of the two "to" regions the object is forwarded into.
+ *
+ * This encoding efficiency allows to store the forwarding information in the object header _together_ with the
+ * compressed class pointer.
+ *
+ * When recording the sliding forwarding, the mark word would look roughly like this:
+ *
+ *   64                              32                                0
+ *    [................................OOOOOOOOOOOOOOOOOOOOOOOOOOOOAFTT]
+ *                                                                    ^----- normal lock bits, would record "object is forwarded"
+ *                                                                  ^------- fallback bit (explained below)
+ *                                                                 ^-------- alternate region select
+ *                                     ^------------------------------------ in-region offset
+ *     ^-------------------------------------------------------------------- protected area, *not touched* by this code, useful for
+ *                                                                           compressed class pointer with compact object headers
+ *
+ * Adding a forwarding then generally works as follows:
+ *   1. Compute the "to" offset in the "to" region, this gives "offset".
+ *   2. Check if the primary "from" offset at base table contains "to" region base, use it.
+ *      If not usable, continue to next step. If usable, set "alternate" = "false" and jump to (4).
+ *   3. Check if the alternate "from" offset at base table contains "to" region base, use it.
+ *      This gives us "alternate" = "true". This should always complete for sliding forwarding.
+ *   4. Compute the mark word from "offset" and "alternate", write it out
+ *
+ * Similarly, looking up the target address, given an original object address generally works as follows:
+ *   1. Load the mark from object, and decode "offset" and "alternate" from there
+ *   2. Compute the "from" base offset from the object
+ *   3. Look up "to" region base from the base table either at primary or alternate indices, using "alternate" flag
+ *   4. Compute the "to" address from "to" region base and "offset"
+ *
+ * This algorithm is broken by G1 last-ditch serial compaction: there, object from a single region can be
+ * forwarded to multiple, more than two regions. To deal with that, we initialize a fallback-hashtable for
+ * storing those extra forwardings, and set another bit in the header to indicate that the forwardee is not
+ * encoded but should be looked-up in the hashtable. G1 serial compaction is not very common - it is the
+ * last-last-ditch GC that is used when the JVM is scrambling to squeeze more space out of the heap, and at
+ * that point, ultimate performance is no longer the main concern.
+ */
+class SlidingForwarding : public AllStatic {
+private:
+
+  /*
+   * A simple hash-table that acts as fallback for the sliding forwarding.
+   * This is used in the case of G1 serial compaction, which violates the
+   * assumption of sliding forwarding that each object of any region is only
+   * ever forwarded to one of two target regions. At this point, the GC is
+   * scrambling to free up more Java heap memory, and therefore performance
+   * is not the major concern.
+   *
+   * The implementation is a straightforward open hashtable.
+   * It is a single-threaded (not thread-safe) implementation, and that
+   * is sufficient because G1 serial compaction is single-threaded.
+   */
+  inline static unsigned hash(HeapWord* const& from) {
+    uint64_t val = reinterpret_cast<uint64_t>(from);
+    uint64_t hash = FastHash::get_hash64(val, UCONST64(0xAAAAAAAAAAAAAAAA));
+    return checked_cast<unsigned>(hash >> 32);
+  }
+  inline static bool equals(HeapWord* const& lhs, HeapWord* const& rhs) {
+    return lhs == rhs;
+  }
+  typedef ResourceHashtable<HeapWord* /* key-type */, HeapWord* /* value-type */,
+                            1024 /* size */, AnyObj::C_HEAP /* alloc-type */, mtGC,
+                            SlidingForwarding::hash, SlidingForwarding::equals> FallbackTable;
+
+  static const uintptr_t MARK_LOWER_HALF_MASK = right_n_bits(32);
+
+  // We need the lowest two bits to indicate a forwarded object.
+  // The next bit indicates that the forwardee should be looked-up in a fallback-table.
+  static const int FALLBACK_SHIFT = markWord::lock_bits;
+  static const int FALLBACK_BITS = 1;
+  static const int FALLBACK_MASK = right_n_bits(FALLBACK_BITS) << FALLBACK_SHIFT;
+
+  // Next bit selects the target region
+  static const int ALT_REGION_SHIFT = FALLBACK_SHIFT + FALLBACK_BITS;
+  static const int ALT_REGION_BITS = 1;
+  // This will be "2" always, but expose it as named constant for clarity
+  static const size_t NUM_TARGET_REGIONS = 1 << ALT_REGION_BITS;
+
+  // The offset bits start then
+  static const int OFFSET_BITS_SHIFT = ALT_REGION_SHIFT + ALT_REGION_BITS;
+
+  // How many bits we use for the offset
+  static const int NUM_OFFSET_BITS = 32 - OFFSET_BITS_SHIFT;
+
+  // Indicates an unused base address in the target base table.
+  static HeapWord* const UNUSED_BASE;
+
+  static HeapWord*      _heap_start;
+  static size_t         _region_size_words;
+
+  static size_t         _heap_start_region_bias;
+  static size_t         _num_regions;
+  static uint           _region_size_bytes_shift;
+  static uintptr_t      _region_mask;
+
+  // The target base table memory.
+  static HeapWord**     _bases_table;
+  // Entries into the target base tables, biased to the start of the heap.
+  static HeapWord**     _biased_bases[NUM_TARGET_REGIONS];
+
+  static FallbackTable* _fallback_table;
+
+  static inline size_t biased_region_index_containing(HeapWord* addr);
+
+  static inline uintptr_t encode_forwarding(HeapWord* from, HeapWord* to);
+  static inline HeapWord* decode_forwarding(HeapWord* from, uintptr_t encoded);
+
+  static void fallback_forward_to(HeapWord* from, HeapWord* to);
+  static HeapWord* fallback_forwardee(HeapWord* from);
+
+  static inline void forward_to_impl(oop from, oop to);
+  static inline oop forwardee_impl(oop from);
+
+public:
+  static void initialize(MemRegion heap, size_t region_size_words);
+
+  static void begin();
+  static void end();
+
+  static inline bool is_forwarded(oop obj);
+  static inline bool is_not_forwarded(oop obj);
+
+  static inline void forward_to(oop from, oop to);
+  static inline oop forwardee(oop from);
+};
+
+#endif // SHARE_GC_SHARED_SLIDINGFORWARDING_HPP
diff a/src/hotspot/share/gc/shared/slidingForwarding.inline.hpp b/src/hotspot/share/gc/shared/slidingForwarding.inline.hpp
--- /dev/null
+++ b/src/hotspot/share/gc/shared/slidingForwarding.inline.hpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2021, Red Hat, Inc. All rights reserved.
+ * Copyright Amazon.com Inc. 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef SHARE_GC_SHARED_SLIDINGFORWARDING_INLINE_HPP
+#define SHARE_GC_SHARED_SLIDINGFORWARDING_INLINE_HPP
+
+#include "gc/shared/gc_globals.hpp"
+#include "gc/shared/slidingForwarding.hpp"
+#include "oops/markWord.hpp"
+#include "oops/oop.inline.hpp"
+#include "utilities/macros.hpp"
+
+inline bool SlidingForwarding::is_forwarded(oop obj) {
+  return obj->is_forwarded();
+}
+
+inline bool SlidingForwarding::is_not_forwarded(oop obj) {
+  return !obj->is_forwarded();
+}
+
+size_t SlidingForwarding::biased_region_index_containing(HeapWord* addr) {
+  return (uintptr_t)addr >> _region_size_bytes_shift;
+}
+
+uintptr_t SlidingForwarding::encode_forwarding(HeapWord* from, HeapWord* to) {
+  static_assert(NUM_TARGET_REGIONS == 2, "Only implemented for this amount");
+
+  size_t from_reg_idx = biased_region_index_containing(from);
+  HeapWord* to_region_base = (HeapWord*)((uintptr_t)to & _region_mask);
+
+  HeapWord** base = &_biased_bases[0][from_reg_idx];
+  uintptr_t alternate = 0;
+  if (*base == to_region_base) {
+    // Primary is good
+  } else if (*base == UNUSED_BASE) {
+    // Primary is free
+    *base = to_region_base;
+  } else {
+    base = &_biased_bases[1][from_reg_idx];
+    if (*base == to_region_base) {
+      // Alternate is good
+    } else if (*base == UNUSED_BASE) {
+      // Alternate is free
+      *base = to_region_base;
+    } else {
+      // Both primary and alternate are not fitting
+      // This happens only in the following rare situations:
+      // - In Serial GC, sometimes when compact-top switches spaces, because the
+      //   region boudaries are virtual and objects can cross regions
+      // - In G1 serial compaction, because tails of various compaction chains
+      //   are distributed across the remainders of already compacted regions.
+      return (1 << FALLBACK_SHIFT) | markWord::marked_value;
+    }
+    alternate = 1;
+  }
+
+  size_t offset = pointer_delta(to, to_region_base);
+  assert(offset < _region_size_words, "Offset should be within the region. from: " PTR_FORMAT
+         ", to: " PTR_FORMAT ", to_region_base: " PTR_FORMAT ", offset: " SIZE_FORMAT,
+         p2i(from), p2i(to), p2i(to_region_base), offset);
+
+  uintptr_t encoded = (offset << OFFSET_BITS_SHIFT) |
+                      (alternate << ALT_REGION_SHIFT) |
+                      markWord::marked_value;
+
+  assert(to == decode_forwarding(from, encoded), "must be reversible");
+  assert((encoded & ~MARK_LOWER_HALF_MASK) == 0, "must encode to lowest 32 bits");
+  return encoded;
+}
+
+HeapWord* SlidingForwarding::decode_forwarding(HeapWord* from, uintptr_t encoded) {
+  assert((encoded & markWord::lock_mask_in_place) == markWord::marked_value, "must be marked as forwarded");
+  assert((encoded & FALLBACK_MASK) == 0, "must not be fallback-forwarded");
+  assert((encoded & ~MARK_LOWER_HALF_MASK) == 0, "must decode from lowest 32 bits");
+  size_t alternate = (encoded >> ALT_REGION_SHIFT) & right_n_bits(ALT_REGION_BITS);
+  assert(alternate < NUM_TARGET_REGIONS, "Sanity");
+  uintptr_t offset = (encoded >> OFFSET_BITS_SHIFT);
+
+  size_t from_idx = biased_region_index_containing(from);
+  HeapWord* base = _biased_bases[alternate][from_idx];
+  assert(base != UNUSED_BASE, "must not be unused base: encoded: " INTPTR_FORMAT, encoded);
+  HeapWord* decoded = base + offset;
+  assert(decoded >= _heap_start,
+         "Address must be above heap start. encoded: " INTPTR_FORMAT ", alt_region: " SIZE_FORMAT ", base: " PTR_FORMAT,
+         encoded, alternate, p2i(base));
+
+  return decoded;
+}
+
+inline void SlidingForwarding::forward_to_impl(oop from, oop to) {
+  assert(_bases_table != nullptr, "call begin() before forwarding");
+
+  markWord from_header = from->mark();
+  if (from_header.has_displaced_mark_helper()) {
+    from_header = from_header.displaced_mark_helper();
+  }
+
+  HeapWord* from_hw = cast_from_oop<HeapWord*>(from);
+  HeapWord* to_hw   = cast_from_oop<HeapWord*>(to);
+  uintptr_t encoded = encode_forwarding(from_hw, to_hw);
+  markWord new_header = markWord((from_header.value() & ~MARK_LOWER_HALF_MASK) | encoded);
+  from->set_mark(new_header);
+
+  if ((encoded & FALLBACK_MASK) != 0) {
+    fallback_forward_to(from_hw, to_hw);
+  }
+}
+
+inline void SlidingForwarding::forward_to(oop obj, oop fwd) {
+#ifdef _LP64
+  assert(_bases_table != nullptr, "expect sliding forwarding initialized");
+  forward_to_impl(obj, fwd);
+  assert(forwardee(obj) == fwd, "must be forwarded to correct forwardee");
+#else
+  obj->forward_to(fwd);
+#endif
+}
+
+inline oop SlidingForwarding::forwardee_impl(oop from) {
+  assert(_bases_table != nullptr, "call begin() before asking for forwarding");
+
+  markWord header = from->mark();
+  HeapWord* from_hw = cast_from_oop<HeapWord*>(from);
+  if ((header.value() & FALLBACK_MASK) != 0) {
+    HeapWord* to = fallback_forwardee(from_hw);
+    return cast_to_oop(to);
+  }
+  uintptr_t encoded = header.value() & MARK_LOWER_HALF_MASK;
+  HeapWord* to = decode_forwarding(from_hw, encoded);
+  return cast_to_oop(to);
+}
+
+inline oop SlidingForwarding::forwardee(oop obj) {
+#ifdef _LP64
+  assert(_bases_table != nullptr, "expect sliding forwarding initialized");
+  return forwardee_impl(obj);
+#else
+  return obj->forwardee();
+#endif
+}
+
+#endif // SHARE_GC_SHARED_SLIDINGFORWARDING_INLINE_HPP
diff a/src/hotspot/share/gc/shenandoah/shenandoahAsserts.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAsserts.cpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahAsserts.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahAsserts.cpp
@@ -195,11 +195,11 @@
     print_failure(_safe_unknown, obj, interior_loc, nullptr, "Shenandoah assert_correct failed",
                   "oop must point to a heap address",
                   file, line);
   }
 
-  Klass* obj_klass = obj->klass_or_null();
+  Klass* obj_klass = obj->forward_safe_klass();
   if (obj_klass == nullptr) {
     print_failure(_safe_unknown, obj, interior_loc, nullptr, "Shenandoah assert_correct failed",
                   "Object klass pointer should not be null",
                   file,line);
   }
@@ -227,11 +227,11 @@
       print_failure(_safe_oop, obj, interior_loc, nullptr, "Shenandoah assert_correct failed",
                     "Forwardee must point to a heap address",
                     file, line);
     }
 
-    if (obj_klass != fwd->klass()) {
+    if (obj_klass != fwd->forward_safe_klass()) {
       print_failure(_safe_oop, obj, interior_loc, nullptr, "Shenandoah assert_correct failed",
                     "Forwardee klass disagrees with object class",
                     file, line);
     }
 
diff a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp
@@ -26,10 +26,11 @@
 
 #include "compiler/oopMap.hpp"
 #include "gc/shared/continuationGCSupport.hpp"
 #include "gc/shared/gcTraceTime.inline.hpp"
 #include "gc/shared/preservedMarks.inline.hpp"
+#include "gc/shared/slidingForwarding.inline.hpp"
 #include "gc/shared/tlab_globals.hpp"
 #include "gc/shared/workerThread.hpp"
 #include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp"
 #include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
 #include "gc/shenandoah/shenandoahConcurrentGC.hpp"
@@ -226,19 +227,23 @@
   {
     // The rest of code performs region moves, where region status is undefined
     // until all phases run together.
     ShenandoahHeapLocker lock(heap->lock());
 
+    SlidingForwarding::begin();
+
     phase2_calculate_target_addresses(worker_slices);
 
     OrderAccess::fence();
 
     phase3_update_references();
 
     phase4_compact_objects(worker_slices);
 
     phase5_epilog();
+
+    SlidingForwarding::end();
   }
 
   // Resize metaspace
   MetaspaceGC::compute_new_size();
 
@@ -367,11 +372,11 @@
     // Object fits into current region, record new location, if object does not move:
     assert(_compact_point + obj_size <= _to_region->end(), "must fit");
     shenandoah_assert_not_forwarded(nullptr, p);
     if (_compact_point != cast_from_oop<HeapWord*>(p)) {
       _preserved_marks->push_if_necessary(p, p->mark());
-      p->forward_to(cast_to_oop(_compact_point));
+      SlidingForwarding::forward_to(p, cast_to_oop(_compact_point));
     }
     _compact_point += obj_size;
   }
 };
 
@@ -490,11 +495,11 @@
       size_t start = to_end - num_regions;
 
       if (start >= to_begin && start != r->index()) {
         // Fits into current window, and the move is non-trivial. Record the move then, and continue scan.
         _preserved_marks->get(0)->push_if_necessary(old_obj, old_obj->mark());
-        old_obj->forward_to(cast_to_oop(heap->get_region(start)->bottom()));
+        SlidingForwarding::forward_to(old_obj, cast_to_oop(heap->get_region(start)->bottom()));
         to_end = start;
         continue;
       }
     }
 
@@ -750,12 +755,12 @@
   inline void do_oop_work(T* p) {
     T o = RawAccess<>::oop_load(p);
     if (!CompressedOops::is_null(o)) {
       oop obj = CompressedOops::decode_not_null(o);
       assert(_ctx->is_marked(obj), "must be marked");
-      if (obj->is_forwarded()) {
-        oop forw = obj->forwardee();
+      if (SlidingForwarding::is_forwarded(obj)) {
+        oop forw = SlidingForwarding::forwardee(obj);
         RawAccess<IS_NOT_NULL>::oop_store(p, forw);
       }
     }
   }
 
@@ -861,13 +866,13 @@
     _heap(ShenandoahHeap::heap()), _worker_id(worker_id) {}
 
   void do_object(oop p) {
     assert(_heap->complete_marking_context()->is_marked(p), "must be marked");
     size_t size = p->size();
-    if (p->is_forwarded()) {
+    if (SlidingForwarding::is_forwarded(p)) {
       HeapWord* compact_from = cast_from_oop<HeapWord*>(p);
-      HeapWord* compact_to = cast_from_oop<HeapWord*>(p->forwardee());
+      HeapWord* compact_to = cast_from_oop<HeapWord*>(SlidingForwarding::forwardee(p));
       assert(compact_from != compact_to, "Forwarded object should move");
       Copy::aligned_conjoint_words(compact_from, compact_to, size);
       oop new_obj = cast_to_oop(compact_to);
 
       ContinuationGCSupport::relativize_stack_chunk(new_obj);
@@ -969,20 +974,20 @@
 
   for (size_t c = heap->num_regions(); c > 0; c--) {
     ShenandoahHeapRegion* r = heap->get_region(c - 1);
     if (r->is_humongous_start()) {
       oop old_obj = cast_to_oop(r->bottom());
-      if (!old_obj->is_forwarded()) {
+      if (SlidingForwarding::is_not_forwarded(old_obj)) {
         // No need to move the object, it stays at the same slot
         continue;
       }
       size_t words_size = old_obj->size();
       size_t num_regions = ShenandoahHeapRegion::required_regions(words_size * HeapWordSize);
 
       size_t old_start = r->index();
       size_t old_end   = old_start + num_regions - 1;
-      size_t new_start = heap->heap_region_index_containing(old_obj->forwardee());
+      size_t new_start = heap->heap_region_index_containing(SlidingForwarding::forwardee(old_obj));
       size_t new_end   = new_start + num_regions - 1;
       assert(old_start != new_start, "must be real move");
       assert(r->is_stw_move_allowed(), "Region " SIZE_FORMAT " should be movable", r->index());
 
       Copy::aligned_conjoint_words(r->bottom(), heap->get_region(new_start)->bottom(), words_size);
diff a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
@@ -32,10 +32,11 @@
 #include "gc/shared/gcTimer.hpp"
 #include "gc/shared/gcTraceTime.inline.hpp"
 #include "gc/shared/locationPrinter.inline.hpp"
 #include "gc/shared/memAllocator.hpp"
 #include "gc/shared/plab.hpp"
+#include "gc/shared/slidingForwarding.hpp"
 #include "gc/shared/tlab_globals.hpp"
 
 #include "gc/shenandoah/shenandoahBarrierSet.hpp"
 #include "gc/shenandoah/shenandoahClosures.inline.hpp"
 #include "gc/shenandoah/shenandoahCollectionSet.hpp"
@@ -437,10 +438,12 @@
 
   _control_thread = new ShenandoahControlThread();
 
   ShenandoahInitLogger::print();
 
+  SlidingForwarding::initialize(_heap_region, ShenandoahHeapRegion::region_size_words());
+
   return JNI_OK;
 }
 
 void ShenandoahHeap::initialize_mode() {
   if (ShenandoahGCMode != nullptr) {
diff a/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp
@@ -294,11 +294,11 @@
     return ShenandoahBarrierSet::resolve_forwarded(p);
   }
 
   assert(ShenandoahThreadLocalData::is_evac_allowed(thread), "must be enclosed in oom-evac scope");
 
-  size_t size = p->size();
+  size_t size = p->forward_safe_size();
 
   assert(!heap_region_containing(p)->is_humongous(), "never evacuate humongous objects");
 
   bool alloc_from_gclab = true;
   HeapWord* copy = nullptr;
@@ -329,15 +329,29 @@
     return ShenandoahBarrierSet::resolve_forwarded(p);
   }
 
   // Copy the object:
   Copy::aligned_disjoint_words(cast_from_oop<HeapWord*>(p), copy, size);
-
-  // Try to install the new forwarding pointer.
   oop copy_val = cast_to_oop(copy);
-  ContinuationGCSupport::relativize_stack_chunk(copy_val);
 
+  if (UseCompactObjectHeaders) {
+    // The copy above is not atomic. Make sure we have seen the proper mark
+    // and re-install it into the copy, so that Klass* is guaranteed to be correct.
+    markWord mark = copy_val->mark();
+    if (!mark.is_marked()) {
+      copy_val->set_mark(mark);
+      ContinuationGCSupport::relativize_stack_chunk(copy_val);
+    } else {
+      // If we copied a mark-word that indicates 'forwarded' state, the object
+      // installation would not succeed. We cannot access Klass* anymore either.
+      // Skip the transformation.
+    }
+  } else {
+    ContinuationGCSupport::relativize_stack_chunk(copy_val);
+  }
+
+  // Try to install the new forwarding pointer.
   oop result = ShenandoahForwarding::try_update_forwardee(p, copy_val);
   if (result == copy_val) {
     // Successfully evacuated. Our copy is now the public one!
     shenandoah_assert_correct(nullptr, copy_val);
     return copy_val;
@@ -509,11 +523,11 @@
     assert (cs >= tams, "only objects past TAMS here: "   PTR_FORMAT " (" PTR_FORMAT ")", p2i(cs), p2i(tams));
     assert (cs < limit, "only objects below limit here: " PTR_FORMAT " (" PTR_FORMAT ")", p2i(cs), p2i(limit));
     oop obj = cast_to_oop(cs);
     assert(oopDesc::is_oop(obj), "sanity");
     assert(ctx->is_marked(obj), "object expected to be marked");
-    size_t size = obj->size();
+    size_t size = obj->forward_safe_size();
     cl->do_object(obj);
     cs += size;
   }
 }
 
diff a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp
@@ -96,11 +96,11 @@
   template <class T>
   void do_oop_work(T* p) {
     T o = RawAccess<>::oop_load(p);
     if (!CompressedOops::is_null(o)) {
       oop obj = CompressedOops::decode_not_null(o);
-      if (is_instance_ref_klass(obj->klass())) {
+      if (is_instance_ref_klass(obj->forward_safe_klass())) {
         obj = ShenandoahForwarding::get_forwardee(obj);
       }
       // Single threaded verification can use faster non-atomic stack and bitmap
       // methods.
       //
@@ -123,11 +123,11 @@
               "oop must be in heap");
     check(ShenandoahAsserts::_safe_unknown, obj, is_object_aligned(obj),
               "oop must be aligned");
 
     ShenandoahHeapRegion *obj_reg = _heap->heap_region_containing(obj);
-    Klass* obj_klass = obj->klass_or_null();
+    Klass* obj_klass = obj->forward_safe_klass();
 
     // Verify that obj is not in dead space:
     {
       // Do this before touching obj->size()
       check(ShenandoahAsserts::_safe_unknown, obj, obj_klass != nullptr,
@@ -138,15 +138,15 @@
       HeapWord *obj_addr = cast_from_oop<HeapWord*>(obj);
       check(ShenandoahAsserts::_safe_unknown, obj, obj_addr < obj_reg->top(),
              "Object start should be within the region");
 
       if (!obj_reg->is_humongous()) {
-        check(ShenandoahAsserts::_safe_unknown, obj, (obj_addr + obj->size()) <= obj_reg->top(),
+        check(ShenandoahAsserts::_safe_unknown, obj, (obj_addr + obj->forward_safe_size()) <= obj_reg->top(),
                "Object end should be within the region");
       } else {
         size_t humongous_start = obj_reg->index();
-        size_t humongous_end = humongous_start + (obj->size() >> ShenandoahHeapRegion::region_size_words_shift());
+        size_t humongous_end = humongous_start + (obj->forward_safe_size() >> ShenandoahHeapRegion::region_size_words_shift());
         for (size_t idx = humongous_start + 1; idx < humongous_end; idx++) {
           check(ShenandoahAsserts::_safe_unknown, obj, _heap->get_region(idx)->is_humongous_continuation(),
                  "Humongous object is in continuation that fits it");
         }
       }
@@ -159,11 +159,11 @@
       switch (_options._verify_liveness) {
         case ShenandoahVerifier::_verify_liveness_disable:
           // skip
           break;
         case ShenandoahVerifier::_verify_liveness_complete:
-          Atomic::add(&_ld[obj_reg->index()], (uint) obj->size(), memory_order_relaxed);
+          Atomic::add(&_ld[obj_reg->index()], (uint) obj->forward_safe_size(), memory_order_relaxed);
           // fallthrough for fast failure for un-live regions:
         case ShenandoahVerifier::_verify_liveness_conservative:
           check(ShenandoahAsserts::_safe_oop, obj, obj_reg->has_live(),
                    "Object must belong to region with live data");
           break;
@@ -200,11 +200,11 @@
              "Should have no humongous forwardees");
 
       HeapWord *fwd_addr = cast_from_oop<HeapWord *>(fwd);
       check(ShenandoahAsserts::_safe_oop, obj, fwd_addr < fwd_reg->top(),
              "Forwardee start should be within the region");
-      check(ShenandoahAsserts::_safe_oop, obj, (fwd_addr + fwd->size()) <= fwd_reg->top(),
+      check(ShenandoahAsserts::_safe_oop, obj, (fwd_addr + fwd->forward_safe_size()) <= fwd_reg->top(),
              "Forwardee end should be within the region");
 
       oop fwd2 = ShenandoahForwarding::get_forwardee_raw_unchecked(fwd);
       check(ShenandoahAsserts::_safe_oop, obj, (fwd == fwd2),
              "Double forwarding");
@@ -303,11 +303,12 @@
    * Verify oop fields from this object.
    * @param obj host object for verified fields
    */
   void verify_oops_from(oop obj) {
     _loc = obj;
-    obj->oop_iterate(this);
+    Klass* klass = obj->forward_safe_klass();
+    obj->oop_iterate_backwards(this, klass);
     _loc = nullptr;
   }
 
   virtual void do_oop(oop* p) { do_oop_work(p); }
   virtual void do_oop(narrowOop* p) { do_oop_work(p); }
@@ -583,11 +584,11 @@
     oop obj = cast_to_oop(addr);
     cl.verify_oop_standalone(obj);
 
     // Verify everything reachable from that object too, hopefully realizing
     // everything was already marked, and never touching further:
-    if (!is_instance_ref_klass(obj->klass())) {
+    if (!is_instance_ref_klass(obj->forward_safe_klass())) {
       cl.verify_oops_from(obj);
       (*processed)++;
     }
     while (!stack.is_empty()) {
       ShenandoahVerifierTask task = stack.pop();
diff a/src/hotspot/share/gc/x/c2/xBarrierSetC2.cpp b/src/hotspot/share/gc/x/c2/xBarrierSetC2.cpp
--- a/src/hotspot/share/gc/x/c2/xBarrierSetC2.cpp
+++ b/src/hotspot/share/gc/x/c2/xBarrierSetC2.cpp
@@ -296,11 +296,11 @@
       // to the first element in the array when cloning object arrays. Otherwise, load
       // barriers are applied to parts of the header. Also adjust the length accordingly.
       assert(src_offset == dest_offset, "should be equal");
       jlong offset = src_offset->get_long();
       if (offset != arrayOopDesc::base_offset_in_bytes(T_OBJECT)) {
-        assert(!UseCompressedClassPointers, "should only happen without compressed class pointers");
+        assert(!UseCompressedClassPointers || UseCompactObjectHeaders, "should only happen without compressed class pointers");
         assert((arrayOopDesc::base_offset_in_bytes(T_OBJECT) - offset) == BytesPerLong, "unexpected offset");
         length = phase->transform_later(new SubLNode(length, phase->longcon(1))); // Size is in longs
         src_offset = phase->longcon(arrayOopDesc::base_offset_in_bytes(T_OBJECT));
         dest_offset = src_offset;
       }
diff a/src/hotspot/share/gc/x/xObjArrayAllocator.cpp b/src/hotspot/share/gc/x/xObjArrayAllocator.cpp
--- a/src/hotspot/share/gc/x/xObjArrayAllocator.cpp
+++ b/src/hotspot/share/gc/x/xObjArrayAllocator.cpp
@@ -71,12 +71,16 @@
   // Segmented clearing
 
   // The array is going to be exposed before it has been completely
   // cleared, therefore we can't expose the header at the end of this
   // function. Instead explicitly initialize it according to our needs.
-  arrayOopDesc::set_mark(mem, markWord::prototype());
-  arrayOopDesc::release_set_klass(mem, _klass);
+  if (UseCompactObjectHeaders) {
+    arrayOopDesc::release_set_mark(mem, _klass->prototype_header());
+  } else {
+    arrayOopDesc::set_mark(mem, markWord::prototype());
+    arrayOopDesc::release_set_klass(mem, _klass);
+  }
   assert(_length >= 0, "length should be non-negative");
   arrayOopDesc::set_length(mem, _length);
 
   // Keep the array alive across safepoints through an invisible
   // root. Invisible roots are not visited by the heap itarator
diff a/src/hotspot/share/gc/z/c2/zBarrierSetC2.cpp b/src/hotspot/share/gc/z/c2/zBarrierSetC2.cpp
--- a/src/hotspot/share/gc/z/c2/zBarrierSetC2.cpp
+++ b/src/hotspot/share/gc/z/c2/zBarrierSetC2.cpp
@@ -495,11 +495,11 @@
       // to the first element in the array when cloning object arrays. Otherwise, load
       // barriers are applied to parts of the header. Also adjust the length accordingly.
       assert(src_offset == dest_offset, "should be equal");
       const jlong offset = src_offset->get_long();
       if (offset != arrayOopDesc::base_offset_in_bytes(T_OBJECT)) {
-        assert(!UseCompressedClassPointers, "should only happen without compressed class pointers");
+        assert(!UseCompressedClassPointers || UseCompactObjectHeaders, "should only happen without compressed class pointers");
         assert((arrayOopDesc::base_offset_in_bytes(T_OBJECT) - offset) == BytesPerLong, "unexpected offset");
         length = phase->transform_later(new SubLNode(length, phase->longcon(1))); // Size is in longs
         src_offset = phase->longcon(arrayOopDesc::base_offset_in_bytes(T_OBJECT));
         dest_offset = src_offset;
       }
diff a/src/hotspot/share/gc/z/zObjArrayAllocator.cpp b/src/hotspot/share/gc/z/zObjArrayAllocator.cpp
--- a/src/hotspot/share/gc/z/zObjArrayAllocator.cpp
+++ b/src/hotspot/share/gc/z/zObjArrayAllocator.cpp
@@ -74,12 +74,16 @@
   // cleared, therefore we can't expose the header at the end of this
   // function. Instead explicitly initialize it according to our needs.
 
   // Signal to the ZIterator that this is an invisible root, by setting
   // the mark word to "marked". Reset to prototype() after the clearing.
-  arrayOopDesc::set_mark(mem, markWord::prototype().set_marked());
-  arrayOopDesc::release_set_klass(mem, _klass);
+  if (UseCompactObjectHeaders) {
+    oopDesc::release_set_mark(mem, _klass->prototype_header().set_marked());
+  } else {
+    arrayOopDesc::set_mark(mem, markWord::prototype().set_marked());
+    arrayOopDesc::release_set_klass(mem, _klass);
+  }
   assert(_length >= 0, "length should be non-negative");
   arrayOopDesc::set_length(mem, _length);
 
   // Keep the array alive across safepoints through an invisible
   // root. Invisible roots are not visited by the heap iterator
@@ -145,9 +149,13 @@
   mem_zap_end_padding(mem);
 
   ZThreadLocalData::clear_invisible_root(_thread);
 
   // Signal to the ZIterator that this is no longer an invisible root
-  oopDesc::release_set_mark(mem, markWord::prototype());
+  if (UseCompactObjectHeaders) {
+    oopDesc::release_set_mark(mem, _klass->prototype_header());
+  } else {
+    oopDesc::release_set_mark(mem, markWord::prototype());
+  }
 
   return cast_to_oop(mem);
 }
diff a/src/hotspot/share/gc/z/zRelocate.cpp b/src/hotspot/share/gc/z/zRelocate.cpp
--- a/src/hotspot/share/gc/z/zRelocate.cpp
+++ b/src/hotspot/share/gc/z/zRelocate.cpp
@@ -589,24 +589,25 @@
   }
 
   zaddress try_relocate_object_inner(zaddress from_addr) {
     ZForwardingCursor cursor;
 
-    const size_t size = ZUtils::object_size(from_addr);
     ZPage* const to_page = target(_forwarding->to_age());
 
     // Lookup forwarding
     {
       const zaddress to_addr = _forwarding->find(from_addr, &cursor);
       if (!is_null(to_addr)) {
         // Already relocated
+        const size_t size = ZUtils::object_size(to_addr);
         increase_other_forwarded(size);
         return to_addr;
       }
     }
 
     // Allocate object
+    const size_t size = ZUtils::object_size(from_addr);
     const zaddress allocated_addr = _allocator->alloc_object(to_page, size);
     if (is_null(allocated_addr)) {
       // Allocation failed
       return zaddress::null;
     }
diff a/src/hotspot/share/interpreter/interpreterRuntime.cpp b/src/hotspot/share/interpreter/interpreterRuntime.cpp
--- a/src/hotspot/share/interpreter/interpreterRuntime.cpp
+++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp
@@ -733,11 +733,10 @@
 // be shared by method invocation and synchronized blocks.
 //%note synchronization_3
 
 //%note monitor_1
 JRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* current, BasicObjectLock* elem))
-  assert(LockingMode != LM_LIGHTWEIGHT, "Should call monitorenter_obj() when using the new lightweight locking");
 #ifdef ASSERT
   current->last_frame().interpreter_frame_verify_monitor(elem);
 #endif
   Handle h_obj(current, elem->obj());
   assert(Universe::heap()->is_in_or_null(h_obj()),
@@ -748,27 +747,10 @@
 #ifdef ASSERT
   current->last_frame().interpreter_frame_verify_monitor(elem);
 #endif
 JRT_END
 
-// NOTE: We provide a separate implementation for the new lightweight locking to workaround a limitation
-// of registers in x86_32. This entry point accepts an oop instead of a BasicObjectLock*.
-// The problem is that we would need to preserve the register that holds the BasicObjectLock,
-// but we are using that register to hold the thread. We don't have enough registers to
-// also keep the BasicObjectLock, but we don't really need it anyway, we only need
-// the object. See also InterpreterMacroAssembler::lock_object().
-// As soon as legacy stack-locking goes away we could remove the other monitorenter() entry
-// point, and only use oop-accepting entries (same for monitorexit() below).
-JRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter_obj(JavaThread* current, oopDesc* obj))
-  assert(LockingMode == LM_LIGHTWEIGHT, "Should call monitorenter() when not using the new lightweight locking");
-  Handle h_obj(current, cast_to_oop(obj));
-  assert(Universe::heap()->is_in_or_null(h_obj()),
-         "must be null or an object");
-  ObjectSynchronizer::enter(h_obj, nullptr, current);
-  return;
-JRT_END
-
 JRT_LEAF(void, InterpreterRuntime::monitorexit(BasicObjectLock* elem))
   oop obj = elem->obj();
   assert(Universe::heap()->is_in(obj), "must be an object");
   // The object could become unlocked through a JNI call, which we have no other checks for.
   // Give a fatal message if CheckJNICalls. Otherwise we ignore it.
diff a/src/hotspot/share/interpreter/zero/bytecodeInterpreter.cpp b/src/hotspot/share/interpreter/zero/bytecodeInterpreter.cpp
--- a/src/hotspot/share/interpreter/zero/bytecodeInterpreter.cpp
+++ b/src/hotspot/share/interpreter/zero/bytecodeInterpreter.cpp
@@ -51,18 +51,21 @@
 #include "oops/resolvedMethodEntry.hpp"
 #include "oops/typeArrayOop.inline.hpp"
 #include "prims/jvmtiExport.hpp"
 #include "prims/jvmtiThreadState.hpp"
 #include "runtime/atomic.hpp"
+#include "runtime/basicLock.inline.hpp"
 #include "runtime/frame.inline.hpp"
+#include "runtime/globals.hpp"
 #include "runtime/handles.inline.hpp"
 #include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/orderAccess.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "runtime/threadCritical.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/exceptions.hpp"
+#include "utilities/globalDefinitions.hpp"
 #include "utilities/macros.hpp"
 
 /*
  * USELABELS - If using GCC, then use labels for the opcode dispatching
  * rather -then a switch statement. This improves performance because it
@@ -622,27 +625,32 @@
 
         // The initial monitor is ours for the taking.
         BasicObjectLock* mon = &istate->monitor_base()[-1];
         mon->set_obj(rcvr);
 
-        // Traditional lightweight locking.
-        markWord displaced = rcvr->mark().set_unlocked();
-        mon->lock()->set_displaced_header(displaced);
-        bool call_vm = (LockingMode == LM_MONITOR);
-        bool inc_monitor_count = true;
-        if (call_vm || rcvr->cas_set_mark(markWord::from_pointer(mon), displaced) != displaced) {
-          // Is it simple recursive case?
-          if (!call_vm && THREAD->is_lock_owned((address) displaced.clear_lock_bits().to_pointer())) {
-            mon->lock()->set_displaced_header(markWord::from_pointer(nullptr));
-          } else {
-            inc_monitor_count = false;
-            CALL_VM(InterpreterRuntime::monitorenter(THREAD, mon), handle_exception);
+        bool success = false;
+        if (LockingMode == LM_LEGACY) {
+           // Traditional lightweight locking.
+          markWord displaced = rcvr->mark().set_unlocked();
+          mon->lock()->set_displaced_header(displaced);
+          success = true;
+          if (rcvr->cas_set_mark(markWord::from_pointer(mon), displaced) != displaced) {
+            // Is it simple recursive case?
+            if (THREAD->is_lock_owned((address) displaced.clear_lock_bits().to_pointer())) {
+              mon->lock()->set_displaced_header(markWord::from_pointer(nullptr));
+            } else {
+              success = false;
+            }
+          }
+          if (success) {
+            THREAD->inc_held_monitor_count();
           }
         }
-        if (inc_monitor_count) {
-          THREAD->inc_held_monitor_count();
+        if (!success) {
+            CALL_VM(InterpreterRuntime::monitorenter(THREAD, mon), handle_exception);
         }
+
       }
       THREAD->set_do_not_unlock_if_synchronized(false);
 
       // Notify jvmti.
       // Whenever JVMTI puts a thread in interp_only_mode, method
@@ -721,27 +729,32 @@
       // find a free monitor
       BasicObjectLock* entry = (BasicObjectLock*) istate->stack_base();
       assert(entry->obj() == nullptr, "Frame manager didn't allocate the monitor");
       entry->set_obj(lockee);
 
-      // traditional lightweight locking
-      markWord displaced = lockee->mark().set_unlocked();
-      entry->lock()->set_displaced_header(displaced);
-      bool call_vm = (LockingMode == LM_MONITOR);
-      bool inc_monitor_count = true;
-      if (call_vm || lockee->cas_set_mark(markWord::from_pointer(entry), displaced) != displaced) {
-        // Is it simple recursive case?
-        if (!call_vm && THREAD->is_lock_owned((address) displaced.clear_lock_bits().to_pointer())) {
-          entry->lock()->set_displaced_header(markWord::from_pointer(nullptr));
-        } else {
-          inc_monitor_count = false;
-          CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
+      bool success = false;
+      if (LockingMode == LM_LEGACY) {
+        // traditional lightweight locking
+        markWord displaced = lockee->mark().set_unlocked();
+        entry->lock()->set_displaced_header(displaced);
+        success = true;
+        if (lockee->cas_set_mark(markWord::from_pointer(entry), displaced) != displaced) {
+          // Is it simple recursive case?
+          if (THREAD->is_lock_owned((address) displaced.clear_lock_bits().to_pointer())) {
+            entry->lock()->set_displaced_header(markWord::from_pointer(nullptr));
+          } else {
+            success = false;
+          }
+        }
+        if (success) {
+          THREAD->inc_held_monitor_count();
         }
       }
-      if (inc_monitor_count) {
-        THREAD->inc_held_monitor_count();
+      if (!success) {
+        CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
       }
+
       UPDATE_PC_AND_TOS(1, -1);
       goto run;
     }
     default: {
       fatal("Unexpected message from frame manager");
@@ -1651,27 +1664,32 @@
           most_recent++;
         }
         if (entry != nullptr) {
           entry->set_obj(lockee);
 
-          // traditional lightweight locking
-          markWord displaced = lockee->mark().set_unlocked();
-          entry->lock()->set_displaced_header(displaced);
-          bool call_vm = (LockingMode == LM_MONITOR);
-          bool inc_monitor_count = true;
-          if (call_vm || lockee->cas_set_mark(markWord::from_pointer(entry), displaced) != displaced) {
-            // Is it simple recursive case?
-            if (!call_vm && THREAD->is_lock_owned((address) displaced.clear_lock_bits().to_pointer())) {
-              entry->lock()->set_displaced_header(markWord::from_pointer(nullptr));
-            } else {
-              inc_monitor_count = false;
-              CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
+          bool success = false;
+          if (LockingMode == LM_LEGACY) {
+            // traditional lightweight locking
+            markWord displaced = lockee->mark().set_unlocked();
+            entry->lock()->set_displaced_header(displaced);
+            success = true;
+            if (lockee->cas_set_mark(markWord::from_pointer(entry), displaced) != displaced) {
+              // Is it simple recursive case?
+              if (THREAD->is_lock_owned((address) displaced.clear_lock_bits().to_pointer())) {
+                entry->lock()->set_displaced_header(markWord::from_pointer(nullptr));
+              } else {
+                success = false;
+              }
+            }
+            if (success) {
+              THREAD->inc_held_monitor_count();
             }
           }
-          if (inc_monitor_count) {
-            THREAD->inc_held_monitor_count();
+          if (!success) {
+            CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
           }
+
           UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
         } else {
           istate->set_msg(more_monitors);
           UPDATE_PC_AND_RETURN(0); // Re-execute
         }
@@ -1685,27 +1703,31 @@
         BasicObjectLock* limit = istate->monitor_base();
         BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
         while (most_recent != limit ) {
           if ((most_recent)->obj() == lockee) {
             BasicLock* lock = most_recent->lock();
-            markWord header = lock->displaced_header();
-            most_recent->set_obj(nullptr);
 
-            // If it isn't recursive we either must swap old header or call the runtime
-            bool dec_monitor_count = true;
-            bool call_vm = (LockingMode == LM_MONITOR);
-            if (header.to_pointer() != nullptr || call_vm) {
-              markWord old_header = markWord::encode(lock);
-              if (call_vm || lockee->cas_set_mark(header, old_header) != old_header) {
-                // restore object for the slow case
-                most_recent->set_obj(lockee);
-                dec_monitor_count = false;
-                InterpreterRuntime::monitorexit(most_recent);
+            bool success = false;
+            if (LockingMode == LM_LEGACY) {
+              // If it isn't recursive we either must swap old header or call the runtime
+              most_recent->set_obj(nullptr);
+              success = true;
+              markWord header = lock->displaced_header();
+              if (header.to_pointer() != nullptr) {
+                markWord old_header = markWord::encode(lock);
+                if (lockee->cas_set_mark(header, old_header) != old_header) {
+                  // restore object for the slow case
+                  most_recent->set_obj(lockee);
+                  success = false;
+                }
+              }
+              if (success) {
+                THREAD->dec_held_monitor_count();
               }
             }
-            if (dec_monitor_count) {
-              THREAD->dec_held_monitor_count();
+            if (!success) {
+              InterpreterRuntime::monitorexit(most_recent);
             }
             UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
           }
           most_recent++;
         }
@@ -1997,14 +2019,17 @@
                 size_t hdr_size = oopDesc::header_size();
                 Copy::fill_to_words(result + hdr_size, obj_size - hdr_size, 0);
               }
 
               // Initialize header, mirrors MemAllocator.
-              oopDesc::set_mark(result, markWord::prototype());
-              oopDesc::set_klass_gap(result, 0);
-              oopDesc::release_set_klass(result, ik);
-
+              if (UseCompactObjectHeaders) {
+                oopDesc::release_set_mark(result, ik->prototype_header());
+              } else {
+                oopDesc::set_mark(result, markWord::prototype());
+                oopDesc::set_klass_gap(result, 0);
+                oopDesc::release_set_klass(result, ik);
+              }
               oop obj = cast_to_oop(result);
 
               // Must prevent reordering of stores for object initialization
               // with stores that publish the new object.
               OrderAccess::storestore();
@@ -3123,26 +3148,32 @@
       // Check all the monitors to see they are unlocked. Install exception if found to be locked.
       while (end < base) {
         oop lockee = end->obj();
         if (lockee != nullptr) {
           BasicLock* lock = end->lock();
-          markWord header = lock->displaced_header();
-          end->set_obj(nullptr);
-
-          // If it isn't recursive we either must swap old header or call the runtime
-          bool dec_monitor_count = true;
-          if (header.to_pointer() != nullptr) {
-            markWord old_header = markWord::encode(lock);
-            if (lockee->cas_set_mark(header, old_header) != old_header) {
-              // restore object for the slow case
-              end->set_obj(lockee);
-              dec_monitor_count = false;
-              InterpreterRuntime::monitorexit(end);
+
+          bool success = false;
+          if (LockingMode == LM_LEGACY) {
+            markWord header = lock->displaced_header();
+            end->set_obj(nullptr);
+
+            // If it isn't recursive we either must swap old header or call the runtime
+            success = true;
+            if (header.to_pointer() != nullptr) {
+              markWord old_header = markWord::encode(lock);
+              if (lockee->cas_set_mark(header, old_header) != old_header) {
+                // restore object for the slow case
+                end->set_obj(lockee);
+                success = false;
+              }
+            }
+            if (success) {
+              THREAD->dec_held_monitor_count();
             }
           }
-          if (dec_monitor_count) {
-            THREAD->dec_held_monitor_count();
+          if (!success) {
+            InterpreterRuntime::monitorexit(end);
           }
 
           // One error is plenty
           if (illegal_state_oop() == nullptr && !suppress_error) {
             {
@@ -3186,11 +3217,11 @@
             if (!suppress_error) {
               VM_JAVA_ERROR_NO_JUMP(vmSymbols::java_lang_NullPointerException(), "");
               illegal_state_oop = Handle(THREAD, THREAD->pending_exception());
               THREAD->clear_pending_exception();
             }
-          } else if (LockingMode == LM_MONITOR) {
+          } else if (LockingMode != LM_LEGACY) {
             InterpreterRuntime::monitorexit(base);
             if (THREAD->has_pending_exception()) {
               if (!suppress_error) illegal_state_oop = Handle(THREAD, THREAD->pending_exception());
               THREAD->clear_pending_exception();
             }
diff a/src/hotspot/share/jfr/leakprofiler/chains/objectSampleMarker.hpp b/src/hotspot/share/jfr/leakprofiler/chains/objectSampleMarker.hpp
--- a/src/hotspot/share/jfr/leakprofiler/chains/objectSampleMarker.hpp
+++ b/src/hotspot/share/jfr/leakprofiler/chains/objectSampleMarker.hpp
@@ -68,11 +68,11 @@
     // save the original markWord
     _store->push(ObjectSampleMarkWord(obj, obj->mark()));
     // now we will set the mark word to "marked" in order to quickly
     // identify sample objects during the reachability search from gc roots.
     assert(!obj->mark().is_marked(), "should only mark an object once");
-    obj->set_mark(markWord::prototype().set_marked());
+    obj->set_mark(obj->prototype_mark().set_marked());
     assert(obj->mark().is_marked(), "invariant");
   }
 };
 
 #endif // SHARE_JFR_LEAKPROFILER_CHAINS_OBJECTSAMPLEMARKER_HPP
diff a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp
--- a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp
+++ b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp
@@ -435,11 +435,15 @@
     JVMCI_THROW_MSG_NULL(NullPointerException, "base object is null");
   }
 
   const char* base_desc = nullptr;
   JVMCIKlassHandle klass(THREAD);
-  if (offset == oopDesc::klass_offset_in_bytes()) {
+
+  // With compact object headers, we can test for the explicit offset within
+  // the header to figure out if compiler code is accessing the class.
+  int klass_offset = UseCompactObjectHeaders ? 4 : oopDesc::klass_offset_in_bytes();
+  if (offset == klass_offset) {
     if (JVMCIENV->isa_HotSpotObjectConstantImpl(base_object)) {
       Handle base_oop = JVMCIENV->asConstant(base_object, JVMCI_CHECK_NULL);
       klass = base_oop->klass();
     } else {
       goto unexpected;
diff a/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp
--- a/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp
+++ b/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp
@@ -175,12 +175,18 @@
 
   Universe_collectedHeap = Universe::heap();
   Universe_base_vtable_size = Universe::base_vtable_size();
   Universe_narrow_oop_base = CompressedOops::base();
   Universe_narrow_oop_shift = CompressedOops::shift();
-  Universe_narrow_klass_base = CompressedKlassPointers::base();
-  Universe_narrow_klass_shift = CompressedKlassPointers::shift();
+  if (UseCompressedClassPointers) {
+    Universe_narrow_klass_base = CompressedKlassPointers::base();
+    Universe_narrow_klass_shift = CompressedKlassPointers::shift();
+  } else {
+    // TODO: Do we need to set these when running without compressed class pointers?
+    Universe_narrow_klass_base = 0;
+    Universe_narrow_klass_shift = 0;
+  }
   Universe_non_oop_bits = Universe::non_oop_word();
   Universe_verify_oop_mask = Universe::verify_oop_mask();
   Universe_verify_oop_bits = Universe::verify_oop_bits();
 
   _supports_inline_contig_alloc = false;
diff a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp
--- a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp
+++ b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp
@@ -134,11 +134,11 @@
   unchecked_nonstatic_field(Array<u1>,         _data,                                  sizeof(u1))                                   \
   unchecked_nonstatic_field(Array<u2>,         _data,                                  sizeof(u2))                                   \
   nonstatic_field(Array<Klass*>,               _length,                                int)                                          \
   nonstatic_field(Array<Klass*>,               _data[0],                               Klass*)                                       \
                                                                                                                                      \
-  volatile_nonstatic_field(BasicLock,          _displaced_header,                      markWord)                                     \
+  volatile_nonstatic_field(BasicLock,          _metadata,                              uintptr_t)                                    \
                                                                                                                                      \
   static_field(CodeCache,                      _low_bound,                             address)                                      \
   static_field(CodeCache,                      _high_bound,                            address)                                      \
                                                                                                                                      \
   nonstatic_field(CollectedHeap,               _total_collections,                     unsigned int)                                 \
@@ -451,11 +451,10 @@
   declare_constant(CompLevel_limited_profile)                             \
   declare_constant(CompLevel_full_profile)                                \
   declare_constant(CompLevel_full_optimization)                           \
   declare_constant(HeapWordSize)                                          \
   declare_constant(InvocationEntryBci)                                    \
-  declare_constant(LogKlassAlignmentInBytes)                              \
   declare_constant(JVMCINMethodData::SPECULATION_LENGTH_BITS)             \
                                                                           \
   declare_constant(JVM_ACC_WRITTEN_FLAGS)                                 \
   declare_constant(JVM_ACC_HAS_FINALIZER)                                 \
   declare_constant(JVM_ACC_IS_CLONEABLE_FAST)                             \
diff a/src/hotspot/share/memory/allocation.hpp b/src/hotspot/share/memory/allocation.hpp
--- a/src/hotspot/share/memory/allocation.hpp
+++ b/src/hotspot/share/memory/allocation.hpp
@@ -128,10 +128,11 @@
   f(mtSynchronizer,   "Synchronization")                                             \
   f(mtServiceability, "Serviceability")                                              \
   f(mtMetaspace,      "Metaspace")                                                   \
   f(mtStringDedup,    "String Deduplication")                                        \
   f(mtObjectMonitor,  "Object Monitors")                                             \
+  f(mtOMWorld,        "OM World")                                                    \
   f(mtNone,           "Unknown")                                                     \
   //end
 
 #define MEMORY_TYPE_DECLARE_ENUM(type, human_readable) \
   type,
diff a/src/hotspot/share/memory/classLoaderMetaspace.cpp b/src/hotspot/share/memory/classLoaderMetaspace.cpp
--- a/src/hotspot/share/memory/classLoaderMetaspace.cpp
+++ b/src/hotspot/share/memory/classLoaderMetaspace.cpp
@@ -28,53 +28,64 @@
 #include "memory/classLoaderMetaspace.hpp"
 #include "memory/metaspace.hpp"
 #include "memory/metaspaceUtils.hpp"
 #include "memory/metaspace/chunkManager.hpp"
 #include "memory/metaspace/internalStats.hpp"
+#include "memory/metaspace/metablock.hpp"
 #include "memory/metaspace/metaspaceArena.hpp"
 #include "memory/metaspace/metaspaceArenaGrowthPolicy.hpp"
+#include "memory/metaspace/metaspaceCommon.hpp"
+#include "memory/metaspace/metaspaceContext.hpp"
 #include "memory/metaspace/metaspaceSettings.hpp"
 #include "memory/metaspace/metaspaceStatistics.hpp"
 #include "memory/metaspace/runningCounters.hpp"
 #include "memory/metaspaceTracer.hpp"
+#include "oops/klass.hpp"
 #include "runtime/mutexLocker.hpp"
 #include "utilities/debug.hpp"
 
 using metaspace::ChunkManager;
+using metaspace::MetaBlock;
 using metaspace::MetaspaceArena;
+using metaspace::MetaspaceContext;
 using metaspace::ArenaGrowthPolicy;
 using metaspace::RunningCounters;
 using metaspace::InternalStats;
 
 #define LOGFMT         "CLMS @" PTR_FORMAT " "
 #define LOGFMT_ARGS    p2i(this)
 
 ClassLoaderMetaspace::ClassLoaderMetaspace(Mutex* lock, Metaspace::MetaspaceType space_type) :
+    ClassLoaderMetaspace(lock, space_type,
+                         MetaspaceContext::context_nonclass(),
+                         MetaspaceContext::context_class(),
+                         CompressedKlassPointers::klass_alignment_in_words())
+{}
+
+ClassLoaderMetaspace::ClassLoaderMetaspace(Mutex* lock, Metaspace::MetaspaceType space_type,
+                                           MetaspaceContext* non_class_context,
+                                           MetaspaceContext* class_context,
+                                           size_t klass_alignment_words) :
   _lock(lock),
   _space_type(space_type),
   _non_class_space_arena(nullptr),
   _class_space_arena(nullptr)
 {
-  ChunkManager* const non_class_cm =
-          ChunkManager::chunkmanager_nonclass();
-
   // Initialize non-class Arena
   _non_class_space_arena = new MetaspaceArena(
-      non_class_cm,
+      non_class_context,
       ArenaGrowthPolicy::policy_for_space_type(space_type, false),
-      RunningCounters::used_nonclass_counter(),
-      "non-class sm");
+      Metaspace::min_allocation_alignment_words,
+      "non-class arena");
 
   // If needed, initialize class arena
-  if (Metaspace::using_class_space()) {
-    ChunkManager* const class_cm =
-            ChunkManager::chunkmanager_class();
+  if (class_context != nullptr) {
     _class_space_arena = new MetaspaceArena(
-        class_cm,
+        class_context,
         ArenaGrowthPolicy::policy_for_space_type(space_type, true),
-        RunningCounters::used_class_counter(),
-        "class sm");
+        klass_alignment_words,
+        "class arena");
   }
 
   UL2(debug, "born (nonclass arena: " PTR_FORMAT ", class arena: " PTR_FORMAT ".",
       p2i(_non_class_space_arena), p2i(_class_space_arena));
 }
@@ -87,16 +98,32 @@
 
 }
 
 // Allocate word_size words from Metaspace.
 MetaWord* ClassLoaderMetaspace::allocate(size_t word_size, Metaspace::MetadataType mdType) {
+  word_size = align_up(word_size, Metaspace::min_allocation_word_size);
   MutexLocker fcl(lock(), Mutex::_no_safepoint_check_flag);
-  if (Metaspace::is_class_space_allocation(mdType)) {
-    return class_space_arena()->allocate(word_size);
+  MetaBlock result, wastage;
+  const bool is_class = have_class_space_arena() && mdType == Metaspace::ClassType;
+  if (is_class) {
+    assert(word_size >= (sizeof(Klass)/BytesPerWord), "weird size for klass: %zu", word_size);
+    result = class_space_arena()->allocate(word_size, wastage);
   } else {
-    return non_class_space_arena()->allocate(word_size);
+    result = non_class_space_arena()->allocate(word_size, wastage);
+  }
+  if (wastage.is_nonempty()) {
+    non_class_space_arena()->deallocate(wastage);
+  }
+#ifdef ASSERT
+  if (result.is_nonempty()) {
+    const bool in_class_arena = class_space_arena() != nullptr ? class_space_arena()->contains(result) : false;
+    const bool in_nonclass_arena = non_class_space_arena()->contains(result);
+    assert((is_class && in_class_arena) || (!is_class && in_class_arena != in_nonclass_arena),
+           "block from neither arena " METABLOCKFORMAT "?", METABLOCKFORMATARGS(result));
   }
+#endif
+  return result.base();
 }
 
 // Attempt to expand the GC threshold to be good for at least another word_size words
 // and allocate. Returns null if failure. Used during Metaspace GC.
 MetaWord* ClassLoaderMetaspace::expand_and_allocate(size_t word_size, Metaspace::MetadataType mdType) {
@@ -129,17 +156,23 @@
 }
 
 // Prematurely returns a metaspace allocation to the _block_freelists
 // because it is not needed anymore.
 void ClassLoaderMetaspace::deallocate(MetaWord* ptr, size_t word_size, bool is_class) {
-  MutexLocker fcl(lock(), Mutex::_no_safepoint_check_flag);
-  if (Metaspace::using_class_space() && is_class) {
-    class_space_arena()->deallocate(ptr, word_size);
-  } else {
-    non_class_space_arena()->deallocate(ptr, word_size);
+  NOT_LP64(word_size = align_down(word_size, Metaspace::min_allocation_word_size);)
+  MetaBlock bl(ptr, word_size);
+  if (word_size >= Metaspace::min_allocation_word_size) {
+    MutexLocker fcl(lock(), Mutex::_no_safepoint_check_flag);
+    if (have_class_space_arena() && is_class) {
+      assert(class_space_arena()->contains(bl),
+             "Not from class arena " METABLOCKFORMAT "?", METABLOCKFORMATARGS(bl));
+      class_space_arena()->deallocate(MetaBlock(ptr, word_size));
+    } else {
+      non_class_space_arena()->deallocate(MetaBlock(ptr, word_size));
+    }
+    DEBUG_ONLY(InternalStats::inc_num_deallocs();)
   }
-  DEBUG_ONLY(InternalStats::inc_num_deallocs();)
 }
 
 // Update statistics. This walks all in-use chunks.
 void ClassLoaderMetaspace::add_to_statistics(metaspace::ClmsStats* out) const {
   MutexLocker fcl(lock(), Mutex::_no_safepoint_check_flag);
@@ -177,11 +210,11 @@
   size_t used_nc, comm_nc, cap_nc;
   size_t used_c = 0, comm_c = 0, cap_c = 0;
   {
     MutexLocker fcl(lock(), Mutex::_no_safepoint_check_flag);
     usage_numbers(Metaspace::MetadataType::NonClassType, &used_nc, &comm_nc, &cap_nc);
-    if (Metaspace::using_class_space()) {
+    if (have_class_space_arena()) {
       usage_numbers(Metaspace::MetadataType::ClassType, &used_c, &comm_c, &cap_c);
     }
   }
   if (p_used_words != nullptr) {
     (*p_used_words) = used_nc + used_c;
diff a/src/hotspot/share/memory/classLoaderMetaspace.hpp b/src/hotspot/share/memory/classLoaderMetaspace.hpp
--- a/src/hotspot/share/memory/classLoaderMetaspace.hpp
+++ b/src/hotspot/share/memory/classLoaderMetaspace.hpp
@@ -31,11 +31,13 @@
 
 class outputStream;
 
 namespace metaspace {
   struct ClmsStats;
+  class ClmsTester;
   class MetaspaceArena;
+  class MetaspaceContext;
 }
 
 // A ClassLoaderMetaspace manages MetaspaceArena(s) for a CLD.
 //
 // A CLD owns one MetaspaceArena if UseCompressedClassPointers is false. Otherwise
@@ -55,10 +57,11 @@
 //                                                  + chunk ... chunk
 //                                                               ^
 //                                                               alloc top
 //
 class ClassLoaderMetaspace : public CHeapObj<mtClass> {
+  friend class metaspace::ClmsTester; // for gtests
 
   // A reference to an outside lock, held by the CLD.
   Mutex* const _lock;
 
   const Metaspace::MetaspaceType _space_type;
@@ -73,12 +76,18 @@
 
   Mutex* lock() const                             { return _lock; }
   metaspace::MetaspaceArena* non_class_space_arena() const   { return _non_class_space_arena; }
   metaspace::MetaspaceArena* class_space_arena() const       { return _class_space_arena; }
 
-public:
+  bool have_class_space_arena() const { return _class_space_arena != nullptr; }
+
+  ClassLoaderMetaspace(Mutex* lock, Metaspace::MetaspaceType space_type,
+                       metaspace::MetaspaceContext* non_class_context,
+                       metaspace::MetaspaceContext* class_context,
+                       size_t klass_alignment_words);
 
+public:
   ClassLoaderMetaspace(Mutex* lock, Metaspace::MetaspaceType space_type);
 
   ~ClassLoaderMetaspace();
 
   Metaspace::MetaspaceType space_type() const { return _space_type; }
diff a/src/hotspot/share/memory/metaspace.cpp b/src/hotspot/share/memory/metaspace.cpp
--- a/src/hotspot/share/memory/metaspace.cpp
+++ b/src/hotspot/share/memory/metaspace.cpp
@@ -536,10 +536,12 @@
 }
 
 //////  Metaspace methods /////
 
 const MetaspaceTracer* Metaspace::_tracer = nullptr;
+const void* Metaspace::_class_space_start = nullptr;
+const void* Metaspace::_class_space_end = nullptr;
 
 bool Metaspace::initialized() {
   return metaspace::MetaspaceContext::context_nonclass() != nullptr
       LP64_ONLY(&& (using_class_space() ? Metaspace::class_space_is_initialized() : true));
 }
@@ -568,10 +570,12 @@
   assert(is_aligned(rs.base(), Metaspace::reserve_alignment()) &&
          is_aligned(rs.size(), Metaspace::reserve_alignment()),
          "wrong alignment");
 
   MetaspaceContext::initialize_class_space_context(rs);
+  _class_space_start = rs.base();
+  _class_space_end = rs.end();
 }
 
 // Returns true if class space has been setup (initialize_class_space).
 bool Metaspace::class_space_is_initialized() {
   return MetaspaceContext::context_class() != nullptr;
@@ -772,10 +776,11 @@
 
     // Initialize space
     Metaspace::initialize_class_space(rs);
 
     // Set up compressed class pointer encoding.
+    // In CDS=off mode, we give the JVM some leeway to choose a favorable base/shift combination.
     CompressedKlassPointers::initialize((address)rs.base(), rs.size());
   }
 
 #endif
 
@@ -840,13 +845,21 @@
 
   // Try to allocate metadata.
   MetaWord* result = loader_data->metaspace_non_null()->allocate(word_size, mdtype);
 
   if (result != nullptr) {
+#ifdef ASSERT
+    if (using_class_space() && mdtype == ClassType) {
+      assert(is_in_class_space(result) &&
+             is_aligned(result, CompressedKlassPointers::klass_alignment_in_bytes()), "Sanity");
+    } else {
+      assert((is_in_class_space(result) || is_in_nonclass_metaspace(result)) &&
+             is_aligned(result, Metaspace::min_allocation_alignment_bytes), "Sanity");
+    }
+#endif
     // Zero initialize.
     Copy::fill_to_words((HeapWord*)result, word_size, 0);
-
     log_trace(metaspace)("Metaspace::allocate: type %d return " PTR_FORMAT ".", (int)type, p2i(result));
   }
 
   return result;
 }
@@ -975,19 +988,15 @@
   // classes have been unloaded. However, this call is required for the code
   // in MetaspaceCriticalAllocation::try_allocate_critical to work.
   MetaspaceCriticalAllocation::process();
 }
 
-bool Metaspace::contains(const void* ptr) {
-  if (MetaspaceShared::is_in_shared_metaspace(ptr)) {
-    return true;
-  }
-  return contains_non_shared(ptr);
+// Returns true if pointer points into one of the metaspace regions, or
+// into the class space.
+bool Metaspace::is_in_shared_metaspace(const void* ptr) {
+  return MetaspaceShared::is_in_shared_metaspace(ptr);
 }
 
-bool Metaspace::contains_non_shared(const void* ptr) {
-  if (using_class_space() && VirtualSpaceList::vslist_class()->contains((MetaWord*)ptr)) {
-     return true;
-  }
-
+// Returns true if pointer points into one of the non-class-space metaspace regions.
+bool Metaspace::is_in_nonclass_metaspace(const void* ptr) {
   return VirtualSpaceList::vslist_nonclass()->contains((MetaWord*)ptr);
 }
diff a/src/hotspot/share/memory/metaspace.hpp b/src/hotspot/share/memory/metaspace.hpp
--- a/src/hotspot/share/memory/metaspace.hpp
+++ b/src/hotspot/share/memory/metaspace.hpp
@@ -24,20 +24,20 @@
  */
 #ifndef SHARE_MEMORY_METASPACE_HPP
 #define SHARE_MEMORY_METASPACE_HPP
 
 #include "memory/allocation.hpp"
+#include "memory/virtualspace.hpp"
 #include "runtime/globals.hpp"
 #include "utilities/exceptions.hpp"
 #include "utilities/globalDefinitions.hpp"
 
 class ClassLoaderData;
 class MetaspaceShared;
 class MetaspaceTracer;
 class Mutex;
 class outputStream;
-class ReservedSpace;
 
 ////////////////// Metaspace ///////////////////////
 
 // Namespace for important central static functions
 // (auxiliary stuff goes into MetaspaceUtils)
@@ -62,11 +62,13 @@
 
 private:
 
   static const MetaspaceTracer* _tracer;
 
-  static bool _initialized;
+  // For quick pointer testing: extent of class space; nullptr if no class space.
+  static const void* _class_space_start;
+  static const void* _class_space_end;
 
 public:
 
   static const MetaspaceTracer* tracer() { return _tracer; }
 
@@ -103,20 +105,53 @@
   static size_t commit_alignment_words();
 
   // The largest possible single allocation
   static size_t max_allocation_word_size();
 
+  // Minimum allocation alignment, in bytes. All MetaData shall be aligned correclty
+  // to be able to hold 64-bit data types. Unlike malloc, we don't care for larger
+  // data types.
+  static constexpr size_t min_allocation_alignment_bytes = sizeof(uint64_t);
+
+  // Minimum allocation alignment, in words, Metaspace observes.
+  static constexpr size_t min_allocation_alignment_words = min_allocation_alignment_bytes / BytesPerWord;
+
+  // Every allocation will get rounded up to the minimum word size.
+  static constexpr size_t min_allocation_word_size = min_allocation_alignment_words;
+
   static MetaWord* allocate(ClassLoaderData* loader_data, size_t word_size,
                             MetaspaceObj::Type type, TRAPS);
 
   // Non-TRAPS version of allocate which can be called by a non-Java thread, that returns
   // null on failure.
   static MetaWord* allocate(ClassLoaderData* loader_data, size_t word_size,
                             MetaspaceObj::Type type);
 
-  static bool contains(const void* ptr);
-  static bool contains_non_shared(const void* ptr);
+  static bool contains(const void* ptr) {
+    return is_in_shared_metaspace(ptr) || // in cds
+           is_in_class_space(ptr) ||      // in class space
+           is_in_nonclass_metaspace(ptr); // in one of the non-class regions?
+  }
+
+  // kept for now for backward compat reasons, but lets test if callers really need this
+  static bool contains_non_shared(const void* ptr) {
+    return is_in_class_space(ptr) ||      // in class space
+           is_in_nonclass_metaspace(ptr); // in one of the non-class regions?
+  }
+
+  // Returns true if pointer points into one of the metaspace regions, or
+  // into the class space.
+  static bool is_in_shared_metaspace(const void* ptr);
+
+  // Returns true if pointer points into one of the non-class-space metaspace regions.
+  static bool is_in_nonclass_metaspace(const void* ptr);
+
+  // Returns true if ptr points into class space, false if it doesn't or if
+  // there is no class space.
+  static inline bool is_in_class_space(const void* ptr) {
+    return ptr < _class_space_end && ptr >= _class_space_start;
+  }
 
   // Free empty virtualspaces
   static void purge(bool classes_unloaded);
 
   static void report_metadata_oome(ClassLoaderData* loader_data, size_t word_size,
diff a/src/hotspot/share/memory/metaspace/binList.hpp b/src/hotspot/share/memory/metaspace/binList.hpp
--- a/src/hotspot/share/memory/metaspace/binList.hpp
+++ b/src/hotspot/share/memory/metaspace/binList.hpp
@@ -26,20 +26,20 @@
 
 #ifndef SHARE_MEMORY_METASPACE_BINLIST_HPP
 #define SHARE_MEMORY_METASPACE_BINLIST_HPP
 
 #include "memory/metaspace/counters.hpp"
+#include "memory/metaspace/metablock.hpp"
 #include "memory/metaspace/metaspaceCommon.hpp"
 #include "utilities/align.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/globalDefinitions.hpp"
 
 namespace metaspace {
 
 // BinList is a data structure to manage small to very small memory blocks
-// (only a few words). It is used to manage deallocated blocks - see
-// class FreeBlocks.
+// (only a few words). It is used to manage deallocated small blocks.
 
 // Memory blocks are kept in a vector of linked lists of equi-sized blocks:
 //
 // wordsize
 //
@@ -141,11 +141,14 @@
     for (int i = 0; i < num_lists; i++) {
       _blocks[i] = nullptr;
     }
   }
 
-  void add_block(MetaWord* p, size_t word_size) {
+  void add_block(MetaBlock mb) {
+    assert(!mb.is_empty(), "Don't add empty blocks");
+    const size_t word_size = mb.word_size();
+    MetaWord* const p = mb.base();
     assert(word_size >= MinWordSize &&
            word_size <= MaxWordSize, "bad block size");
     DEBUG_ONLY(write_canary(p, word_size);)
     const int index = index_for_word_size(word_size);
     Block* old_head = _blocks[index];
@@ -153,30 +156,28 @@
     _blocks[index] = new_head;
     _counter.add(word_size);
   }
 
   // Given a word_size, searches and returns a block of at least that size.
-  // Block may be larger. Real block size is returned in *p_real_word_size.
-  MetaWord* remove_block(size_t word_size, size_t* p_real_word_size) {
+  // Block may be larger.
+  MetaBlock remove_block(size_t word_size) {
     assert(word_size >= MinWordSize &&
            word_size <= MaxWordSize, "bad block size " SIZE_FORMAT ".", word_size);
+    MetaBlock result;
     int index = index_for_word_size(word_size);
     index = index_for_next_non_empty_list(index);
     if (index != -1) {
       Block* b = _blocks[index];
       const size_t real_word_size = word_size_for_index(index);
       assert(b != nullptr, "Sanity");
       assert(check_canary(b, real_word_size),
              "bad block in list[%d] (" BLOCK_FORMAT ")", index, BLOCK_FORMAT_ARGS(b, real_word_size));
       _blocks[index] = b->_next;
       _counter.sub(real_word_size);
-      *p_real_word_size = real_word_size;
-      return (MetaWord*)b;
-    } else {
-      *p_real_word_size = 0;
-      return nullptr;
+      result = MetaBlock((MetaWord*)b, real_word_size);
     }
+    return result;
   }
 
   // Returns number of blocks in this structure
   unsigned count() const { return _counter.count(); }
 
@@ -189,14 +190,18 @@
   void verify() const {
     MemRangeCounter local_counter;
     for (int i = 0; i < num_lists; i++) {
       const size_t s = word_size_for_index(i);
       int pos = 0;
+      Block* b_last = nullptr; // catch simple circularities
       for (Block* b = _blocks[i]; b != nullptr; b = b->_next, pos++) {
         assert(check_canary(b, s), "");
+        assert(b != b_last, "Circle");
         local_counter.add(s);
+        b_last = b;
       }
+      if (UseNewCode)printf("\n");
     }
     local_counter.check(_counter);
   }
 #endif // ASSERT
 
diff a/src/hotspot/share/memory/metaspace/blockTree.cpp b/src/hotspot/share/memory/metaspace/blockTree.cpp
--- a/src/hotspot/share/memory/metaspace/blockTree.cpp
+++ b/src/hotspot/share/memory/metaspace/blockTree.cpp
@@ -180,12 +180,12 @@
   _counter.check(counter);
 
   #undef assrt0n
 }
 
-void BlockTree::zap_range(MetaWord* p, size_t word_size) {
-  memset(p, 0xF3, word_size * sizeof(MetaWord));
+void BlockTree::zap_block(MetaBlock bl) {
+  memset(bl.base(), 0xF3, bl.word_size() * sizeof(MetaWord));
 }
 
 void BlockTree::print_tree(outputStream* st) const {
 
   // Note: we do not print the tree indented, since I found that printing it
@@ -224,10 +224,16 @@
           st->print_cr("@" PTR_FORMAT ": unreadable (skipping rest of chain).", p2i(n2));
           break; // stop printing this chain.
         }
       }
 
+      // Handle simple circularities
+      if (n == n->_right || n == n->_left || n == n->_next) {
+        st->print_cr("@" PTR_FORMAT ": circularity detected.", p2i(n));
+        return; // stop printing
+      }
+
       // Handle children.
       if (n->_right != nullptr) {
         walkinfo info2;
         info2.n = n->_right;
         info2.depth = info.depth + 1;
diff a/src/hotspot/share/memory/metaspace/blockTree.hpp b/src/hotspot/share/memory/metaspace/blockTree.hpp
--- a/src/hotspot/share/memory/metaspace/blockTree.hpp
+++ b/src/hotspot/share/memory/metaspace/blockTree.hpp
@@ -27,17 +27,18 @@
 #define SHARE_MEMORY_METASPACE_BLOCKTREE_HPP
 
 #include "memory/allocation.hpp"
 #include "memory/metaspace/chunklevel.hpp"
 #include "memory/metaspace/counters.hpp"
+#include "memory/metaspace/metablock.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/globalDefinitions.hpp"
 
 namespace metaspace {
 
 // BlockTree is a rather simple binary search tree. It is used to
-//  manage small to medium free memory blocks (see class FreeBlocks).
+//  manage medium to large free memory blocks.
 //
 // There is no separation between payload (managed blocks) and nodes: the
 //  memory blocks themselves are the nodes, with the block size being the key.
 //
 // We store node pointer information in these blocks when storing them. That
@@ -78,12 +79,11 @@
 
     static const intptr_t _canary_value =
         NOT_LP64(0x4e4f4445) LP64_ONLY(0x4e4f44454e4f4445ULL); // "NODE" resp "NODENODE"
 
     // Note: we afford us the luxury of an always-there canary value.
-    //  The space for that is there (these nodes are only used to manage larger blocks,
-    //  see FreeBlocks::MaxSmallBlocksWordSize).
+    //  The space for that is there (these nodes are only used to manage larger blocks).
     //  It is initialized in debug and release, but only automatically tested
     //  in debug.
     const intptr_t _canary;
 
     // Normal tree node stuff...
@@ -333,38 +333,39 @@
       }
     }
   }
 
 #ifdef ASSERT
-  void zap_range(MetaWord* p, size_t word_size);
+  void zap_block(MetaBlock block);
   // Helper for verify()
   void verify_node_pointer(const Node* n) const;
 #endif // ASSERT
 
 public:
 
   BlockTree() : _root(nullptr) {}
 
   // Add a memory block to the tree. Its content will be overwritten.
-  void add_block(MetaWord* p, size_t word_size) {
-    DEBUG_ONLY(zap_range(p, word_size));
+  void add_block(MetaBlock block) {
+    DEBUG_ONLY(zap_block(block);)
+    const size_t word_size = block.word_size();
     assert(word_size >= MinWordSize, "invalid block size " SIZE_FORMAT, word_size);
-    Node* n = new(p) Node(word_size);
+    Node* n = new(block.base()) Node(word_size);
     if (_root == nullptr) {
       _root = n;
     } else {
       insert(_root, n);
     }
     _counter.add(word_size);
   }
 
   // Given a word_size, search and return the smallest block that is equal or
-  //  larger than that size. Upon return, *p_real_word_size contains the actual
-  //  block size.
-  MetaWord* remove_block(size_t word_size, size_t* p_real_word_size) {
+  //  larger than that size.
+  MetaBlock remove_block(size_t word_size) {
     assert(word_size >= MinWordSize, "invalid block size " SIZE_FORMAT, word_size);
 
+    MetaBlock result;
     Node* n = find_closest_fit(word_size);
 
     if (n != nullptr) {
       DEBUG_ONLY(check_node(n);)
       assert(n->_word_size >= word_size, "sanity");
@@ -377,19 +378,17 @@
         n = remove_from_list(n);
       } else {
         remove_node_from_tree(n);
       }
 
-      MetaWord* p = (MetaWord*)n;
-      *p_real_word_size = n->_word_size;
+      result = MetaBlock((MetaWord*)n, n->_word_size);
 
       _counter.sub(n->_word_size);
 
-      DEBUG_ONLY(zap_range(p, n->_word_size));
-      return p;
+      DEBUG_ONLY(zap_block(result);)
     }
-    return nullptr;
+    return result;
   }
 
   // Returns number of blocks in this structure
   unsigned count() const { return _counter.count(); }
 
diff a/src/hotspot/share/memory/metaspace/freeBlocks.cpp b/src/hotspot/share/memory/metaspace/freeBlocks.cpp
--- a/src/hotspot/share/memory/metaspace/freeBlocks.cpp
+++ b/src/hotspot/share/memory/metaspace/freeBlocks.cpp
@@ -28,34 +28,25 @@
 #include "utilities/debug.hpp"
 #include "utilities/globalDefinitions.hpp"
 
 namespace metaspace {
 
-void FreeBlocks::add_block(MetaWord* p, size_t word_size) {
-  if (word_size > MaxSmallBlocksWordSize) {
-    _tree.add_block(p, word_size);
+void FreeBlocks::add_block(MetaBlock bl) {
+  if (bl.word_size() > _small_blocks.MaxWordSize) {
+    _tree.add_block(bl);
   } else {
-    _small_blocks.add_block(p, word_size);
+    _small_blocks.add_block(bl);
   }
 }
 
-MetaWord* FreeBlocks::remove_block(size_t requested_word_size) {
+MetaBlock FreeBlocks::remove_block(size_t requested_word_size) {
   size_t real_size = 0;
-  MetaWord* p = nullptr;
-  if (requested_word_size > MaxSmallBlocksWordSize) {
-    p = _tree.remove_block(requested_word_size, &real_size);
+  MetaBlock bl;
+  if (requested_word_size > _small_blocks.MaxWordSize) {
+    bl = _tree.remove_block(requested_word_size);
   } else {
-    p = _small_blocks.remove_block(requested_word_size, &real_size);
+    bl = _small_blocks.remove_block(requested_word_size);
   }
-  if (p != nullptr) {
-    // Blocks which are larger than a certain threshold are split and
-    //  the remainder is handed back to the manager.
-    const size_t waste = real_size - requested_word_size;
-    if (waste >= MinWordSize) {
-      add_block(p + requested_word_size, waste);
-    }
-  }
-  return p;
+  return bl;
 }
 
 } // namespace metaspace
-
diff a/src/hotspot/share/memory/metaspace/freeBlocks.hpp b/src/hotspot/share/memory/metaspace/freeBlocks.hpp
--- a/src/hotspot/share/memory/metaspace/freeBlocks.hpp
+++ b/src/hotspot/share/memory/metaspace/freeBlocks.hpp
@@ -71,24 +71,20 @@
 
   // This verifies that blocks too large to go into the binlist can be
   // kept in the blocktree.
   STATIC_ASSERT(BinList32::MaxWordSize >= BlockTree::MinWordSize);
 
-  // Cutoff point: blocks larger than this size are kept in the
-  // tree, blocks smaller than or equal to this size in the bin list.
-  const size_t MaxSmallBlocksWordSize = BinList32::MaxWordSize;
-
 public:
 
   // Smallest blocks we can keep in this structure.
   const static size_t MinWordSize = BinList32::MinWordSize;
 
   // Add a block to the deallocation management.
-  void add_block(MetaWord* p, size_t word_size);
+  void add_block(MetaBlock bl);
 
-  // Retrieve a block of at least requested_word_size.
-  MetaWord* remove_block(size_t requested_word_size);
+  // Retrieve a block of at least requested_word_size. May be larger.
+  MetaBlock remove_block(size_t requested_word_size);
 
 #ifdef ASSERT
   void verify() const {
     _tree.verify();
     _small_blocks.verify();
diff a/src/hotspot/share/memory/metaspace/metablock.hpp b/src/hotspot/share/memory/metaspace/metablock.hpp
--- /dev/null
+++ b/src/hotspot/share/memory/metaspace/metablock.hpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2023 Red Hat, Inc. All rights reserved.
+ * Copyright (c) 2023, 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef SHARE_MEMORY_METASPACE_METABLOCK_HPP
+#define SHARE_MEMORY_METASPACE_METABLOCK_HPP
+
+#include "utilities/globalDefinitions.hpp"
+
+class outputStream;
+
+namespace metaspace {
+
+// Tiny structure to be passed by value
+class MetaBlock {
+
+  MetaWord* _base;
+  size_t _word_size;
+
+public:
+
+  MetaBlock(MetaWord* p, size_t word_size) :
+    _base(word_size == 0 ? nullptr : p), _word_size(word_size) {}
+  MetaBlock() : MetaBlock(nullptr, 0) {}
+
+  MetaWord* base() const { return _base; }
+  const MetaWord* end() const { return _base + _word_size; }
+  size_t word_size() const { return _word_size; }
+  bool is_empty() const { return _base == nullptr; }
+  bool is_nonempty() const { return _base != nullptr; }
+  void reset() { _base = nullptr; _word_size = 0; }
+
+  bool operator==(const MetaBlock& rhs) const {
+    return base() == rhs.base() &&
+           word_size() == rhs.word_size();
+  }
+
+  // Split off tail block.
+  inline MetaBlock split_off_tail(size_t tailsize);
+
+  DEBUG_ONLY(inline void verify() const;)
+
+  // Convenience functions
+  inline bool is_aligned_base(size_t alignment_words) const;
+  inline bool is_aligned_size(size_t alignment_words) const;
+
+  void print_on(outputStream* st) const;
+};
+
+#define METABLOCKFORMAT                 "block (@" PTR_FORMAT " word size " SIZE_FORMAT ")"
+#define METABLOCKFORMATARGS(__block__)  p2i((__block__).base()), (__block__).word_size()
+
+} // namespace metaspace
+
+#endif // SHARE_MEMORY_METASPACE_METABLOCK_HPP
diff a/src/hotspot/share/memory/metaspace/metablock.inline.hpp b/src/hotspot/share/memory/metaspace/metablock.inline.hpp
--- /dev/null
+++ b/src/hotspot/share/memory/metaspace/metablock.inline.hpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2023 Red Hat, Inc. All rights reserved.
+ * Copyright (c) 2023, 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef SHARE_MEMORY_METASPACE_METABLOCK_INLINE_HPP
+#define SHARE_MEMORY_METASPACE_METABLOCK_INLINE_HPP
+
+#include "memory/metaspace/metablock.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "utilities/align.hpp"
+#include "utilities/ostream.hpp"
+#include "utilities/debug.hpp"
+
+class outputStream;
+
+namespace metaspace {
+
+inline MetaBlock MetaBlock::split_off_tail(size_t tailsize) {
+  if (is_empty() || tailsize == 0) {
+    return MetaBlock();
+  }
+  assert(tailsize <= _word_size, "invalid split point for block "
+         METABLOCKFORMAT ": %zu", METABLOCKFORMATARGS(*this), tailsize);
+  const size_t new_size = _word_size - tailsize;
+  MetaBlock tail(_base + new_size, tailsize);
+  _word_size = new_size;
+  if (_word_size == 0) {
+    _base = nullptr;
+  }
+  return tail;
+}
+
+inline void MetaBlock::print_on(outputStream* st) const {
+  st->print(METABLOCKFORMAT, METABLOCKFORMATARGS(*this));
+}
+
+// Convenience functions
+inline bool MetaBlock::is_aligned_base(size_t alignment_words) const {
+  return is_aligned(_base, alignment_words * BytesPerWord);
+}
+
+inline bool MetaBlock::is_aligned_size(size_t alignment_words) const {
+  return is_aligned(_word_size, alignment_words);
+}
+
+// some convenience asserts
+#define assert_block_base_aligned(block, alignment_words) \
+  assert(block.is_aligned_base(alignment_words), "Block wrong base alignment " METABLOCKFORMAT, METABLOCKFORMATARGS(block));
+
+#define assert_block_size_aligned(block, alignment_words) \
+  assert(block.is_aligned_size(alignment_words), "Block wrong size alignment " METABLOCKFORMAT, METABLOCKFORMATARGS(block));
+
+#define assert_block_larger_or_equal(block, x) \
+  assert(block.word_size() >= x, "Block too small " METABLOCKFORMAT, METABLOCKFORMATARGS(block));
+
+#ifdef ASSERT
+inline void MetaBlock::verify() const {
+  assert( (_base == nullptr && _word_size == 0) ||
+          (_base != nullptr && _word_size > 0),
+          "block invalid " METABLOCKFORMAT, METABLOCKFORMATARGS(*this));
+}
+#endif
+
+} // namespace metaspace
+
+#endif // SHARE_MEMORY_METASPACE_METABLOCK_INLINE_HPP
diff a/src/hotspot/share/memory/metaspace/metaspaceArena.cpp b/src/hotspot/share/memory/metaspace/metaspaceArena.cpp
--- a/src/hotspot/share/memory/metaspace/metaspaceArena.cpp
+++ b/src/hotspot/share/memory/metaspace/metaspaceArena.cpp
@@ -28,14 +28,16 @@
 #include "logging/logStream.hpp"
 #include "memory/metaspace/chunkManager.hpp"
 #include "memory/metaspace/counters.hpp"
 #include "memory/metaspace/freeBlocks.hpp"
 #include "memory/metaspace/internalStats.hpp"
+#include "memory/metaspace/metablock.inline.hpp"
 #include "memory/metaspace/metachunk.hpp"
 #include "memory/metaspace/metaspaceArena.hpp"
 #include "memory/metaspace/metaspaceArenaGrowthPolicy.hpp"
 #include "memory/metaspace/metaspaceCommon.hpp"
+#include "memory/metaspace/metaspaceContext.hpp"
 #include "memory/metaspace/metaspaceSettings.hpp"
 #include "memory/metaspace/metaspaceStatistics.hpp"
 #include "memory/metaspace/virtualSpaceList.hpp"
 #include "runtime/atomic.hpp"
 #include "runtime/init.hpp"
@@ -54,28 +56,29 @@
 chunklevel_t MetaspaceArena::next_chunk_level() const {
   const int growth_step = _chunks.count();
   return _growth_policy->get_level_at_step(growth_step);
 }
 
-// Given a chunk, add its remaining free committed space to the free block list.
-void MetaspaceArena::salvage_chunk(Metachunk* c) {
-  size_t remaining_words = c->free_below_committed_words();
+// Given a chunk, return the committed remainder of this chunk.
+MetaBlock MetaspaceArena::salvage_chunk(Metachunk* c) {
+  MetaBlock result;
+  const size_t remaining_words = c->free_below_committed_words();
   if (remaining_words >= FreeBlocks::MinWordSize) {
 
     UL2(trace, "salvaging chunk " METACHUNK_FULL_FORMAT ".", METACHUNK_FULL_FORMAT_ARGS(c));
 
     MetaWord* ptr = c->allocate(remaining_words);
     assert(ptr != nullptr, "Should have worked");
-    _total_used_words_counter->increment_by(remaining_words);
 
-    add_allocation_to_fbl(ptr, remaining_words);
+    result = MetaBlock(ptr, remaining_words);
 
     // After this operation: the chunk should have no free committed space left.
     assert(c->free_below_committed_words() == 0,
            "Salvaging chunk failed (chunk " METACHUNK_FULL_FORMAT ").",
            METACHUNK_FULL_FORMAT_ARGS(c));
   }
+  return result;
 }
 
 // Allocate a new chunk from the underlying chunk manager able to hold at least
 // requested word size.
 Metachunk* MetaspaceArena::allocate_new_chunk(size_t requested_word_size) {
@@ -95,35 +98,42 @@
   assert(c->is_in_use(), "Wrong chunk state.");
   assert(c->free_below_committed_words() >= requested_word_size, "Chunk not committed");
   return c;
 }
 
-void MetaspaceArena::add_allocation_to_fbl(MetaWord* p, size_t word_size) {
-  assert(p != nullptr, "p is null");
-  assert_is_aligned_metaspace_pointer(p);
-  assert(word_size > 0, "zero sized");
-
+void MetaspaceArena::add_allocation_to_fbl(MetaBlock bl) {
+  assert(bl.is_nonempty(), "Sanity");
+  assert_block_base_aligned(bl, allocation_alignment_words());
+  assert_block_size_aligned(bl, Metaspace::min_allocation_alignment_words);
   if (_fbl == nullptr) {
     _fbl = new FreeBlocks(); // Create only on demand
   }
-  _fbl->add_block(p, word_size);
+  _fbl->add_block(bl);
 }
 
-MetaspaceArena::MetaspaceArena(ChunkManager* chunk_manager, const ArenaGrowthPolicy* growth_policy,
-                               SizeAtomicCounter* total_used_words_counter,
-                               const char* name) :
-  _chunk_manager(chunk_manager),
+MetaspaceArena::MetaspaceArena(MetaspaceContext* context,
+               const ArenaGrowthPolicy* growth_policy,
+               size_t allocation_alignment_words,
+               const char* name) :
+  _allocation_alignment_words(allocation_alignment_words),
+  _chunk_manager(context->cm()),
   _growth_policy(growth_policy),
   _chunks(),
   _fbl(nullptr),
-  _total_used_words_counter(total_used_words_counter),
+  _total_used_words_counter(context->used_words_counter()),
   _name(name)
 #ifdef ASSERT
   , _first_fence(nullptr)
 #endif
 {
-  UL(debug, ": born.");
+  // Check arena allocation alignment
+  assert(is_power_of_2(_allocation_alignment_words) &&
+         _allocation_alignment_words >= Metaspace::min_allocation_alignment_words &&
+         _allocation_alignment_words <= chunklevel::MIN_CHUNK_WORD_SIZE,
+         "Invalid alignment: %zu", _allocation_alignment_words);
+
+  UL(debug, "born.");
 
   // Update statistics
   InternalStats::inc_num_arena_births();
 }
 
@@ -148,11 +158,11 @@
     _chunk_manager->return_chunk(c);
     // c may be invalid after return_chunk(c) was called. Don't access anymore.
     c = c2;
   }
 
-  UL2(info, "returned %d chunks, total capacity " SIZE_FORMAT " words.",
+  UL2(debug, "returned %d chunks, total capacity " SIZE_FORMAT " words.",
       return_counter.count(), return_counter.total_size());
 
   _total_used_words_counter->decrement_by(return_counter.total_size());
   SOMETIMES(chunk_manager()->verify();)
   delete _fbl;
@@ -213,70 +223,119 @@
 // 1) Attempt to allocate from the free block list.
 // 2) Attempt to allocate from the current chunk.
 // 3) Attempt to enlarge the current chunk in place if it is too small.
 // 4) Attempt to get a new chunk and allocate from that chunk.
 // At any point, if we hit a commit limit, we return null.
-MetaWord* MetaspaceArena::allocate(size_t requested_word_size) {
+MetaBlock MetaspaceArena::allocate(size_t requested_word_size, MetaBlock& wastage) {
   UL2(trace, "requested " SIZE_FORMAT " words.", requested_word_size);
 
-  MetaWord* p = nullptr;
   const size_t aligned_word_size = get_raw_word_size_for_requested_word_size(requested_word_size);
 
+  MetaBlock result;
+  bool taken_from_fbl = false;
+
   // Before bothering the arena proper, attempt to re-use a block from the free blocks list
   if (_fbl != nullptr && !_fbl->is_empty()) {
-    p = _fbl->remove_block(aligned_word_size);
-    if (p != nullptr) {
+    result = _fbl->remove_block(aligned_word_size);
+    if (result.is_nonempty()) {
+      assert_block_larger_or_equal(result, aligned_word_size);
+      assert_block_base_aligned(result, allocation_alignment_words());
+      assert_block_size_aligned(result, Metaspace::min_allocation_alignment_words);
+      // Split off wastage
+      wastage = result.split_off_tail(result.word_size() - aligned_word_size);
+      // Stats, logging
       DEBUG_ONLY(InternalStats::inc_num_allocs_from_deallocated_blocks();)
-      UL2(trace, "returning " PTR_FORMAT " - taken from fbl (now: %d, " SIZE_FORMAT ").",
-          p2i(p), _fbl->count(), _fbl->total_size());
-      assert_is_aligned_metaspace_pointer(p);
+      UL2(trace, "returning " METABLOCKFORMAT " with wastage " METABLOCKFORMAT " - taken from fbl (now: %d, " SIZE_FORMAT ").",
+          METABLOCKFORMATARGS(result), METABLOCKFORMATARGS(wastage), _fbl->count(), _fbl->total_size());
       // Note: free blocks in freeblock dictionary still count as "used" as far as statistics go;
-      // therefore we have no need to adjust any usage counters (see epilogue of allocate_inner())
-      // and can just return here.
-      return p;
+      // therefore we don't need to adjust any usage counters (see epilogue of allocate_inner()).
+      taken_from_fbl = true;
     }
   }
 
-  // Primary allocation
-  p = allocate_inner(aligned_word_size);
+  if (result.is_empty()) {
+    // Free-block allocation failed; we allocate from the arena.
+    // These allocations are fenced.
+    size_t plus_fence = 0;
+  #ifdef ASSERT
+    static constexpr size_t fence_word_size = sizeof(Fence) / BytesPerWord;
+    STATIC_ASSERT(is_aligned(fence_word_size, Metaspace::min_allocation_alignment_words));
+    if (Settings::use_allocation_guard() &&
+        aligned_word_size <= Metaspace::max_allocation_word_size() - fence_word_size) {
+      plus_fence = fence_word_size;
+    }
+  #endif
+
+    // Allocate from arena proper
+    result = allocate_inner(aligned_word_size + plus_fence, wastage);
 
-#ifdef ASSERT
-  // Fence allocation
-  if (p != nullptr && Settings::use_allocation_guard()) {
-    STATIC_ASSERT(is_aligned(sizeof(Fence), BytesPerWord));
-    MetaWord* guard = allocate_inner(sizeof(Fence) / BytesPerWord);
-    if (guard != nullptr) {
-      // Ignore allocation errors for the fence to keep coding simple. If this
-      // happens (e.g. because right at this time we hit the Metaspace GC threshold)
-      // we miss adding this one fence. Not a big deal. Note that his would
-      // be pretty rare. Chances are much higher the primary allocation above
-      // would have already failed).
-      Fence* f = new(guard) Fence(_first_fence);
+  #ifdef ASSERT
+    if (result.is_nonempty() && plus_fence > 0) {
+      assert(result.word_size() == aligned_word_size + plus_fence, "Sanity");
+      MetaBlock fenceblock = result.split_off_tail(fence_word_size);
+      Fence* f = new(fenceblock.base()) Fence(_first_fence);
       _first_fence = f;
     }
+  #endif
+  } // End: allocate from arena proper
+
+  // Logging
+  if (result.is_nonempty()) {
+    LogTarget(Trace, metaspace) lt;
+    if (lt.is_enabled()) {
+      LogStream ls(lt);
+      ls.print(LOGFMT ": returning " METABLOCKFORMAT " taken from %s, ", LOGFMT_ARGS,
+               METABLOCKFORMATARGS(result), (taken_from_fbl ? "fbl" : "arena"));
+      if (wastage.is_empty()) {
+        ls.print("no wastage");
+      } else {
+        ls.print("wastage " METABLOCKFORMAT, METABLOCKFORMATARGS(wastage));
+      }
+    }
+  } else {
+    UL(info, "allocation failed, returned null.");
   }
+
+  // Final sanity checks
+#ifdef ASSERT
+    result.verify();
+    wastage.verify();
+    if (result.is_nonempty()) {
+      assert(result.word_size() == aligned_word_size &&
+             is_aligned(result.base(), _allocation_alignment_words * BytesPerWord),
+             "result bad or unaligned: " METABLOCKFORMAT ".", METABLOCKFORMATARGS(result));
+    }
+    if (wastage.is_nonempty()) {
+      assert(wastage.is_empty() ||
+             (wastage.is_aligned_base(Metaspace::min_allocation_alignment_words) &&
+              wastage.is_aligned_size(Metaspace::min_allocation_alignment_words)),
+             "Misaligned wastage: " METABLOCKFORMAT".", METABLOCKFORMATARGS(wastage));
+    }
 #endif // ASSERT
 
-  return p;
+  return result;
 }
 
 // Allocate from the arena proper, once dictionary allocations and fencing are sorted out.
-MetaWord* MetaspaceArena::allocate_inner(size_t word_size) {
-  assert_is_aligned(word_size, metaspace::AllocationAlignmentWordSize);
+MetaBlock MetaspaceArena::allocate_inner(size_t word_size, MetaBlock& wastage) {
 
-  MetaWord* p = nullptr;
+  MetaBlock result;
   bool current_chunk_too_small = false;
   bool commit_failure = false;
+  size_t alignment_gap_size = 0;
 
   if (current_chunk() != nullptr) {
-
     // Attempt to satisfy the allocation from the current chunk.
 
+    const MetaWord* const chunk_top = current_chunk()->top();
+    alignment_gap_size = align_up(chunk_top, _allocation_alignment_words * BytesPerWord) - chunk_top;
+    const size_t word_size_plus_alignment = word_size + alignment_gap_size;
+
     // If the current chunk is too small to hold the requested size, attempt to enlarge it.
     // If that fails, retire the chunk.
-    if (current_chunk()->free_words() < word_size) {
-      if (!attempt_enlarge_current_chunk(word_size)) {
+    if (current_chunk()->free_words() < word_size_plus_alignment) {
+      if (!attempt_enlarge_current_chunk(word_size_plus_alignment)) {
         current_chunk_too_small = true;
       } else {
         DEBUG_ONLY(InternalStats::inc_num_chunks_enlarged();)
         UL(debug, "enlarged chunk.");
       }
@@ -284,24 +343,30 @@
 
     // Commit the chunk far enough to hold the requested word size. If that fails, we
     // hit a limit (either GC threshold or MaxMetaspaceSize). In that case retire the
     // chunk.
     if (!current_chunk_too_small) {
-      if (!current_chunk()->ensure_committed_additional(word_size)) {
-        UL2(info, "commit failure (requested size: " SIZE_FORMAT ")", word_size);
+      if (!current_chunk()->ensure_committed_additional(word_size_plus_alignment)) {
+        UL2(info, "commit failure (requested size: " SIZE_FORMAT ")", word_size_plus_alignment);
         commit_failure = true;
       }
     }
 
     // Allocate from the current chunk. This should work now.
     if (!current_chunk_too_small && !commit_failure) {
-      p = current_chunk()->allocate(word_size);
-      assert(p != nullptr, "Allocation from chunk failed.");
+      MetaWord* const p_gap = current_chunk()->allocate(word_size_plus_alignment);
+      assert(p_gap != nullptr, "Allocation from chunk failed.");
+      MetaWord* const p_user_allocation = p_gap + alignment_gap_size;
+      result = MetaBlock(p_user_allocation, word_size);
+      if (alignment_gap_size > 0) {
+        NOT_LP64(assert(alignment_gap_size >= AllocationAlignmentWordSize, "Sanity"));
+        wastage = MetaBlock(p_gap, alignment_gap_size);
+      }
     }
   }
 
-  if (p == nullptr) {
+  if (result.is_empty()) {
     // If we are here, we either had no current chunk to begin with or it was deemed insufficient.
     assert(current_chunk() == nullptr ||
            current_chunk_too_small || commit_failure, "Sanity");
 
     Metachunk* new_chunk = allocate_new_chunk(word_size);
@@ -311,67 +376,71 @@
 
       assert(new_chunk->free_below_committed_words() >= word_size, "Sanity");
 
       // We have a new chunk. Before making it the current chunk, retire the old one.
       if (current_chunk() != nullptr) {
-        salvage_chunk(current_chunk());
+        wastage = salvage_chunk(current_chunk());
         DEBUG_ONLY(InternalStats::inc_num_chunks_retired();)
       }
 
       _chunks.add(new_chunk);
 
-      // Now, allocate from that chunk. That should work.
-      p = current_chunk()->allocate(word_size);
+      // Now, allocate from that chunk. That should work. Note that the resulting allocation
+      // is guaranteed to be aligned to arena alignment, since arena alignment cannot be larger
+      // than smallest chunk size, and chunk starts are aligned by their size (buddy allocation).
+      MetaWord* const p = current_chunk()->allocate(word_size);
       assert(p != nullptr, "Allocation from chunk failed.");
+      result = MetaBlock(p, word_size);
     } else {
       UL2(info, "failed to allocate new chunk for requested word size " SIZE_FORMAT ".", word_size);
     }
   }
 
-  if (p == nullptr) {
+  if (result.is_empty()) {
     InternalStats::inc_num_allocs_failed_limit();
   } else {
     DEBUG_ONLY(InternalStats::inc_num_allocs();)
-    _total_used_words_counter->increment_by(word_size);
+    _total_used_words_counter->increment_by(word_size + wastage.word_size());
   }
 
   SOMETIMES(verify();)
 
-  if (p == nullptr) {
-    UL(info, "allocation failed, returned null.");
-  } else {
+  if (result.is_nonempty()) {
     UL2(trace, "after allocation: %u chunk(s), current:" METACHUNK_FULL_FORMAT,
         _chunks.count(), METACHUNK_FULL_FORMAT_ARGS(current_chunk()));
-    UL2(trace, "returning " PTR_FORMAT ".", p2i(p));
   }
 
-  assert_is_aligned_metaspace_pointer(p);
+#ifdef ASSERT
+  if (wastage.is_nonempty()) {
+    // Wastage from arena allocations only occurs if either or both are true:
+    // - it is too small to hold the requested allocation words
+    // - it is misaligned
+    assert(!wastage.is_aligned_base(allocation_alignment_words()) ||
+           wastage.word_size() < word_size,
+           "Unexpected wastage: " METABLOCKFORMAT ", arena alignment: %zu, allocation word size: %zu",
+           METABLOCKFORMATARGS(wastage), allocation_alignment_words(), word_size);
+    wastage.verify();
+  }
+#endif // ASSERT
 
-  return p;
+  return result;
 }
 
 // Prematurely returns a metaspace allocation to the _block_freelists
 // because it is not needed anymore (requires CLD lock to be active).
-void MetaspaceArena::deallocate(MetaWord* p, size_t word_size) {
-  // At this point a current chunk must exist since we only deallocate if we did allocate before.
-  assert(current_chunk() != nullptr, "stray deallocation?");
-  assert(is_valid_area(p, word_size),
-         "Pointer range not part of this Arena and cannot be deallocated: (" PTR_FORMAT ".." PTR_FORMAT ").",
-         p2i(p), p2i(p + word_size));
-
-  UL2(trace, "deallocating " PTR_FORMAT ", word size: " SIZE_FORMAT ".",
-      p2i(p), word_size);
-
-  // Only blocks that had been allocated via MetaspaceArena::allocate(size) must be handed in
-  // to MetaspaceArena::deallocate(), and only with the same size that had been original used for allocation.
-  // Therefore the pointer must be aligned correctly, and size can be alignment-adjusted (the latter
-  // only matters on 32-bit):
-  assert_is_aligned_metaspace_pointer(p);
-  size_t raw_word_size = get_raw_word_size_for_requested_word_size(word_size);
-
-  add_allocation_to_fbl(p, raw_word_size);
-
+void MetaspaceArena::deallocate(MetaBlock block) {
+  DEBUG_ONLY(block.verify();)
+  // This only matters on 32-bit:
+  // Since we always align up allocations from arena, we align up here, too.
+#ifndef _LP64
+  MetaBlock raw_block(block.base(), get_raw_word_size_for_requested_word_size(block.word_size()));
+  add_allocation_to_fbl(raw_block);
+#else
+  add_allocation_to_fbl(block);
+#endif
+  UL2(trace, "added to fbl: " METABLOCKFORMAT ", (now: %d, " SIZE_FORMAT ").",
+      METABLOCKFORMATARGS(block), _fbl->count(), _fbl->total_size());
   SOMETIMES(verify();)
 }
 
 // Update statistics. This walks all in-use chunks.
 void MetaspaceArena::add_to_statistics(ArenaStats* out) const {
@@ -437,19 +506,20 @@
   for (const Fence* f = _first_fence; f != nullptr; f = f->next()) {
     f->verify();
   }
 }
 
-// Returns true if the area indicated by pointer and size have actually been allocated
-// from this arena.
-bool MetaspaceArena::is_valid_area(MetaWord* p, size_t word_size) const {
-  assert(p != nullptr && word_size > 0, "Sanity");
+// Returns true if the given block is contained in this arena
+// Returns true if the given block is contained in this arena
+bool MetaspaceArena::contains(MetaBlock bl) const {
+  DEBUG_ONLY(bl.verify();)
+  assert(bl.is_nonempty(), "Sanity");
   bool found = false;
   for (const Metachunk* c = _chunks.first(); c != nullptr && !found; c = c->next()) {
-    assert(c->is_valid_committed_pointer(p) ==
-           c->is_valid_committed_pointer(p + word_size - 1), "range intersects");
-    found = c->is_valid_committed_pointer(p);
+    assert(c->is_valid_committed_pointer(bl.base()) ==
+           c->is_valid_committed_pointer(bl.end() - 1), "range intersects");
+    found = c->is_valid_committed_pointer(bl.base());
   }
   return found;
 }
 
 #endif // ASSERT
diff a/src/hotspot/share/memory/metaspace/metaspaceArena.hpp b/src/hotspot/share/memory/metaspace/metaspaceArena.hpp
--- a/src/hotspot/share/memory/metaspace/metaspaceArena.hpp
+++ b/src/hotspot/share/memory/metaspace/metaspaceArena.hpp
@@ -27,23 +27,25 @@
 #define SHARE_MEMORY_METASPACE_METASPACEARENA_HPP
 
 #include "memory/allocation.hpp"
 #include "memory/metaspace.hpp"
 #include "memory/metaspace/counters.hpp"
+#include "memory/metaspace/metablock.hpp"
 #include "memory/metaspace/metachunkList.hpp"
 
 class outputStream;
 class Mutex;
 
 namespace metaspace {
 
 class ArenaGrowthPolicy;
+struct ArenaStats;
+class MetaspaceContext;
 class ChunkManager;
-class Metachunk;
 class FreeBlocks;
+class Metachunk;
 
-struct ArenaStats;
 
 // The MetaspaceArena is a growable metaspace memory pool belonging to a CLD;
 //  internally it consists of a list of metaspace chunks, of which the head chunk
 //  is the current chunk from which we allocate via pointer bump.
 //
@@ -72,14 +74,17 @@
 // MetaspaceArena also keeps a FreeBlocks structure to manage memory blocks which
 //  had been deallocated prematurely.
 //
 
 class MetaspaceArena : public CHeapObj<mtClass> {
+  friend class MetaspaceArenaTestFriend;
 
   // Please note that access to a metaspace arena may be shared
   // between threads and needs to be synchronized in CLMS.
 
+  const size_t _allocation_alignment_words;
+
   // Reference to the chunk manager to allocate chunks from.
   ChunkManager* const _chunk_manager;
 
   // Reference to the growth policy to use.
   const ArenaGrowthPolicy* const _growth_policy;
@@ -124,14 +129,14 @@
 
   ChunkManager* chunk_manager() const           { return _chunk_manager; }
 
   // free block list
   FreeBlocks* fbl() const                       { return _fbl; }
-  void add_allocation_to_fbl(MetaWord* p, size_t word_size);
+  void add_allocation_to_fbl(MetaBlock bl);
 
-  // Given a chunk, add its remaining free committed space to the free block list.
-  void salvage_chunk(Metachunk* c);
+  // Given a chunk, return the committed remainder of this chunk.
+  MetaBlock salvage_chunk(Metachunk* c);
 
   // Allocate a new chunk from the underlying chunk manager able to hold at least
   // requested word size.
   Metachunk* allocate_new_chunk(size_t requested_word_size);
 
@@ -142,36 +147,35 @@
   //  requested_word_size additional words.
   //
   // On success, true is returned, false otherwise.
   bool attempt_enlarge_current_chunk(size_t requested_word_size);
 
-  // Returns true if the area indicated by pointer and size have actually been allocated
-  // from this arena.
-  DEBUG_ONLY(bool is_valid_area(MetaWord* p, size_t word_size) const;)
-
   // Allocate from the arena proper, once dictionary allocations and fencing are sorted out.
-  MetaWord* allocate_inner(size_t word_size);
+  MetaBlock allocate_inner(size_t word_size, MetaBlock& wastage);
 
 public:
 
-  MetaspaceArena(ChunkManager* chunk_manager, const ArenaGrowthPolicy* growth_policy,
-                 SizeAtomicCounter* total_used_words_counter,
+  MetaspaceArena(MetaspaceContext* context,
+                 const ArenaGrowthPolicy* growth_policy,
+                 size_t allocation_alignment_words,
                  const char* name);
 
   ~MetaspaceArena();
 
+  size_t allocation_alignment_words() const { return _allocation_alignment_words; }
+  size_t allocation_alignment_bytes() const { return allocation_alignment_words() * BytesPerWord; }
+
   // Allocate memory from Metaspace.
-  // 1) Attempt to allocate from the dictionary of deallocated blocks.
-  // 2) Attempt to allocate from the current chunk.
-  // 3) Attempt to enlarge the current chunk in place if it is too small.
-  // 4) Attempt to get a new chunk and allocate from that chunk.
-  // At any point, if we hit a commit limit, we return null.
-  MetaWord* allocate(size_t word_size);
+  // On success, returns non-empty block of the specified word size, and
+  // possibly a wastage block that is the result of alignment operations.
+  // On failure, returns an empty block. Failure may happen if we hit a
+  // commit limit.
+  MetaBlock allocate(size_t word_size, MetaBlock& wastage);
 
   // Prematurely returns a metaspace allocation to the _block_freelists because it is not
   // needed anymore.
-  void deallocate(MetaWord* p, size_t word_size);
+  void deallocate(MetaBlock bl);
 
   // Update statistics. This walks all in-use chunks.
   void add_to_statistics(ArenaStats* out) const;
 
   // Convenience method to get the most important usage statistics.
@@ -181,10 +185,12 @@
   DEBUG_ONLY(void verify() const;)
   DEBUG_ONLY(void verify_allocation_guards() const;)
 
   void print_on(outputStream* st) const;
 
+  // Returns true if the given block is contained in this arena
+  DEBUG_ONLY(bool contains(MetaBlock bl) const;)
 };
 
 } // namespace metaspace
 
 #endif // SHARE_MEMORY_METASPACE_METASPACEARENA_HPP
diff a/src/hotspot/share/memory/metaspace/metaspaceCommon.hpp b/src/hotspot/share/memory/metaspace/metaspaceCommon.hpp
--- a/src/hotspot/share/memory/metaspace/metaspaceCommon.hpp
+++ b/src/hotspot/share/memory/metaspace/metaspaceCommon.hpp
@@ -40,17 +40,15 @@
 
 // Metaspace allocations have to be aligned such that 64-bit values are aligned
 //  correctly. We currently don't hold members with a larger alignment requirement
 //  than 64-bit inside MetaData, so 8-byte alignment is enough.
 //
-// Klass* structures need to be aligned to KlassAlignmentInBytes, but since that is
-// 64-bit, we don't need special handling for allocating Klass*.
+// Klass* structures need to be aligned to Klass* alignment,
 //
 // On 64-bit platforms, we align to word size; on 32-bit, we align to two words.
 
 static const size_t AllocationAlignmentByteSize = 8;
-STATIC_ASSERT(AllocationAlignmentByteSize == (size_t)KlassAlignmentInBytes);
 
 static const size_t AllocationAlignmentWordSize = AllocationAlignmentByteSize / BytesPerWord;
 
 // Returns the raw word size allocated for a given net allocation. This only matters on 32-bit, where
 // allocations have to be 64-bit aligned too and therefore must be 2-word-aligned.
diff a/src/hotspot/share/memory/metaspace/metaspaceContext.cpp b/src/hotspot/share/memory/metaspace/metaspaceContext.cpp
--- a/src/hotspot/share/memory/metaspace/metaspaceContext.cpp
+++ b/src/hotspot/share/memory/metaspace/metaspaceContext.cpp
@@ -73,10 +73,22 @@
 void MetaspaceContext::print_on(outputStream* st) const {
   _vslist->print_on(st);
   _cm->print_on(st);
 }
 
+size_t MetaspaceContext::used_words() const {
+  return _used_words_counter.get();
+}
+
+size_t MetaspaceContext::committed_words() const {
+  return _vslist->committed_words();
+}
+
+size_t MetaspaceContext::reserved_words() const {
+  return _vslist->reserved_words();
+}
+
 #ifdef ASSERT
 void MetaspaceContext::verify() const {
   _vslist->verify();
   _cm->verify();
 }
diff a/src/hotspot/share/memory/metaspace/metaspaceContext.hpp b/src/hotspot/share/memory/metaspace/metaspaceContext.hpp
--- a/src/hotspot/share/memory/metaspace/metaspaceContext.hpp
+++ b/src/hotspot/share/memory/metaspace/metaspaceContext.hpp
@@ -25,10 +25,11 @@
 
 #ifndef SHARE_MEMORY_METASPACE_METASPACECONTEXT_HPP
 #define SHARE_MEMORY_METASPACE_METASPACECONTEXT_HPP
 
 #include "memory/allocation.hpp"
+#include "memory/metaspace/counters.hpp"
 #include "memory/virtualspace.hpp"
 #include "utilities/debug.hpp"
 
 class outputStream;
 
@@ -59,10 +60,11 @@
 class MetaspaceContext : public CHeapObj<mtMetaspace> {
 
   const char* const _name;
   VirtualSpaceList* const _vslist;
   ChunkManager* const _cm;
+  SizeAtomicCounter _used_words_counter;
 
   MetaspaceContext(const char* name, VirtualSpaceList* vslist, ChunkManager* cm) :
     _name(name),
     _vslist(vslist),
     _cm(cm)
@@ -76,12 +78,13 @@
   // Destroys the context: deletes chunkmanager and virtualspacelist.
   // If this is a non-expandable context over an existing space, that space remains
   // untouched, otherwise all memory is unmapped.
   ~MetaspaceContext();
 
-  VirtualSpaceList* vslist() { return _vslist; }
-  ChunkManager* cm() { return _cm; }
+  VirtualSpaceList* vslist()                    { return _vslist; }
+  ChunkManager* cm()                            { return _cm; }
+  SizeAtomicCounter* used_words_counter()       { return &_used_words_counter; }
 
   // Create a new, empty, expandable metaspace context.
   static MetaspaceContext* create_expandable_context(const char* name, CommitLimiter* commit_limiter);
 
   // Create a new, empty, non-expandable metaspace context atop of an externally provided space.
@@ -101,10 +104,13 @@
 
   // Returns pointer to the global class space context, if compressed class space is active,
   // null otherwise.
   static MetaspaceContext* context_class()        { return _class_space_context; }
 
+  size_t used_words() const;
+  size_t committed_words() const;
+  size_t reserved_words() const;
 };
 
 } // end namespace
 
 #endif // SHARE_MEMORY_METASPACE_METASPACECONTEXT_HPP
diff a/src/hotspot/share/memory/metaspace/metaspaceReporter.cpp b/src/hotspot/share/memory/metaspace/metaspaceReporter.cpp
--- a/src/hotspot/share/memory/metaspace/metaspaceReporter.cpp
+++ b/src/hotspot/share/memory/metaspace/metaspaceReporter.cpp
@@ -37,10 +37,11 @@
 #include "memory/metaspace/metaspaceStatistics.hpp"
 #include "memory/metaspace/printCLDMetaspaceInfoClosure.hpp"
 #include "memory/metaspace/runningCounters.hpp"
 #include "memory/metaspace/virtualSpaceList.hpp"
 #include "memory/metaspaceUtils.hpp"
+#include "oops/compressedKlass.hpp"
 #include "runtime/os.hpp"
 
 namespace metaspace {
 
 static const char* describe_spacetype(Metaspace::MetaspaceType st) {
@@ -116,10 +117,13 @@
   out->print("Current GC threshold: ");
   print_human_readable_size(out, MetaspaceGC::capacity_until_GC(), scale);
   out->cr();
   out->print_cr("CDS: %s", (UseSharedSpaces ? "on" : (CDSConfig::is_dumping_static_archive() ? "dump" : "off")));
   Settings::print_on(out);
+#ifdef _LP64
+  CompressedKlassPointers::print_mode(out);
+#endif
 }
 
 // This will print out a basic metaspace usage report but
 // unlike print_report() is guaranteed not to lock or to walk the CLDG.
 void MetaspaceReporter::print_basic_report(outputStream* out, size_t scale) {
@@ -323,11 +327,11 @@
   out->cr();
   out->print("Waste (unused committed space):");
   // For all wastages, print percentages from total. As total use the total size of memory committed for metaspace.
   const size_t committed_words = RunningCounters::committed_words();
 
-  out->print("(percentages refer to total committed size ");
+  out->print(" (percentages refer to total committed size ");
   print_scaled_words(out, committed_words, scale);
   out->print_cr("):");
 
   // Print waste for in-use chunks.
   InUseChunkStats ucs_nonclass = cl._stats_total._arena_stats_nonclass.totals();
diff a/src/hotspot/share/memory/metaspace/metaspaceStatistics.cpp b/src/hotspot/share/memory/metaspace/metaspaceStatistics.cpp
--- a/src/hotspot/share/memory/metaspace/metaspaceStatistics.cpp
+++ b/src/hotspot/share/memory/metaspace/metaspaceStatistics.cpp
@@ -184,20 +184,16 @@
     print_scaled_words(st, _free_blocks_word_size, scale);
   }
 }
 
 #ifdef ASSERT
-
 void ArenaStats::verify() const {
   size_t total_used = 0;
   for (chunklevel_t l = chunklevel::LOWEST_CHUNK_LEVEL; l <= chunklevel::HIGHEST_CHUNK_LEVEL; l++) {
     _stats[l].verify();
     total_used += _stats[l]._used_words;
   }
-  // Deallocated allocations still count as used
-  assert(total_used >= _free_blocks_word_size,
-         "Sanity");
 }
 #endif
 
 // Returns total arena statistics for both class and non-class metaspace
 ArenaStats ClmsStats::totals() const {
diff a/src/hotspot/share/memory/metaspace/runningCounters.cpp b/src/hotspot/share/memory/metaspace/runningCounters.cpp
--- a/src/hotspot/share/memory/metaspace/runningCounters.cpp
+++ b/src/hotspot/share/memory/metaspace/runningCounters.cpp
@@ -23,19 +23,16 @@
  *
  */
 
 #include "precompiled.hpp"
 #include "memory/metaspace/chunkManager.hpp"
-#include "memory/metaspace/counters.hpp"
+#include "memory/metaspace/metaspaceContext.hpp"
 #include "memory/metaspace/runningCounters.hpp"
 #include "memory/metaspace/virtualSpaceList.hpp"
 
 namespace metaspace {
 
-SizeAtomicCounter RunningCounters::_used_class_counter;
-SizeAtomicCounter RunningCounters::_used_nonclass_counter;
-
 // Return reserved size, in words, for Metaspace
 size_t RunningCounters::reserved_words() {
   return reserved_words_class() + reserved_words_nonclass();
 }
 
@@ -68,15 +65,16 @@
 size_t RunningCounters::used_words() {
   return used_words_class() + used_words_nonclass();
 }
 
 size_t RunningCounters::used_words_class() {
-  return _used_class_counter.get();
+  const MetaspaceContext* context = MetaspaceContext::context_class();
+  return context != nullptr ? context->used_words() : 0;
 }
 
 size_t RunningCounters::used_words_nonclass() {
-  return _used_nonclass_counter.get();
+  return MetaspaceContext::context_nonclass()->used_words();
 }
 
 // ---- free chunks -----
 
 // Returns size, in words, of all chunks in all freelists.
diff a/src/hotspot/share/memory/metaspace/runningCounters.hpp b/src/hotspot/share/memory/metaspace/runningCounters.hpp
--- a/src/hotspot/share/memory/metaspace/runningCounters.hpp
+++ b/src/hotspot/share/memory/metaspace/runningCounters.hpp
@@ -25,21 +25,15 @@
 
 #ifndef SHARE_MEMORY_METASPACE_RUNNINGCOUNTERS_HPP
 #define SHARE_MEMORY_METASPACE_RUNNINGCOUNTERS_HPP
 
 #include "memory/allStatic.hpp"
-#include "memory/metaspace/counters.hpp"
 
 namespace metaspace {
 
 // This class is a convenience interface for accessing global metaspace counters.
-class RunningCounters : public AllStatic {
-
-  static SizeAtomicCounter _used_class_counter;
-  static SizeAtomicCounter _used_nonclass_counter;
-
-public:
+struct RunningCounters : public AllStatic {
 
   // ---- virtual memory -----
 
   // Return reserved size, in words, for Metaspace
   static size_t reserved_words();
@@ -63,14 +57,10 @@
   // Returns size, in words, of all chunks in all freelists.
   static size_t free_chunks_words();
   static size_t free_chunks_words_class();
   static size_t free_chunks_words_nonclass();
 
-  // Direct access to the counters.
-  static SizeAtomicCounter* used_nonclass_counter()     { return &_used_nonclass_counter; }
-  static SizeAtomicCounter* used_class_counter()        { return &_used_class_counter; }
-
 };
 
 } // namespace metaspace
 
 #endif // SHARE_MEMORY_METASPACE_RUNNINGCOUNTERS_HPP
diff a/src/hotspot/share/memory/metaspace/testHelpers.cpp b/src/hotspot/share/memory/metaspace/testHelpers.cpp
--- a/src/hotspot/share/memory/metaspace/testHelpers.cpp
+++ b/src/hotspot/share/memory/metaspace/testHelpers.cpp
@@ -52,27 +52,31 @@
   delete _lock;
 }
 
 MetaWord* MetaspaceTestArena::allocate(size_t word_size) {
   MutexLocker fcl(_lock, Mutex::_no_safepoint_check_flag);
-  return _arena->allocate(word_size);
+  MetaBlock result, wastage;
+  result = _arena->allocate(word_size, wastage);
+  if (wastage.is_nonempty()) {
+    _arena->deallocate(wastage);
+  }
+  return result.base();
 }
 
 void MetaspaceTestArena::deallocate(MetaWord* p, size_t word_size) {
   MutexLocker fcl(_lock, Mutex::_no_safepoint_check_flag);
-  return _arena->deallocate(p, word_size);
+  _arena->deallocate(MetaBlock(p, word_size));
 }
 
 ///// MetaspaceTestArea //////
 
 MetaspaceTestContext::MetaspaceTestContext(const char* name, size_t commit_limit, size_t reserve_limit) :
   _name(name),
   _reserve_limit(reserve_limit),
   _commit_limit(commit_limit),
   _context(nullptr),
   _commit_limiter(commit_limit == 0 ? max_uintx : commit_limit), // commit_limit == 0 -> no limit
-  _used_words_counter(),
   _rs()
 {
   assert(is_aligned(reserve_limit, Metaspace::reserve_alignment_words()), "reserve_limit (" SIZE_FORMAT ") "
                     "not aligned to metaspace reserve alignment (" SIZE_FORMAT ")",
                     reserve_limit, Metaspace::reserve_alignment_words());
@@ -101,11 +105,11 @@
   const ArenaGrowthPolicy* growth_policy = ArenaGrowthPolicy::policy_for_space_type(type, false);
   Mutex* lock = new Mutex(Monitor::nosafepoint, "MetaspaceTestArea_lock");
   MetaspaceArena* arena = nullptr;
   {
     MutexLocker ml(lock,  Mutex::_no_safepoint_check_flag);
-    arena = new MetaspaceArena(_context->cm(), growth_policy, &_used_words_counter, _name);
+    arena = new MetaspaceArena(_context, growth_policy, Metaspace::min_allocation_alignment_words, _name);
   }
   return new MetaspaceTestArena(lock, arena);
 }
 
 void MetaspaceTestContext::purge_area() {
@@ -122,7 +126,20 @@
 
 void MetaspaceTestContext::print_on(outputStream* st) const {
   _context->print_on(st);
 }
 
+size_t MetaspaceTestContext::used_words() const {
+  return _context->used_words_counter()->get();
+}
+
+size_t MetaspaceTestContext::committed_words() const {
+  assert(_commit_limiter.committed_words() == _context->committed_words(), "Sanity");
+  return _context->committed_words();
+}
+
+size_t MetaspaceTestContext::reserved_words() const {
+  return _context->reserved_words();
+}
+
 } // namespace metaspace
 
diff a/src/hotspot/share/memory/metaspace/testHelpers.hpp b/src/hotspot/share/memory/metaspace/testHelpers.hpp
--- a/src/hotspot/share/memory/metaspace/testHelpers.hpp
+++ b/src/hotspot/share/memory/metaspace/testHelpers.hpp
@@ -73,11 +73,10 @@
   const size_t _reserve_limit;
   const size_t _commit_limit;
 
   MetaspaceContext* _context;
   CommitLimiter _commit_limiter;
-  SizeAtomicCounter _used_words_counter;
 
   // For non-expandable contexts we keep track of the space
   // and delete it at destruction time.
   ReservedSpace _rs;
 
@@ -96,19 +95,20 @@
 
   // Accessors
   const CommitLimiter& commit_limiter() const { return _commit_limiter; }
   const VirtualSpaceList& vslist() const      { return *(_context->vslist()); }
   ChunkManager& cm()                          { return *(_context->cm()); }
+  MetaspaceContext* context() const           { return _context; }
 
   // Returns reserve- and commit limit we run the test with (in the real world,
   // these would be equivalent to CompressedClassSpaceSize resp MaxMetaspaceSize)
   size_t reserve_limit() const    { return _reserve_limit == 0 ? max_uintx : 0; }
   size_t commit_limit() const     { return _commit_limit == 0 ? max_uintx : 0; }
 
-  // Convenience function to retrieve total committed/used words
-  size_t used_words() const       { return _used_words_counter.get(); }
-  size_t committed_words() const  { return _commit_limiter.committed_words(); }
+  size_t used_words() const;
+  size_t committed_words() const;
+  size_t reserved_words() const;
 
   DEBUG_ONLY(void verify() const;)
 
   void print_on(outputStream* st) const;
 
diff a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp
--- a/src/hotspot/share/memory/universe.cpp
+++ b/src/hotspot/share/memory/universe.cpp
@@ -378,12 +378,17 @@
 void Universe::genesis(TRAPS) {
   ResourceMark rm(THREAD);
   HandleMark   hm(THREAD);
 
   // Explicit null checks are needed if these offsets are not smaller than the page size
-  assert(oopDesc::klass_offset_in_bytes() < static_cast<intptr_t>(os::vm_page_size()),
-         "Klass offset is expected to be less than the page size");
+  if (UseCompactObjectHeaders) {
+    assert(oopDesc::mark_offset_in_bytes() < static_cast<intptr_t>(os::vm_page_size()),
+           "Mark offset is expected to be less than the page size");
+  } else {
+    assert(oopDesc::klass_offset_in_bytes() < static_cast<intptr_t>(os::vm_page_size()),
+           "Klass offset is expected to be less than the page size");
+  }
   assert(arrayOopDesc::length_offset_in_bytes() < static_cast<intptr_t>(os::vm_page_size()),
          "Array length offset is expected to be less than the page size");
 
   { AutoModifyRestore<bool> temporarily(_bootstrapping, true);
 
diff a/src/hotspot/share/oops/arrayOop.hpp b/src/hotspot/share/oops/arrayOop.hpp
--- a/src/hotspot/share/oops/arrayOop.hpp
+++ b/src/hotspot/share/oops/arrayOop.hpp
@@ -80,12 +80,17 @@
 
   // The _length field is not declared in C++.  It is allocated after the
   // declared nonstatic fields in arrayOopDesc if not compressed, otherwise
   // it occupies the second half of the _klass field in oopDesc.
   static int length_offset_in_bytes() {
-    return UseCompressedClassPointers ? klass_gap_offset_in_bytes() :
-                               sizeof(arrayOopDesc);
+    if (UseCompactObjectHeaders) {
+      return oopDesc::base_offset_in_bytes();
+    } else if (UseCompressedClassPointers) {
+      return klass_gap_offset_in_bytes();
+    } else {
+      return sizeof(arrayOopDesc);
+    }
   }
 
   // Returns the offset of the first element.
   static int base_offset_in_bytes(BasicType type) {
     size_t hs = header_size_in_bytes();
diff a/src/hotspot/share/oops/compressedKlass.cpp b/src/hotspot/share/oops/compressedKlass.cpp
--- a/src/hotspot/share/oops/compressedKlass.cpp
+++ b/src/hotspot/share/oops/compressedKlass.cpp
@@ -23,104 +23,278 @@
  */
 
 #include "precompiled.hpp"
 #include "logging/log.hpp"
 #include "memory/metaspace.hpp"
-#include "oops/compressedKlass.hpp"
+#include "oops/klass.hpp"
+#include "oops/compressedKlass.inline.hpp"
 #include "runtime/globals.hpp"
+#include "runtime/java.hpp"
 #include "runtime/os.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/globalDefinitions.hpp"
 #include "utilities/ostream.hpp"
 
-address CompressedKlassPointers::_base = nullptr;
-int CompressedKlassPointers::_shift = 0;
-size_t CompressedKlassPointers::_range = 0;
+int CompressedKlassPointers::_tiny_cp = -1;
+int CompressedKlassPointers::_narrow_klass_pointer_bits = -1;
+int CompressedKlassPointers::_max_shift = -1;
+#ifdef ASSERT
+address CompressedKlassPointers::_klass_range_start = (address)-1;
+address CompressedKlassPointers::_klass_range_end = (address)-1;
+narrowKlass CompressedKlassPointers::_lowest_valid_narrow_klass_id = (narrowKlass)-1;
+narrowKlass CompressedKlassPointers::_highest_valid_narrow_klass_id = (narrowKlass)-1;
+#endif
+
+address CompressedKlassPointers::_base = (address)-1;
+int CompressedKlassPointers::_shift = -1;
+size_t CompressedKlassPointers::_range = (size_t)-1;
+
+// The maximum allowed length of the Klass range (the address range engulfing
+// CDS + class space) must not exceed 32-bit.
+// There is a theoretical limit of: must not exceed the size of a fully-shifted
+// narrow Klass pointer, which would be 32 + 3 = 35 bits in legacy mode;
+// however, keeping this size below 32-bit allows us to use decoding techniques
+// like 16-bit moves into the third quadrant on some architectures, and keeps
+// the code less complex. 32-bit have always been enough for CDS+class space.
+static constexpr size_t max_klass_range_size = 4 * G;
 
 #ifdef _LP64
 
-#ifdef ASSERT
-void CompressedKlassPointers::assert_is_valid_encoding(address addr, size_t len, address base, int shift) {
-  assert(base + nth_bit(32 + shift) >= addr + len, "Encoding (base=" PTR_FORMAT ", shift=%d) does not "
-         "fully cover the class range " PTR_FORMAT "-" PTR_FORMAT, p2i(base), shift, p2i(addr), p2i(addr + len));
+void CompressedKlassPointers::pre_initialize() {
+  if (UseCompactObjectHeaders) {
+    _tiny_cp = 1;
+    _narrow_klass_pointer_bits = narrow_klass_pointer_bits_tinycp;
+    _max_shift = max_shift_tinycp;
+  } else {
+    _tiny_cp = 0;
+    _narrow_klass_pointer_bits = narrow_klass_pointer_bits_legacy;
+    _max_shift = max_shift_legacy;
+  }
 }
+
+#ifdef ASSERT
+void CompressedKlassPointers::sanity_check_after_initialization() {
+  // In expectation of an assert, prepare condensed info to be printed with the assert.
+  char tmp[256];
+  os::snprintf(tmp, sizeof(tmp), PTR_FORMAT " " PTR_FORMAT " " PTR_FORMAT " %d " SIZE_FORMAT " %u %u",
+      p2i(_klass_range_start), p2i(_klass_range_end), p2i(_base), _shift, _range,
+      _lowest_valid_narrow_klass_id, _highest_valid_narrow_klass_id);
+#define ASSERT_HERE(cond) assert(cond, " (%s)", tmp);
+#define ASSERT_HERE_2(cond, msg) assert(cond, msg " (%s)", tmp);
+
+  // There is no technical reason preventing us from using other klass pointer bit lengths,
+  // but it should be a deliberate choice
+  ASSERT_HERE(_narrow_klass_pointer_bits == 32 || _narrow_klass_pointer_bits == 22);
+
+  // All values must be inited
+  ASSERT_HERE(_max_shift != -1);
+  ASSERT_HERE(_klass_range_start != (address)-1);
+  ASSERT_HERE(_klass_range_end != (address)-1);
+  ASSERT_HERE(_lowest_valid_narrow_klass_id != (narrowKlass)-1);
+  ASSERT_HERE(_base != (address)-1);
+  ASSERT_HERE(_shift != -1);
+  ASSERT_HERE(_range != (size_t)-1);
+
+  const size_t klab = klass_alignment_in_bytes();
+  ASSERT_HERE(klab >= sizeof(uint64_t) && klab <= K);
+
+  // Check that Klass range is fully engulfed in the encoding range
+  ASSERT_HERE(_klass_range_end > _klass_range_start);
+
+  const address encoding_end = _base + nth_bit(narrow_klass_pointer_bits() + _shift);
+  ASSERT_HERE_2(_klass_range_start >= _base && _klass_range_end <= encoding_end,
+                "Resulting encoding range does not fully cover the class range");
+
+  // Check that Klass range is aligned to Klass alignment. That should never be an issue since we mmap the
+  // relevant regions and klass alignment - tied to smallest metachunk size of 1K - will always be smaller
+  // than smallest page size of 4K.
+  ASSERT_HERE_2(is_aligned(_klass_range_start, klab) && is_aligned(_klass_range_end, klab),
+                "Klass range must start at a properly aligned address");
+
+  // Check that lowest and highest possible narrowKlass values make sense
+  ASSERT_HERE_2(_lowest_valid_narrow_klass_id > 0, "Null is not a valid narrowKlass");
+  ASSERT_HERE(_highest_valid_narrow_klass_id > _lowest_valid_narrow_klass_id);
+
+  Klass* k1 = decode_not_null_without_asserts(_lowest_valid_narrow_klass_id, _base, _shift);
+  ASSERT_HERE_2((address)k1 == _klass_range_start + klab, "Not lowest");
+  narrowKlass nk1 = encode_not_null_without_asserts(k1, _base, _shift);
+  ASSERT_HERE_2(nk1 == _lowest_valid_narrow_klass_id, "not reversible");
+
+  Klass* k2 = decode_not_null_without_asserts(_highest_valid_narrow_klass_id, _base, _shift);
+  // _highest_valid_narrow_klass_id must be decoded to the highest theoretically possible
+  // valid Klass* position in range, if we assume minimal Klass size
+  ASSERT_HERE((address)k2 < _klass_range_end);
+  ASSERT_HERE_2(align_up(((address)k2 + sizeof(Klass)), klab) >= _klass_range_end, "Not highest");
+  narrowKlass nk2 = encode_not_null_without_asserts(k2, _base, _shift);
+  ASSERT_HERE_2(nk2 == _highest_valid_narrow_klass_id, "not reversible");
+
+#ifdef AARCH64
+  // On aarch64, we never expect a shift value > 0 in legacy mode
+  ASSERT_HERE_2(tiny_classpointer_mode() || _shift == 0, "Shift > 0 in legacy mode?");
 #endif
+#undef ASSERT_HERE
+#undef ASSERT_HERE_2
+}
+
+void CompressedKlassPointers::calc_lowest_highest_narrow_klass_id() {
+  // Given a Klass range, calculate lowest and highest narrowKlass.
+  const size_t klab = klass_alignment_in_bytes();
+  // Note that 0 is not a valid narrowKlass, and Metaspace prevents us for that reason from allocating at
+  // the very start of class space. So the very first valid Klass position is start-of-range + klab.
+  _lowest_valid_narrow_klass_id =
+      (narrowKlass) (((uintptr_t)(_klass_range_start - _base) + klab) >> _shift);
+  address highest_possible_klass = align_down(_klass_range_end - sizeof(Klass), klab);
+  _highest_valid_narrow_klass_id = (narrowKlass) ((uintptr_t)(highest_possible_klass - _base) >> _shift);
+}
+#endif // ASSERT
 
 // Given a klass range [addr, addr+len) and a given encoding scheme, assert that this scheme covers the range, then
 // set this encoding scheme. Used by CDS at runtime to re-instate the scheme used to pre-compute klass ids for
 // archived heap objects.
 void CompressedKlassPointers::initialize_for_given_encoding(address addr, size_t len, address requested_base, int requested_shift) {
   address const end = addr + len;
 
-  const int narrow_klasspointer_bits = sizeof(narrowKlass) * 8;
-  const size_t encoding_range_size = nth_bit(narrow_klasspointer_bits + requested_shift);
+  if (len > max_klass_range_size) {
+    // Class space size is limited to 3G. This can theoretically happen if the CDS archive
+    // is larger than 1G and class space size is set to the maximum possible 3G.
+    vm_exit_during_initialization("Sum of CDS archive size and class space size exceed 4 GB");
+  }
+
+  const size_t encoding_range_size = nth_bit(narrow_klass_pointer_bits() + requested_shift);
   address encoding_range_end = requested_base + encoding_range_size;
 
   // Note: it would be technically valid for the encoding base to precede the start of the Klass range. But we only call
   // this function from CDS, and therefore know this to be true.
   assert(requested_base == addr, "Invalid requested base");
-  assert(encoding_range_end >= end, "Encoding does not cover the full Klass range");
 
   _base = requested_base;
   _shift = requested_shift;
   _range = encoding_range_size;
 
-  DEBUG_ONLY(assert_is_valid_encoding(addr, len, _base, _shift);)
+#ifdef ASSERT
+  _klass_range_start = addr;
+  _klass_range_end = addr + len;
+  calc_lowest_highest_narrow_klass_id();
+  sanity_check_after_initialization();
+#endif
+
+  DEBUG_ONLY(sanity_check_after_initialization();)
 }
 
 char* CompressedKlassPointers::reserve_address_space_X(uintptr_t from, uintptr_t to, size_t size, size_t alignment, bool aslr) {
   alignment = MAX2(Metaspace::reserve_alignment(), alignment);
   return os::attempt_reserve_memory_between((char*)from, (char*)to, size, alignment, aslr);
 }
 
 char* CompressedKlassPointers::reserve_address_space_for_unscaled_encoding(size_t size, bool aslr) {
-  return reserve_address_space_X(0, nth_bit(32), size, Metaspace::reserve_alignment(), aslr);
+  const size_t unscaled_max = nth_bit(narrow_klass_pointer_bits());
+  return reserve_address_space_X(0, unscaled_max, size, Metaspace::reserve_alignment(), aslr);
 }
 
 char* CompressedKlassPointers::reserve_address_space_for_zerobased_encoding(size_t size, bool aslr) {
-  return reserve_address_space_X(nth_bit(32), nth_bit(32 + LogKlassAlignmentInBytes), size, Metaspace::reserve_alignment(), aslr);
+  const size_t unscaled_max = nth_bit(narrow_klass_pointer_bits());
+  const size_t zerobased_max = nth_bit(narrow_klass_pointer_bits() + max_shift());
+  return reserve_address_space_X(unscaled_max, zerobased_max, size, Metaspace::reserve_alignment(), aslr);
 }
 
 char* CompressedKlassPointers::reserve_address_space_for_16bit_move(size_t size, bool aslr) {
   return reserve_address_space_X(nth_bit(32), nth_bit(48), size, nth_bit(32), aslr);
 }
 
-#if !defined(AARCH64) || defined(ZERO)
-// On aarch64 we have an own version; all other platforms use the default version
 void CompressedKlassPointers::initialize(address addr, size_t len) {
-  // The default version of this code tries, in order of preference:
-  // -unscaled    (base=0 shift=0)
-  // -zero-based  (base=0 shift>0)
-  // -nonzero-base (base>0 shift=0)
-  // Note that base>0 shift>0 should never be needed, since the klass range will
-  // never exceed 4GB.
-  constexpr uintptr_t unscaled_max = nth_bit(32);
-  assert(len <= unscaled_max, "Klass range larger than 32 bits?");
 
-  constexpr uintptr_t zerobased_max = nth_bit(32 + LogKlassAlignmentInBytes);
+  if (len > max_klass_range_size) {
+    // Class space size is limited to 3G. This can theoretically happen if the CDS archive
+    // is larger than 1G and class space size is set to the maximum possible 3G.
+    vm_exit_during_initialization("Sum of CDS archive size and class space size exceed 4 GB");
+  }
+
+  // Give CPU a shot at a specialized init sequence
+#ifndef ZERO
+  if (pd_initialize(addr, len)) {
+    return;
+  }
+#endif
+
+  if (tiny_classpointer_mode()) {
+
+    // This handles the case that we - experimentally - reduce the number of
+    // class pointer bits further, such that (shift + num bits) < 32.
+    assert(len <= (size_t)nth_bit(narrow_klass_pointer_bits() + max_shift()),
+           "klass range size exceeds encoding");
+
+    // In tiny classpointer mode, we don't attempt for zero-based mode.
+    // Instead, we set the base to the start of the klass range and then try
+    // for the smallest shift possible that still covers the whole range.
+    // The reason is that we want to avoid, if possible, shifts larger than
+    // a cacheline size.
+    _base = addr;
+    _range = len;
+
+    if (TinyClassPointerShift != 0) {
+      _shift = TinyClassPointerShift;
+    } else {
+      constexpr int log_cacheline = 6;
+      int s = max_shift();
+      while (s > log_cacheline && ((size_t)nth_bit(narrow_klass_pointer_bits() + s - 1) > len)) {
+        s--;
+      }
+      _shift = s;
+    }
 
-  address const end = addr + len;
-  if (end <= (address)unscaled_max) {
-    _base = nullptr;
-    _shift = 0;
   } else {
-    if (end <= (address)zerobased_max) {
+
+    // In legacy mode, we try, in order of preference:
+    // -unscaled    (base=0 shift=0)
+    // -zero-based  (base=0 shift>0)
+    // -nonzero-base (base>0 shift=0)
+    // Note that base>0 shift>0 should never be needed, since the klass range will
+    // never exceed 4GB.
+    const uintptr_t unscaled_max = nth_bit(narrow_klass_pointer_bits());
+    const uintptr_t zerobased_max = nth_bit(narrow_klass_pointer_bits() + max_shift());
+
+    address const end = addr + len;
+    if (end <= (address)unscaled_max) {
       _base = nullptr;
-      _shift = LogKlassAlignmentInBytes;
-    } else {
-      _base = addr;
       _shift = 0;
+    } else {
+      if (end <= (address)zerobased_max) {
+        _base = nullptr;
+        _shift = max_shift();
+      } else {
+        _base = addr;
+        _shift = 0;
+      }
     }
+    _range = end - _base;
+
   }
-  _range = end - _base;
 
-  DEBUG_ONLY(assert_is_valid_encoding(addr, len, _base, _shift);)
+#ifdef ASSERT
+  _klass_range_start = addr;
+  _klass_range_end = addr + len;
+  calc_lowest_highest_narrow_klass_id();
+  sanity_check_after_initialization();
+#endif
 }
-#endif // !AARCH64 || ZERO
 
 void CompressedKlassPointers::print_mode(outputStream* st) {
+  st->print_cr("UseCompressedClassPointers %d, UseCompactObjectHeaders %d, "
+               "narrow klass pointer bits %d, max shift %d",
+               UseCompressedClassPointers, UseCompactObjectHeaders,
+               _narrow_klass_pointer_bits, _max_shift);
+  if (_base == (address)-1) {
+    st->print_cr("Narrow klass encoding not initialized");
+    return;
+  }
   st->print_cr("Narrow klass base: " PTR_FORMAT ", Narrow klass shift: %d, "
                "Narrow klass range: " SIZE_FORMAT_X, p2i(base()), shift(),
                range());
+#ifdef ASSERT
+  st->print_cr("Klass range: [" PTR_FORMAT "," PTR_FORMAT ")",
+               p2i(_klass_range_start), p2i(_klass_range_end));
+  st->print_cr("Lowest valid nklass id: %u Highest valid nklass id: %u",
+               _lowest_valid_narrow_klass_id, _highest_valid_narrow_klass_id);
+#endif
 }
 
 #endif // _LP64
diff a/src/hotspot/share/oops/compressedKlass.hpp b/src/hotspot/share/oops/compressedKlass.hpp
--- a/src/hotspot/share/oops/compressedKlass.hpp
+++ b/src/hotspot/share/oops/compressedKlass.hpp
@@ -32,23 +32,34 @@
 class Klass;
 
 // If compressed klass pointers then use narrowKlass.
 typedef juint  narrowKlass;
 
-const int LogKlassAlignmentInBytes = 3;
-const int KlassAlignmentInBytes    = 1 << LogKlassAlignmentInBytes;
-
-// Maximal size of compressed class space. Above this limit compression is not possible.
-// Also upper bound for placement of zero based class space. (Class space is further limited
-// to be < 3G, see arguments.cpp.)
-const  uint64_t KlassEncodingMetaspaceMax = (uint64_t(max_juint) + 1) << LogKlassAlignmentInBytes;
-
 // For UseCompressedClassPointers.
 class CompressedKlassPointers : public AllStatic {
   friend class VMStructs;
   friend class ArchiveBuilder;
 
+  // Tiny-class-pointer mode
+  static int _tiny_cp; // -1, 0=true, 1=false
+
+  // We use a different narrow Klass pointer geometry depending on
+  // whether we run in standard mode or in compact-object-header-mode (Lilliput):
+  // In Lilliput, we use smaller-than-32-bit class pointers ("tiny classpointer mode")
+
+  // Narrow klass pointer bits for an unshifted narrow Klass pointer.
+  static constexpr int narrow_klass_pointer_bits_legacy = 32;
+  static constexpr int narrow_klass_pointer_bits_tinycp = 22;
+
+  static int _narrow_klass_pointer_bits;
+
+  // The maximum shift we can use for standard mode and for TinyCP mode
+  static constexpr int max_shift_legacy = 3;
+  static constexpr int max_shift_tinycp = 10;
+
+  static int _max_shift;
+
   static address _base;
   static int _shift;
 
   // Together with base, this defines the address range within which Klass
   //  structures will be located: [base, base+range). While the maximal
@@ -61,19 +72,69 @@
   static char* reserve_address_space_X(uintptr_t from, uintptr_t to, size_t size, size_t alignment, bool aslr);
   static char* reserve_address_space_for_unscaled_encoding(size_t size, bool aslr);
   static char* reserve_address_space_for_zerobased_encoding(size_t size, bool aslr);
   static char* reserve_address_space_for_16bit_move(size_t size, bool aslr);
 
-  DEBUG_ONLY(static void assert_is_valid_encoding(address addr, size_t len, address base, int shift);)
+  // Returns the highest address expressable with an unshifted narrow Klass pointer
+  inline static uintptr_t highest_unscaled_address();
+
+  static bool pd_initialize(address addr, size_t len);
+
+#ifdef ASSERT
+  // For sanity checks: Klass range
+  static address _klass_range_start;
+  static address _klass_range_end;
+  // For sanity checks: lowest, highest valid narrow klass ids != null
+  static narrowKlass _lowest_valid_narrow_klass_id;
+  static narrowKlass _highest_valid_narrow_klass_id;
+  static void calc_lowest_highest_narrow_klass_id();
+  static void sanity_check_after_initialization();
+#endif // ASSERT
+
+  template <typename T>
+  static inline void check_init(T var) {
+    assert(var != (T)-1, "Not yet initialized");
+  }
 
   static inline Klass* decode_not_null_without_asserts(narrowKlass v, address base, int shift);
   static inline Klass* decode_not_null(narrowKlass v, address base, int shift);
 
   static inline narrowKlass encode_not_null(Klass* v, address base, int shift);
 
 public:
 
+  // Initialization sequence:
+  // 1) Parse arguments. The following arguments take a role:
+  //      - UseCompressedClassPointers
+  //      - UseCompactObjectHeaders
+  //      - Xshare on off dump
+  //      - CompressedClassSpaceSize
+  // 2) call pre_initialize(): depending on UseCompactObjectHeaders, defines the limits of narrow Klass pointer
+  //    geometry (how many bits, the max. possible shift)
+  // 3) .. from here on, narrow_klass_pointer_bits() and max_shift() can be used
+  // 4) call reserve_address_space_for_compressed_classes() either from CDS initialization or, if CDS is off,
+  //    from metaspace initialization. Reserves space for class space + CDS, attempts to reserve such that
+  //    we later can use a "good" encoding scheme. Reservation is highly CPU-specific.
+  // 5) Initialize the narrow Klass encoding scheme by determining encoding base and shift:
+  //   5a) if CDS=on: Calls initialize_for_given_encoding() with the reservation base from step (4) and the
+  //       CDS-intrinsic setting for shift; here, we don't have any freedom to deviate from the base.
+  //   5b) if CDS=off: Calls initialize() - here, we have more freedom and, if we want, can choose an encoding
+  //       base that differs from the reservation base from step (4). That allows us, e.g., to later use
+  //       zero-based encoding.
+  // 6) ... from now on, we can use base() and shift().
+
+  // Called right after argument parsing; defines narrow klass pointer geometry limits
+  static void pre_initialize();
+
+  static bool tiny_classpointer_mode()   { check_init(_tiny_cp); return (_tiny_cp == 1); }
+
+  // The number of bits a narrow Klass pointer has;
+  static int narrow_klass_pointer_bits() { check_init(_narrow_klass_pointer_bits); return _narrow_klass_pointer_bits; }
+
+  // The maximum possible shift; the actual shift employed later can be smaller (see initialize())
+  static int max_shift()                 { check_init(_max_shift); return _max_shift; }
+
   // Reserve a range of memory that is to contain Klass strucutures which are referenced by narrow Klass IDs.
   // If optimize_for_zero_base is true, the implementation will attempt to reserve optimized for zero-based encoding.
   static char* reserve_address_space_for_compressed_classes(size_t size, bool aslr, bool optimize_for_zero_base);
 
   // Given a klass range [addr, addr+len) and a given encoding scheme, assert that this scheme covers the range, then
@@ -89,13 +150,20 @@
   //  structures outside this range).
   static void initialize(address addr, size_t len);
 
   static void     print_mode(outputStream* st);
 
-  static address  base()               { return  _base; }
-  static size_t   range()              { return  _range; }
-  static int      shift()              { return  _shift; }
+  // Can only be used after initialization
+  static address  base()             { check_init(_base); return  _base; }
+  static size_t   range()            { check_init(_range); return  _range; }
+  static int      shift()            { check_init(_shift); return  _shift; }
+
+  // Returns the alignment a Klass* is guaranteed to have.
+  // Note: *Not* the same as 1 << shift ! Klass are always guaranteed to be at least 64-bit aligned,
+  // so this will return 8 even if shift is 0.
+  static int klass_alignment_in_bytes() { return nth_bit(MAX2(3, _shift)); }
+  static int klass_alignment_in_words() { return klass_alignment_in_bytes() / BytesPerWord; }
 
   static bool is_null(Klass* v)      { return v == nullptr; }
   static bool is_null(narrowKlass v) { return v == 0; }
 
   // Versions without asserts
@@ -103,10 +171,21 @@
   static inline Klass* decode_without_asserts(narrowKlass v);
 
   static inline Klass* decode_not_null(narrowKlass v);
   static inline Klass* decode(narrowKlass v);
 
+  static inline narrowKlass encode_not_null_without_asserts(Klass* k, address narrow_base, int shift);
   static inline narrowKlass encode_not_null(Klass* v);
   static inline narrowKlass encode(Klass* v);
+
+#ifdef ASSERT
+  // Given a Klass* k and an encoding (base, shift), check that k can be encoded
+  inline static void check_valid_klass(const Klass* k, address base, int shift);
+  // Given a Klass* k, check that k can be encoded with the current encoding
+  inline static void check_valid_klass(const Klass* k);
+  // Given a narrow Klass ID, check that it is valid according to current encoding
+  inline static void check_valid_narrow_klass_id(narrowKlass nk);
+#endif
+
 };
 
 #endif // SHARE_OOPS_COMPRESSEDKLASS_HPP
diff a/src/hotspot/share/oops/compressedKlass.inline.hpp b/src/hotspot/share/oops/compressedKlass.inline.hpp
--- a/src/hotspot/share/oops/compressedKlass.inline.hpp
+++ b/src/hotspot/share/oops/compressedKlass.inline.hpp
@@ -30,34 +30,31 @@
 #include "memory/universe.hpp"
 #include "oops/oop.hpp"
 #include "utilities/align.hpp"
 #include "utilities/globalDefinitions.hpp"
 
-static inline bool check_alignment(Klass* v) {
-  return (intptr_t)v % KlassAlignmentInBytes == 0;
-}
-
-inline Klass* CompressedKlassPointers::decode_not_null_without_asserts(narrowKlass v, address narrow_base, int shift) {
-  return (Klass*)((uintptr_t)narrow_base +((uintptr_t)v << shift));
+inline Klass* CompressedKlassPointers::decode_not_null_without_asserts(narrowKlass v, address narrow_base_base, int shift) {
+  return (Klass*)((uintptr_t)narrow_base_base +((uintptr_t)v << shift));
 }
 
 inline Klass* CompressedKlassPointers::decode_not_null(narrowKlass v, address narrow_base, int shift) {
   assert(!is_null(v), "narrow klass value can never be zero");
   Klass* result = decode_not_null_without_asserts(v, narrow_base, shift);
-  assert(check_alignment(result), "address not aligned: " PTR_FORMAT, p2i(result));
+  DEBUG_ONLY(check_valid_klass(result, narrow_base, shift));
   return result;
 }
 
+inline narrowKlass CompressedKlassPointers::encode_not_null_without_asserts(Klass* k, address narrow_base, int shift) {
+  return (narrowKlass)(pointer_delta(k, narrow_base, 1) >> shift);
+}
+
 inline narrowKlass CompressedKlassPointers::encode_not_null(Klass* v, address narrow_base, int shift) {
   assert(!is_null(v), "klass value can never be zero");
-  assert(check_alignment(v), "Address not aligned");
-  uint64_t pd = (uint64_t)(pointer_delta(v, narrow_base, 1));
-  assert(KlassEncodingMetaspaceMax > pd, "change encoding max if new encoding");
-  uint64_t result = pd >> shift;
-  assert((result & CONST64(0xffffffff00000000)) == 0, "narrow klass pointer overflow");
+  DEBUG_ONLY(check_valid_klass(v);)
+  narrowKlass result = encode_not_null_without_asserts(v, narrow_base, shift);
   assert(decode_not_null((narrowKlass)result, narrow_base, shift) == v, "reversibility");
-  return (narrowKlass)result;
+  return result;
 }
 
 inline Klass* CompressedKlassPointers::decode_not_null_without_asserts(narrowKlass v) {
   return decode_not_null_without_asserts(v, base(), shift());
 }
@@ -65,21 +62,55 @@
 inline Klass* CompressedKlassPointers::decode_without_asserts(narrowKlass v) {
   return is_null(v) ? nullptr : decode_not_null_without_asserts(v);
 }
 
 inline Klass* CompressedKlassPointers::decode_not_null(narrowKlass v) {
+  DEBUG_ONLY(check_valid_narrow_klass_id(v);)
   return decode_not_null(v, base(), shift());
 }
 
 inline Klass* CompressedKlassPointers::decode(narrowKlass v) {
   return is_null(v) ? nullptr : decode_not_null(v);
 }
 
 inline narrowKlass CompressedKlassPointers::encode_not_null(Klass* v) {
-  return encode_not_null(v, base(), shift());
+  narrowKlass nk = encode_not_null(v, base(), shift());
+  DEBUG_ONLY(check_valid_narrow_klass_id(nk);)
+  return nk;
 }
 
 inline narrowKlass CompressedKlassPointers::encode(Klass* v) {
   return is_null(v) ? (narrowKlass)0 : encode_not_null(v);
 }
 
+#ifdef ASSERT
+inline void CompressedKlassPointers::check_valid_klass(const Klass* k, address base, int shift) {
+  const int log_alignment = MAX2(3, shift); // always at least 64-bit aligned
+  assert(is_aligned(k, nth_bit(log_alignment)), "Klass (" PTR_FORMAT ") not properly aligned to %zu",
+         p2i(k), nth_bit(shift));
+  const address encoding_end = base + nth_bit(narrow_klass_pointer_bits() + shift);
+  assert((address)k >= base && (address)k < encoding_end,
+         "Klass (" PTR_FORMAT ") falls outside of the valid encoding range [" PTR_FORMAT "-" PTR_FORMAT ")",
+         p2i(k), p2i(base), p2i(encoding_end));
+}
+
+inline void CompressedKlassPointers::check_valid_klass(const Klass* k) {
+  assert(UseCompressedClassPointers, "Only call for +UseCCP");
+  check_valid_klass(k, base(), shift());
+  // Also assert that k falls into what we know is the valid Klass range. This is usually smaller
+  // than the encoding range (e.g. encoding range covers 4G, but we only have 1G class space and a
+  // tiny bit of CDS => 1.1G)
+  const address klassrange_end = base() + range();
+  assert((address)k < klassrange_end,
+      "Klass (" PTR_FORMAT ") falls outside of the valid klass range [" PTR_FORMAT "-" PTR_FORMAT ")",
+      p2i(k), p2i(base()), p2i(klassrange_end));
+}
+inline void CompressedKlassPointers::check_valid_narrow_klass_id(narrowKlass nk) {
+  assert(UseCompressedClassPointers, "Only call for +UseCCP");
+  const uint64_t nk_mask = ~right_n_bits(narrow_klass_pointer_bits());
+  assert(((uint64_t)nk & nk_mask) == 0, "narrow klass id bit spillover (%u)", nk);
+  assert(nk >= _lowest_valid_narrow_klass_id &&
+         nk <= _highest_valid_narrow_klass_id, "narrowKlass ID out of range (%u)", nk);
+}
+#endif // ASSERT
+
 #endif // SHARE_OOPS_COMPRESSEDKLASS_INLINE_HPP
diff a/src/hotspot/share/oops/instanceOop.hpp b/src/hotspot/share/oops/instanceOop.hpp
--- a/src/hotspot/share/oops/instanceOop.hpp
+++ b/src/hotspot/share/oops/instanceOop.hpp
@@ -31,19 +31,19 @@
 // An instanceOop is an instance of a Java Class
 // Evaluating "new HashTable()" will create an instanceOop.
 
 class instanceOopDesc : public oopDesc {
  public:
-  // aligned header size.
-  static int header_size() { return sizeof(instanceOopDesc)/HeapWordSize; }
-
   // If compressed, the offset of the fields of the instance may not be aligned.
   static int base_offset_in_bytes() {
-    return (UseCompressedClassPointers) ?
-            klass_gap_offset_in_bytes() :
-            sizeof(instanceOopDesc);
-
+    if (UseCompactObjectHeaders) {
+      return oopDesc::base_offset_in_bytes();
+    } else if (UseCompressedClassPointers) {
+      return klass_gap_offset_in_bytes();
+    } else {
+      return sizeof(instanceOopDesc);
+    }
   }
 };
 
 // See similar requirement for oopDesc.
 static_assert(std::is_trivially_default_constructible<instanceOopDesc>::value, "required");
diff a/src/hotspot/share/oops/klass.cpp b/src/hotspot/share/oops/klass.cpp
--- a/src/hotspot/share/oops/klass.cpp
+++ b/src/hotspot/share/oops/klass.cpp
@@ -189,19 +189,30 @@
 
 void* Klass::operator new(size_t size, ClassLoaderData* loader_data, size_t word_size, TRAPS) throw() {
   return Metaspace::allocate(loader_data, word_size, MetaspaceObj::ClassType, THREAD);
 }
 
+static markWord make_prototype(Klass* kls) {
+  markWord prototype = markWord::prototype();
+#ifdef _LP64
+  if (UseCompactObjectHeaders) {
+    prototype = prototype.set_klass(kls);
+  }
+#endif
+  return prototype;
+}
+
 Klass::Klass() : _kind(UnknownKlassKind) {
   assert(CDSConfig::is_dumping_static_archive() || UseSharedSpaces, "only for cds");
 }
 
 // "Normal" instantiation is preceded by a MetaspaceObj allocation
 // which zeros out memory - calloc equivalent.
 // The constructor is also used from CppVtableCloner,
 // which doesn't zero out the memory before calling the constructor.
 Klass::Klass(KlassKind kind) : _kind(kind),
+                           _prototype_header(make_prototype(this)),
                            _shared_class_path_index(-1) {
   CDS_ONLY(_shared_class_flags = 0;)
   CDS_JAVA_HEAP_ONLY(_archived_mirror_index = -1;)
   _primary_supers[0] = this;
   set_super_check_offset(in_bytes(primary_supers_offset()));
@@ -744,10 +755,14 @@
 
   if (WizardMode) {
      // print header
      obj->mark().print_on(st);
      st->cr();
+     if (UseCompactObjectHeaders) {
+       st->print(BULLET"prototype_header: " INTPTR_FORMAT, _prototype_header.value());
+       st->cr();
+     }
   }
 
   // print class
   st->print(BULLET"klass: ");
   obj->klass()->print_value_on(st);
diff a/src/hotspot/share/oops/klass.hpp b/src/hotspot/share/oops/klass.hpp
--- a/src/hotspot/share/oops/klass.hpp
+++ b/src/hotspot/share/oops/klass.hpp
@@ -163,10 +163,12 @@
                                 // have lots of itable dispatches (e.g., lambdas and streams).
                                 // Keep it away from the beginning of a Klass to avoid cacheline
                                 // contention that may happen when a nearby object is modified.
   AccessFlags _access_flags;    // Access flags. The class/interface distinction is stored here.
 
+  markWord _prototype_header;   // Used to initialize objects' header
+
   JFR_ONLY(DEFINE_TRACE_ID_FIELD;)
 
 private:
   // This is an index into FileMapHeader::_shared_path_table[], to
   // associate this class with the JAR file where it's loaded from during
@@ -673,10 +675,17 @@
   inline bool is_non_strong_hidden() const;
 
   bool is_cloneable() const;
   void set_is_cloneable();
 
+  markWord prototype_header() const {
+    assert(UseCompactObjectHeaders, "only use with compact object headers");
+    return _prototype_header;
+  }
+  inline void set_prototype_header(markWord header);
+  static ByteSize prototype_header_offset() { return in_ByteSize(offset_of(Klass, _prototype_header)); }
+
   JFR_ONLY(DEFINE_TRACE_ID_METHODS;)
 
   virtual void metaspace_pointers_do(MetaspaceClosure* iter);
   virtual MetaspaceObj::Type type() const { return ClassType; }
 
diff a/src/hotspot/share/oops/klass.inline.hpp b/src/hotspot/share/oops/klass.inline.hpp
--- a/src/hotspot/share/oops/klass.inline.hpp
+++ b/src/hotspot/share/oops/klass.inline.hpp
@@ -50,10 +50,15 @@
 // of this function must ensure that a safepoint doesn't happen while interpreting the return value.
 inline bool Klass::is_loader_alive() const {
   return class_loader_data()->is_alive();
 }
 
+inline void Klass::set_prototype_header(markWord header) {
+  assert(UseCompactObjectHeaders, "only with compact headers");
+  _prototype_header = header;
+}
+
 inline oop Klass::java_mirror() const {
   return _java_mirror.resolve();
 }
 
 inline oop Klass::java_mirror_no_keepalive() const {
diff a/src/hotspot/share/oops/markWord.cpp b/src/hotspot/share/oops/markWord.cpp
--- a/src/hotspot/share/oops/markWord.cpp
+++ b/src/hotspot/share/oops/markWord.cpp
@@ -22,14 +22,23 @@
  *
  */
 
 #include "precompiled.hpp"
 #include "oops/markWord.hpp"
+#include "runtime/basicLock.inline.hpp"
 #include "runtime/javaThread.hpp"
 #include "runtime/objectMonitor.inline.hpp"
 #include "utilities/ostream.hpp"
 
+#ifdef _LP64
+STATIC_ASSERT((markWord::klass_shadow_mask_inplace & markWord::klass_mask_in_place) == 0);
+STATIC_ASSERT((markWord::klass_load_shift + markWord::klass_shadow_bits) == markWord::klass_shift);
+STATIC_ASSERT(markWord::klass_shift + markWord::klass_bits == 64);
+// The hash (preceding nKlass) shall be a direct neighbor but not interleave
+STATIC_ASSERT(markWord::klass_shift == markWord::hash_bits_compact + markWord::hash_shift_compact);
+#endif
+
 markWord markWord::displaced_mark_helper() const {
   assert(has_displaced_mark_helper(), "check");
   if (has_monitor()) {
     // Has an inflated monitor. Must be checked before has_locker().
     ObjectMonitor* monitor = this->monitor();
@@ -65,11 +74,12 @@
   if (is_marked()) {  // last bits = 11
     st->print(" marked(" INTPTR_FORMAT ")", value());
   } else if (has_monitor()) {  // last bits = 10
     // have to check has_monitor() before is_locked()
     st->print(" monitor(" INTPTR_FORMAT ")=", value());
-    if (print_monitor_info) {
+    if (print_monitor_info && LockingMode != LM_LIGHTWEIGHT) {
+      // TODO[OMWorld]: Cleanup, even if the monitor read is racy, something should be printed.
       ObjectMonitor* mon = monitor();
       if (mon == nullptr) {
         st->print("null (this should never be seen!)");
       } else {
         mon->print_on(st);
diff a/src/hotspot/share/oops/markWord.hpp b/src/hotspot/share/oops/markWord.hpp
--- a/src/hotspot/share/oops/markWord.hpp
+++ b/src/hotspot/share/oops/markWord.hpp
@@ -24,26 +24,32 @@
 
 #ifndef SHARE_OOPS_MARKWORD_HPP
 #define SHARE_OOPS_MARKWORD_HPP
 
 #include "metaprogramming/primitiveConversions.hpp"
+#include "oops/compressedKlass.hpp"
 #include "oops/oopsHierarchy.hpp"
 #include "runtime/globals.hpp"
+#include "utilities/globalDefinitions.hpp"
 
 #include <type_traits>
 
 // The markWord describes the header of an object.
 //
 // Bit-format of an object header (most significant first, big endian layout below):
 //
 //  32 bits:
 //  --------
-//             hash:25 ------------>| age:4  unused_gap:1  lock:2 (normal object)
+//             hash:25 ------------>| age:4  self-fwd:1  lock:2 (normal object)
 //
 //  64 bits:
 //  --------
-//  unused:25 hash:31 -->| unused_gap:1  age:4  unused_gap:1  lock:2 (normal object)
+//  unused:25 hash:31 -->| unused_gap:1  age:4  self-fwd:1  lock:2 (normal object)
+//
+//  64 bits (with compact headers):
+//  -------------------------------
+//  nklass:32 hash:25 -->| unused_gap:1  age:4  self-fwded:1  lock:2 (normal object)
 //
 //  - hash contains the identity hash value: largest value is
 //    31 bits, see os::random().  Also, 64-bit vm's require
 //    a hash value no bigger than 32 bits because they will not
 //    properly generate a mask larger than that: see library_call.cpp
@@ -101,25 +107,54 @@
   uintptr_t value() const { return _value; }
 
   // Constants
   static const int age_bits                       = 4;
   static const int lock_bits                      = 2;
-  static const int first_unused_gap_bits          = 1;
-  static const int max_hash_bits                  = BitsPerWord - age_bits - lock_bits - first_unused_gap_bits;
+  static const int self_fwd_bits                  = 1;
+  static const int max_hash_bits                  = BitsPerWord - age_bits - lock_bits - self_fwd_bits;
   static const int hash_bits                      = max_hash_bits > 31 ? 31 : max_hash_bits;
-  static const int second_unused_gap_bits         = LP64_ONLY(1) NOT_LP64(0);
+  static const int hash_bits_compact              = hash_bits;
+  // Used only without compact headers.
+  static const int unused_gap_bits                = LP64_ONLY(1) NOT_LP64(0);
 
   static const int lock_shift                     = 0;
-  static const int age_shift                      = lock_bits + first_unused_gap_bits;
-  static const int hash_shift                     = age_shift + age_bits + second_unused_gap_bits;
+  static const int self_fwd_shift                 = lock_shift + lock_bits;
+  static const int age_shift                      = self_fwd_shift + self_fwd_bits;
+  static const int hash_shift                     = age_shift + age_bits + unused_gap_bits;
+  static const int hash_shift_compact             = 11;
 
   static const uintptr_t lock_mask                = right_n_bits(lock_bits);
   static const uintptr_t lock_mask_in_place       = lock_mask << lock_shift;
+  static const uintptr_t self_fwd_mask            = right_n_bits(self_fwd_bits);
+  static const uintptr_t self_fwd_mask_in_place   = self_fwd_mask << self_fwd_shift;
   static const uintptr_t age_mask                 = right_n_bits(age_bits);
   static const uintptr_t age_mask_in_place        = age_mask << age_shift;
   static const uintptr_t hash_mask                = right_n_bits(hash_bits);
   static const uintptr_t hash_mask_in_place       = hash_mask << hash_shift;
+  static const uintptr_t hash_mask_compact        = right_n_bits(hash_bits_compact);
+  static const uintptr_t hash_mask_compact_in_place = hash_mask_compact << hash_shift_compact;
+
+#ifdef _LP64
+  // Used only with compact headers:
+  // We store nKlass in the upper 22 bits of the markword. When extracting, we need to read the upper
+  // 32 bits and rightshift by the lower 10 foreign bits.
+
+  // These are for loading the nKlass with a 32-bit load and subsequent masking of the lower
+  // shadow bits
+  static constexpr int klass_load_shift           = 32;
+  static constexpr int klass_load_bits            = 32;
+  static constexpr int klass_shadow_bits          = 10;
+  static constexpr uintptr_t klass_shadow_mask    = right_n_bits(klass_shadow_bits);
+  static constexpr uintptr_t klass_shadow_mask_inplace  = klass_shadow_mask << klass_load_shift;
+
+  // These are for bit-precise extraction of the nKlass from the 64-bit Markword
+  static constexpr int klass_shift                = 42;
+  static constexpr int klass_bits                 = 22;
+  static constexpr uintptr_t klass_mask           = right_n_bits(klass_bits);
+  static constexpr uintptr_t klass_mask_in_place  = klass_mask << klass_shift;
+#endif
+
 
   static const uintptr_t locked_value             = 0;
   static const uintptr_t unlocked_value           = 1;
   static const uintptr_t monitor_value            = 2;
   static const uintptr_t marked_value             = 3;
@@ -141,10 +176,14 @@
     return (mask_bits(value(), lock_mask_in_place) == unlocked_value);
   }
   bool is_marked()   const {
     return (mask_bits(value(), lock_mask_in_place) == marked_value);
   }
+  bool is_forwarded() const {
+    // Returns true for normal forwarded (0b011) and self-forwarded (0b1xx).
+    return mask_bits(value(), lock_mask_in_place | self_fwd_mask_in_place) >= static_cast<intptr_t>(marked_value);
+  }
   bool is_neutral()  const {  // Not locked, or marked - a "clean" neutral state
     return (mask_bits(value(), lock_mask_in_place) == unlocked_value);
   }
 
   // Special temporary state of the markWord while being inflated.
@@ -192,24 +231,31 @@
   bool has_monitor() const {
     return ((value() & lock_mask_in_place) == monitor_value);
   }
   ObjectMonitor* monitor() const {
     assert(has_monitor(), "check");
+    assert(LockingMode != LM_LIGHTWEIGHT, "Lightweight locking does not use markWord for monitors");
     // Use xor instead of &~ to provide one extra tag-bit check.
     return (ObjectMonitor*) (value() ^ monitor_value);
   }
   bool has_displaced_mark_helper() const {
     intptr_t lockbits = value() & lock_mask_in_place;
-    return LockingMode == LM_LIGHTWEIGHT  ? lockbits == monitor_value   // monitor?
-                                          : (lockbits & unlocked_value) == 0; // monitor | stack-locked?
+    return LockingMode == LM_LIGHTWEIGHT ? false                             // no displaced mark
+                                         : (lockbits & unlocked_value) == 0; // monitor | stack-locked?
   }
   markWord displaced_mark_helper() const;
   void set_displaced_mark_helper(markWord m) const;
   markWord copy_set_hash(intptr_t hash) const {
-    uintptr_t tmp = value() & (~hash_mask_in_place);
-    tmp |= ((hash & hash_mask) << hash_shift);
-    return markWord(tmp);
+    if (UseCompactObjectHeaders) {
+      uintptr_t tmp = value() & (~hash_mask_compact_in_place);
+      tmp |= ((hash & hash_mask_compact) << hash_shift_compact);
+      return markWord(tmp);
+    } else {
+      uintptr_t tmp = value() & (~hash_mask_in_place);
+      tmp |= ((hash & hash_mask) << hash_shift);
+      return markWord(tmp);
+    }
   }
   // it is only used to be stored into BasicLock as the
   // indicator that the lock is using heavyweight monitor
   static markWord unused_mark() {
     return markWord(marked_value);
@@ -218,16 +264,21 @@
   // stored into object header, it encodes monitor info
   static markWord encode(BasicLock* lock) {
     return from_pointer(lock);
   }
   static markWord encode(ObjectMonitor* monitor) {
+    assert(LockingMode != LM_LIGHTWEIGHT, "Lightweight locking does not use markWord for monitors");
     uintptr_t tmp = (uintptr_t) monitor;
     return markWord(tmp | monitor_value);
   }
 
+  markWord set_has_monitor() const {
+    return markWord((value() & ~lock_mask_in_place) | monitor_value);
+  }
+
   // used to encode pointers during GC
-  markWord clear_lock_bits() { return markWord(value() & ~lock_mask_in_place); }
+  markWord clear_lock_bits() const { return markWord(value() & ~lock_mask_in_place); }
 
   // age operations
   markWord set_marked()   { return markWord((value() & ~lock_mask_in_place) | marked_value); }
   markWord set_unmarked() { return markWord((value() & ~lock_mask_in_place) | unlocked_value); }
 
@@ -238,17 +289,28 @@
   }
   markWord incr_age()      const { return age() == max_age ? markWord(_value) : set_age(age() + 1); }
 
   // hash operations
   intptr_t hash() const {
-    return mask_bits(value() >> hash_shift, hash_mask);
+    if (UseCompactObjectHeaders) {
+      return mask_bits(value() >> hash_shift_compact, hash_mask_compact);
+    } else {
+      return mask_bits(value() >> hash_shift, hash_mask);
+    }
   }
 
   bool has_no_hash() const {
     return hash() == no_hash;
   }
 
+  inline Klass* klass() const;
+  inline Klass* klass_or_null() const;
+  inline Klass* klass_without_asserts() const;
+  inline narrowKlass narrow_klass() const;
+  inline markWord set_narrow_klass(narrowKlass nklass) const;
+  inline markWord set_klass(Klass* klass) const;
+
   // Prototype mark for initialization
   static markWord prototype() {
     return markWord( no_hash_in_place | no_lock_in_place );
   }
 
@@ -258,10 +320,25 @@
   // Prepare address of oop for placement into mark
   inline static markWord encode_pointer_as_mark(void* p) { return from_pointer(p).set_marked(); }
 
   // Recover address of oop from encoded form used in mark
   inline void* decode_pointer() { return (void*)clear_lock_bits().value(); }
+
+  inline bool self_forwarded() const {
+    NOT_LP64(assert(LockingMode != LM_LEGACY, "incorrect with LM_LEGACY on 32 bit");)
+    return mask_bits(value(), self_fwd_mask_in_place) != 0;
+  }
+
+  inline markWord set_self_forwarded() const {
+    NOT_LP64(assert(LockingMode != LM_LEGACY, "incorrect with LM_LEGACY on 32 bit");)
+    return markWord(value() | self_fwd_mask_in_place);
+  }
+
+  inline markWord unset_self_forwarded() const {
+    NOT_LP64(assert(LockingMode != LM_LEGACY, "incorrect with LM_LEGACY on 32 bit");)
+    return markWord(value() & ~self_fwd_mask_in_place);
+  }
 };
 
 // Support atomic operations.
 template<>
 struct PrimitiveConversions::Translate<markWord> : public std::true_type {
diff a/src/hotspot/share/oops/markWord.inline.hpp b/src/hotspot/share/oops/markWord.inline.hpp
--- /dev/null
+++ b/src/hotspot/share/oops/markWord.inline.hpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright Amazon.com Inc. 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef SHARE_OOPS_MARKWORD_INLINE_HPP
+#define SHARE_OOPS_MARKWORD_INLINE_HPP
+
+#include "oops/markWord.hpp"
+#include "oops/compressedOops.inline.hpp"
+
+narrowKlass markWord::narrow_klass() const {
+#ifdef _LP64
+  assert(UseCompactObjectHeaders, "only used with compact object headers");
+  return narrowKlass(value() >> klass_shift);
+#else
+  ShouldNotReachHere();
+  return 0;
+#endif
+}
+
+markWord markWord::set_narrow_klass(narrowKlass nklass) const {
+#ifdef _LP64
+  assert(UseCompactObjectHeaders, "only used with compact object headers");
+  return markWord((value() & ~klass_mask_in_place) | ((uintptr_t) nklass << klass_shift));
+#else
+  ShouldNotReachHere();
+  return markWord(0);
+#endif
+}
+
+Klass* markWord::klass() const {
+#ifdef _LP64
+  assert(UseCompactObjectHeaders, "only used with compact object headers");
+  return CompressedKlassPointers::decode_not_null(narrow_klass());
+#else
+  ShouldNotReachHere();
+  return nullptr;
+#endif
+}
+
+Klass* markWord::klass_or_null() const {
+#ifdef _LP64
+  assert(UseCompactObjectHeaders, "only used with compact object headers");
+  return CompressedKlassPointers::decode(narrow_klass());
+#else
+  ShouldNotReachHere();
+  return nullptr;
+#endif
+}
+
+Klass* markWord::klass_without_asserts() const {
+#ifdef _LP64
+  assert(UseCompactObjectHeaders, "only used with compact object headers");
+  return CompressedKlassPointers::decode_without_asserts(narrow_klass());
+#else
+  ShouldNotReachHere();
+  return nullptr;
+#endif
+}
+
+markWord markWord::set_klass(Klass* klass) const {
+#ifdef _LP64
+  assert(UseCompactObjectHeaders, "only used with compact object headers");
+  assert(UseCompressedClassPointers, "expect compressed klass pointers");
+  narrowKlass nklass = CompressedKlassPointers::encode(const_cast<Klass*>(klass));
+  return set_narrow_klass(nklass);
+#else
+  ShouldNotReachHere();
+  return markWord();
+#endif
+}
+
+#endif // SHARE_OOPS_MARKWORD_INLINE_HPP
diff a/src/hotspot/share/oops/objArrayKlass.cpp b/src/hotspot/share/oops/objArrayKlass.cpp
--- a/src/hotspot/share/oops/objArrayKlass.cpp
+++ b/src/hotspot/share/oops/objArrayKlass.cpp
@@ -141,11 +141,12 @@
   assert(is_array_klass(), "sanity");
   assert(is_objArray_klass(), "sanity");
 }
 
 size_t ObjArrayKlass::oop_size(oop obj) const {
-  assert(obj->is_objArray(), "must be object array");
+  // In this assert, we cannot safely access the Klass* with compact headers.
+  assert(UseCompactObjectHeaders || obj->is_objArray(), "must be object array");
   return objArrayOop(obj)->object_size();
 }
 
 objArrayOop ObjArrayKlass::allocate(int length, TRAPS) {
   check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_NULL);
diff a/src/hotspot/share/oops/objArrayKlass.inline.hpp b/src/hotspot/share/oops/objArrayKlass.inline.hpp
--- a/src/hotspot/share/oops/objArrayKlass.inline.hpp
+++ b/src/hotspot/share/oops/objArrayKlass.inline.hpp
@@ -68,11 +68,12 @@
   }
 }
 
 template <typename T, typename OopClosureType>
 void ObjArrayKlass::oop_oop_iterate(oop obj, OopClosureType* closure) {
-  assert (obj->is_array(), "obj must be array");
+  // In this assert, we cannot safely access the Klass* with compact headers.
+  assert (UseCompactObjectHeaders || obj->is_array(), "obj must be array");
   objArrayOop a = objArrayOop(obj);
 
   if (Devirtualizer::do_metadata(closure)) {
     Devirtualizer::do_klass(closure, obj->klass());
   }
diff a/src/hotspot/share/oops/oop.cpp b/src/hotspot/share/oops/oop.cpp
--- a/src/hotspot/share/oops/oop.cpp
+++ b/src/hotspot/share/oops/oop.cpp
@@ -152,11 +152,12 @@
 bool oopDesc::is_objArray_noinline()    const { return is_objArray();    }
 bool oopDesc::is_typeArray_noinline()   const { return is_typeArray();   }
 
 bool oopDesc::has_klass_gap() {
   // Only has a klass gap when compressed class pointers are used.
-  return UseCompressedClassPointers;
+  // Except when using compact headers.
+  return UseCompressedClassPointers && !UseCompactObjectHeaders;
 }
 
 #if INCLUDE_CDS_JAVA_HEAP
 void oopDesc::set_narrow_klass(narrowKlass nk) {
   assert(CDSConfig::is_dumping_heap(), "Used by CDS only. Do not abuse!");
@@ -218,14 +219,14 @@
 
 jdouble oopDesc::double_field_acquire(int offset) const               { return Atomic::load_acquire(field_addr<jdouble>(offset)); }
 void oopDesc::release_double_field_put(int offset, jdouble value)     { Atomic::release_store(field_addr<jdouble>(offset), value); }
 
 #ifdef ASSERT
-bool oopDesc::size_might_change() {
+bool oopDesc::size_might_change(Klass* klass) {
   // UseParallelGC and UseG1GC can change the length field
   // of an "old copy" of an object array in the young gen so it indicates
   // the grey portion of an already copied array. This will cause the first
   // disjunct below to fail if the two comparands are computed across such
   // a concurrent change.
-  return Universe::heap()->is_gc_active() && is_objArray() && is_forwarded() && (UseParallelGC || UseG1GC);
+  return Universe::heap()->is_gc_active() && klass->is_objArray_klass() && is_forwarded() && (UseParallelGC || UseG1GC);
 }
 #endif
diff a/src/hotspot/share/oops/oop.hpp b/src/hotspot/share/oops/oop.hpp
--- a/src/hotspot/share/oops/oop.hpp
+++ b/src/hotspot/share/oops/oop.hpp
@@ -60,10 +60,12 @@
 
   // There may be ordering constraints on the initialization of fields that
   // make use of the C++ copy/assign incorrect.
   NONCOPYABLE(oopDesc);
 
+  inline oop cas_set_forwardee(markWord new_mark, markWord old_mark, atomic_memory_order order);
+
  public:
   // Must be trivial; see verifying static assert after the class.
   oopDesc() = default;
 
   inline markWord  mark()          const;
@@ -76,10 +78,15 @@
 
   inline void release_set_mark(markWord m);
   inline markWord cas_set_mark(markWord new_mark, markWord old_mark);
   inline markWord cas_set_mark(markWord new_mark, markWord old_mark, atomic_memory_order order);
 
+  inline markWord resolve_mark() const;
+
+  // Returns the prototype mark that should be used for this object.
+  inline markWord prototype_mark() const;
+
   // Used only to re-initialize the mark word (e.g., of promoted
   // objects during a GC) -- requires a valid klass pointer
   inline void init_mark();
 
   inline Klass* klass() const;
@@ -94,11 +101,17 @@
 
   // For klass field compression
   static inline void set_klass_gap(HeapWord* mem, int z);
 
   // size of object header, aligned to platform wordSize
-  static constexpr int header_size() { return sizeof(oopDesc)/HeapWordSize; }
+  static int header_size() {
+    if (UseCompactObjectHeaders) {
+      return sizeof(markWord) / HeapWordSize;
+    } else {
+      return sizeof(oopDesc)/HeapWordSize;
+    }
+  }
 
   // Returns whether this is an instance of k or an instance of a subclass of k
   inline bool is_a(Klass* k) const;
 
   // Returns the actual oop size of the object in machine words
@@ -106,10 +119,24 @@
 
   // Sometimes (for complicated concurrency-related reasons), it is useful
   // to be able to figure out the size of an object knowing its klass.
   inline size_t size_given_klass(Klass* klass);
 
+  // The following set of methods is used to access the mark-word and related
+  // properties when the object may be forwarded. Be careful where and when
+  // using this method. It assumes that the forwardee is installed in
+  // the header as a plain pointer (or self-forwarded). In particular,
+  // those methods can not deal with the sliding-forwarding that is used
+  // in Serial, G1 and Shenandoah full-GCs.
+private:
+  inline Klass*   forward_safe_klass_impl(markWord m) const;
+public:
+  inline Klass*   forward_safe_klass() const;
+  inline Klass*   forward_safe_klass(markWord m) const;
+  inline size_t   forward_safe_size();
+  inline void     forward_safe_init_mark();
+
   // type test operations (inlined in oop.inline.hpp)
   inline bool is_instance()    const;
   inline bool is_instanceRef() const;
   inline bool is_stackChunk()  const;
   inline bool is_array()       const;
@@ -254,20 +281,26 @@
   // garbage collection
   inline bool is_gc_marked() const;
 
   // Forward pointer operations for scavenge
   inline bool is_forwarded() const;
+  inline bool is_self_forwarded() const;
 
   inline void forward_to(oop p);
+  inline void forward_to_self();
 
   // Like "forward_to", but inserts the forwarding pointer atomically.
   // Exactly one thread succeeds in inserting the forwarding pointer, and
   // this call returns null for that thread; any other thread has the
   // value of the forwarding pointer returned and does not modify "this".
   inline oop forward_to_atomic(oop p, markWord compare, atomic_memory_order order = memory_order_conservative);
+  inline oop forward_to_self_atomic(markWord compare, atomic_memory_order order = memory_order_conservative);
 
   inline oop forwardee() const;
+  inline oop forwardee(markWord header) const;
+
+  inline void unset_self_forwarded();
 
   // Age of object during scavenge
   inline uint age() const;
   inline void incr_age();
 
@@ -307,20 +340,48 @@
 
   static bool has_klass_gap();
 
   // for code generation
   static int mark_offset_in_bytes()      { return (int)offset_of(oopDesc, _mark); }
-  static int klass_offset_in_bytes()     { return (int)offset_of(oopDesc, _metadata._klass); }
+  static int klass_offset_in_bytes()     {
+#ifdef _LP64
+    if (UseCompactObjectHeaders) {
+      constexpr int load_shift = markWord::klass_load_shift;
+      STATIC_ASSERT(load_shift % 8 == 0);
+      return mark_offset_in_bytes() + load_shift / 8;
+    } else
+#endif
+    {
+      return (int)offset_of(oopDesc, _metadata._klass);
+    }
+  }
   static int klass_gap_offset_in_bytes() {
     assert(has_klass_gap(), "only applicable to compressed klass pointers");
+    assert(!UseCompactObjectHeaders, "don't use klass_offset_in_bytes() with compact headers");
     return klass_offset_in_bytes() + sizeof(narrowKlass);
   }
 
+  static int base_offset_in_bytes() {
+#ifdef _LP64
+    if (UseCompactObjectHeaders) {
+      // With compact headers, the Klass* field is not used for the Klass*
+      // and is used for the object fields instead.
+      STATIC_ASSERT(sizeof(markWord) == 8);
+      return sizeof(markWord);
+    } else if (UseCompressedClassPointers) {
+      return sizeof(markWord) + sizeof(narrowKlass);
+    } else
+#endif
+    {
+      return sizeof(oopDesc);
+    }
+  }
+
   // for error reporting
   static void* load_oop_raw(oop obj, int offset);
 
-  DEBUG_ONLY(bool size_might_change();)
+  DEBUG_ONLY(bool size_might_change(Klass* klass);)
 };
 
 // An oopDesc is not initialized via a constructor.  Space is allocated in
 // the Java heap, and static functions provided here on HeapWord* are used
 // to fill in certain parts of that memory.  The allocated memory is then
diff a/src/hotspot/share/oops/oop.inline.hpp b/src/hotspot/share/oops/oop.inline.hpp
--- a/src/hotspot/share/oops/oop.inline.hpp
+++ b/src/hotspot/share/oops/oop.inline.hpp
@@ -32,11 +32,11 @@
 #include "oops/access.inline.hpp"
 #include "oops/arrayKlass.hpp"
 #include "oops/arrayOop.hpp"
 #include "oops/compressedKlass.inline.hpp"
 #include "oops/instanceKlass.hpp"
-#include "oops/markWord.hpp"
+#include "oops/markWord.inline.hpp"
 #include "oops/oopsHierarchy.hpp"
 #include "runtime/atomic.hpp"
 #include "runtime/globals.hpp"
 #include "utilities/align.hpp"
 #include "utilities/debug.hpp"
@@ -64,84 +64,107 @@
 
 void oopDesc::set_mark(HeapWord* mem, markWord m) {
   *(markWord*)(((char*)mem) + mark_offset_in_bytes()) = m;
 }
 
-void oopDesc::release_set_mark(HeapWord* mem, markWord m) {
-  Atomic::release_store((markWord*)(((char*)mem) + mark_offset_in_bytes()), m);
-}
-
 void oopDesc::release_set_mark(markWord m) {
   Atomic::release_store(&_mark, m);
 }
 
+void oopDesc::release_set_mark(HeapWord* mem, markWord m) {
+  Atomic::release_store((markWord*)(((char*)mem) + mark_offset_in_bytes()), m);
+}
+
 markWord oopDesc::cas_set_mark(markWord new_mark, markWord old_mark) {
   return Atomic::cmpxchg(&_mark, old_mark, new_mark);
 }
 
 markWord oopDesc::cas_set_mark(markWord new_mark, markWord old_mark, atomic_memory_order order) {
   return Atomic::cmpxchg(&_mark, old_mark, new_mark, order);
 }
 
+markWord oopDesc::prototype_mark() const {
+  if (UseCompactObjectHeaders) {
+    return klass()->prototype_header();
+  } else {
+    return markWord::prototype();
+  }
+}
+
 void oopDesc::init_mark() {
-  set_mark(markWord::prototype());
+  if (UseCompactObjectHeaders) {
+    set_mark(prototype_mark());
+  } else {
+    set_mark(markWord::prototype());
+  }
 }
 
 Klass* oopDesc::klass() const {
-  if (UseCompressedClassPointers) {
-    return CompressedKlassPointers::decode_not_null(_metadata._compressed_klass);
+  if (UseCompactObjectHeaders) {
+    return mark().klass();
+  } else if (UseCompressedClassPointers) {
+     return CompressedKlassPointers::decode_not_null(_metadata._compressed_klass);
   } else {
     return _metadata._klass;
   }
 }
 
 Klass* oopDesc::klass_or_null() const {
-  if (UseCompressedClassPointers) {
+  if (UseCompactObjectHeaders) {
+    return mark().klass_or_null();
+  } else if (UseCompressedClassPointers) {
     return CompressedKlassPointers::decode(_metadata._compressed_klass);
   } else {
     return _metadata._klass;
   }
 }
 
 Klass* oopDesc::klass_or_null_acquire() const {
-  if (UseCompressedClassPointers) {
+  if (UseCompactObjectHeaders) {
+    return mark_acquire().klass();
+  } else if (UseCompressedClassPointers) {
     narrowKlass nklass = Atomic::load_acquire(&_metadata._compressed_klass);
     return CompressedKlassPointers::decode(nklass);
   } else {
     return Atomic::load_acquire(&_metadata._klass);
   }
 }
 
 Klass* oopDesc::klass_without_asserts() const {
-  if (UseCompressedClassPointers) {
+  if (UseCompactObjectHeaders) {
+    return mark().klass_without_asserts();
+  } else if (UseCompressedClassPointers) {
     return CompressedKlassPointers::decode_without_asserts(_metadata._compressed_klass);
   } else {
     return _metadata._klass;
   }
 }
 
 void oopDesc::set_klass(Klass* k) {
   assert(Universe::is_bootstrapping() || (k != nullptr && k->is_klass()), "incorrect Klass");
+  assert(!UseCompactObjectHeaders, "don't set Klass* with compact headers");
   if (UseCompressedClassPointers) {
     _metadata._compressed_klass = CompressedKlassPointers::encode_not_null(k);
   } else {
     _metadata._klass = k;
   }
 }
 
 void oopDesc::release_set_klass(HeapWord* mem, Klass* k) {
   assert(Universe::is_bootstrapping() || (k != nullptr && k->is_klass()), "incorrect Klass");
+  assert(!UseCompactObjectHeaders, "don't set Klass* with compact headers");
   char* raw_mem = ((char*)mem + klass_offset_in_bytes());
   if (UseCompressedClassPointers) {
     Atomic::release_store((narrowKlass*)raw_mem,
                           CompressedKlassPointers::encode_not_null(k));
   } else {
     Atomic::release_store((Klass**)raw_mem, k);
   }
 }
 
 void oopDesc::set_klass_gap(HeapWord* mem, int v) {
+  assert(!UseCompactObjectHeaders, "don't set Klass* gap with compact headers");
   if (UseCompressedClassPointers) {
     *(int*)(((char*)mem) + klass_gap_offset_in_bytes()) = v;
   }
 }
 
@@ -188,11 +211,11 @@
       // This code could be simplified, but by keeping array_header_in_bytes
       // in units of bytes and doing it this way we can round up just once,
       // skipping the intermediate round to HeapWordSize.
       s = align_up(size_in_bytes, MinObjAlignmentInBytes) / HeapWordSize;
 
-      assert(s == klass->oop_size(this) || size_might_change(), "wrong array object size");
+      assert(s == klass->oop_size(this) || size_might_change(klass), "wrong array object size");
     } else {
       // Must be zero, so bite the bullet and take the virtual call.
       s = klass->oop_size(this);
     }
   }
@@ -200,10 +223,57 @@
   assert(s > 0, "Oop size must be greater than zero, not " SIZE_FORMAT, s);
   assert(is_object_aligned(s), "Oop size is not properly aligned: " SIZE_FORMAT, s);
   return s;
 }
 
+#ifdef _LP64
+Klass* oopDesc::forward_safe_klass_impl(markWord m) const {
+  assert(UseCompactObjectHeaders, "Only get here with compact headers");
+  if (m.is_marked()) {
+    oop fwd = forwardee(m);
+    markWord m2 = fwd->mark();
+    assert(!m2.is_marked() || m2.self_forwarded(), "no double forwarding: this: " PTR_FORMAT " (" INTPTR_FORMAT "), fwd: " PTR_FORMAT " (" INTPTR_FORMAT ")", p2i(this), m.value(), p2i(fwd), m2.value());
+    m = m2;
+  }
+  return m.klass();
+}
+#endif
+
+Klass* oopDesc::forward_safe_klass(markWord m) const {
+#ifdef _LP64
+  if (UseCompactObjectHeaders) {
+    return forward_safe_klass_impl(m);
+  } else
+#endif
+  {
+    return klass();
+  }
+}
+
+Klass* oopDesc::forward_safe_klass() const {
+#ifdef _LP64
+  if (UseCompactObjectHeaders) {
+    return forward_safe_klass_impl(mark());
+  } else
+#endif
+  {
+    return klass();
+  }
+}
+
+size_t oopDesc::forward_safe_size() {
+  return size_given_klass(forward_safe_klass());
+}
+
+void oopDesc::forward_safe_init_mark() {
+  if (UseCompactObjectHeaders) {
+    set_mark(forward_safe_klass()->prototype_header());
+  } else {
+    set_mark(markWord::prototype());
+  }
+}
+
 bool oopDesc::is_instance()    const { return klass()->is_instance_klass();             }
 bool oopDesc::is_instanceRef() const { return klass()->is_reference_instance_klass();   }
 bool oopDesc::is_stackChunk()  const { return klass()->is_stack_chunk_instance_klass(); }
 bool oopDesc::is_array()       const { return klass()->is_array_klass();                }
 bool oopDesc::is_objArray()    const { return klass()->is_objArray_klass();             }
@@ -260,39 +330,69 @@
   return mark().is_marked();
 }
 
 // Used by scavengers
 bool oopDesc::is_forwarded() const {
-  // The extra heap check is needed since the obj might be locked, in which case the
-  // mark would point to a stack location and have the sentinel bit cleared
-  return mark().is_marked();
+  return mark().is_forwarded();
+}
+
+bool oopDesc::is_self_forwarded() const {
+  return mark().self_forwarded();
 }
 
 // Used by scavengers
 void oopDesc::forward_to(oop p) {
   markWord m = markWord::encode_pointer_as_mark(p);
   assert(m.decode_pointer() == p, "encoding must be reversible");
   set_mark(m);
 }
 
-oop oopDesc::forward_to_atomic(oop p, markWord compare, atomic_memory_order order) {
-  markWord m = markWord::encode_pointer_as_mark(p);
-  assert(m.decode_pointer() == p, "encoding must be reversible");
-  markWord old_mark = cas_set_mark(m, compare, order);
+void oopDesc::forward_to_self() {
+  set_mark(mark().set_self_forwarded());
+}
+
+oop oopDesc::cas_set_forwardee(markWord new_mark, markWord compare, atomic_memory_order order) {
+  markWord old_mark = cas_set_mark(new_mark, compare, order);
   if (old_mark == compare) {
     return nullptr;
   } else {
-    return cast_to_oop(old_mark.decode_pointer());
+    assert(old_mark.is_forwarded(), "must be forwarded here");
+    return forwardee(old_mark);
+  }
+}
+
+oop oopDesc::forward_to_atomic(oop p, markWord compare, atomic_memory_order order) {
+  markWord m = markWord::encode_pointer_as_mark(p);
+  assert(forwardee(m) == p, "encoding must be reversible");
+  return cas_set_forwardee(m, compare, order);
+}
+
+oop oopDesc::forward_to_self_atomic(markWord old_mark, atomic_memory_order order) {
+  markWord new_mark = old_mark.set_self_forwarded();
+  assert(forwardee(new_mark) == cast_to_oop(this), "encoding must be reversible");
+  return cas_set_forwardee(new_mark, old_mark, order);
+}
+
+oop oopDesc::forwardee(markWord mark) const {
+  assert(mark.is_forwarded(), "only decode when actually forwarded");
+  if (mark.self_forwarded()) {
+    return cast_to_oop(this);
+  } else {
+    return cast_to_oop(mark.decode_pointer());
   }
 }
 
 // Note that the forwardee is not the same thing as the displaced_mark.
 // The forwardee is used when copying during scavenge and mark-sweep.
 // It does need to clear the low two locking- and GC-related bits.
 oop oopDesc::forwardee() const {
   assert(is_forwarded(), "only decode when actually forwarded");
-  return cast_to_oop(mark().decode_pointer());
+  return forwardee(mark());
+}
+
+void oopDesc::unset_self_forwarded() {
+  set_mark(mark().unset_self_forwarded());
 }
 
 // The following method needs to be MT safe.
 uint oopDesc::age() const {
   markWord m = mark();
@@ -345,11 +445,12 @@
   oop_iterate_backwards(cl, klass());
 }
 
 template <typename OopClosureType>
 void oopDesc::oop_iterate_backwards(OopClosureType* cl, Klass* k) {
-  assert(k == klass(), "wrong klass");
+  // In this assert, we cannot safely access the Klass* with compact headers.
+  assert(UseCompactObjectHeaders || k == klass(), "wrong klass");
   OopIteratorClosureDispatch::oop_oop_iterate_backwards(cl, this, k);
 }
 
 bool oopDesc::is_instanceof_or_null(oop obj, Klass* klass) {
   return obj == nullptr || obj->klass()->is_subtype_of(klass);
diff a/src/hotspot/share/oops/typeArrayKlass.cpp b/src/hotspot/share/oops/typeArrayKlass.cpp
--- a/src/hotspot/share/oops/typeArrayKlass.cpp
+++ b/src/hotspot/share/oops/typeArrayKlass.cpp
@@ -169,11 +169,12 @@
   size_t dst_offset = arrayOopDesc::base_offset_in_bytes(element_type()) + ((size_t)dst_pos << l2es);
   ArrayAccess<ARRAYCOPY_ATOMIC>::arraycopy<void>(s, src_offset, d, dst_offset, (size_t)length << l2es);
 }
 
 size_t TypeArrayKlass::oop_size(oop obj) const {
-  assert(obj->is_typeArray(),"must be a type array");
+  // In this assert, we cannot safely access the Klass* with compact headers.
+  assert(UseCompactObjectHeaders || obj->is_typeArray(),"must be a type array");
   typeArrayOop t = typeArrayOop(obj);
   return t->object_size(this);
 }
 
 void TypeArrayKlass::initialize(TRAPS) {
diff a/src/hotspot/share/oops/typeArrayKlass.inline.hpp b/src/hotspot/share/oops/typeArrayKlass.inline.hpp
--- a/src/hotspot/share/oops/typeArrayKlass.inline.hpp
+++ b/src/hotspot/share/oops/typeArrayKlass.inline.hpp
@@ -33,11 +33,12 @@
 #include "oops/typeArrayOop.hpp"
 
 class OopIterateClosure;
 
 inline void TypeArrayKlass::oop_oop_iterate_impl(oop obj, OopIterateClosure* closure) {
-  assert(obj->is_typeArray(),"must be a type array");
+  // In this assert, we cannot safely access the Klass* with compact headers.
+  assert(UseCompactObjectHeaders || obj->is_typeArray(),"must be a type array");
   // Performance tweak: We skip processing the klass pointer since all
   // TypeArrayKlasses are guaranteed processed via the null class loader.
 }
 
 template <typename T, typename OopClosureType>
diff a/src/hotspot/share/opto/c2_CodeStubs.hpp b/src/hotspot/share/opto/c2_CodeStubs.hpp
--- a/src/hotspot/share/opto/c2_CodeStubs.hpp
+++ b/src/hotspot/share/opto/c2_CodeStubs.hpp
@@ -101,18 +101,20 @@
 private:
   Register _obj;
   Register _mark;
   Register _t;
   Register _thread;
+  Label _slow_path;
   Label _push_and_slow_path;
   Label _check_successor;
   Label _unlocked_continuation;
 public:
   C2FastUnlockLightweightStub(Register obj, Register mark, Register t, Register thread) : C2CodeStub(),
     _obj(obj), _mark(mark), _t(t), _thread(thread) {}
   int max_size() const;
   void emit(C2_MacroAssembler& masm);
+  Label& slow_path() { return _slow_path; }
   Label& push_and_slow_path() { return _push_and_slow_path; }
   Label& check_successor() { return _check_successor; }
   Label& unlocked_continuation() { return _unlocked_continuation; }
   Label& slow_path_continuation() { return continuation(); }
 };
@@ -128,11 +130,11 @@
   Register monitor() { return _monitor; }
   Register tmp() { return _tmp; }
   int max_size() const;
   void emit(C2_MacroAssembler& masm);
 };
-#endif
+#endif // _LP64
 
 //-----------------------------C2GeneralStub-----------------------------------
 // A generalized stub that can be used to implement an arbitrary stub in a
 // type-safe manner. An example:
 //
diff a/src/hotspot/share/opto/callnode.cpp b/src/hotspot/share/opto/callnode.cpp
--- a/src/hotspot/share/opto/callnode.cpp
+++ b/src/hotspot/share/opto/callnode.cpp
@@ -1615,12 +1615,18 @@
     _is_allocation_MemBar_redundant = true;
   }
 }
 Node *AllocateNode::make_ideal_mark(PhaseGVN *phase, Node* obj, Node* control, Node* mem) {
   Node* mark_node = nullptr;
-  // For now only enable fast locking for non-array types
-  mark_node = phase->MakeConX(markWord::prototype().value());
+  if (UseCompactObjectHeaders) {
+    Node* klass_node = in(AllocateNode::KlassNode);
+    Node* proto_adr = phase->transform(new AddPNode(klass_node, klass_node, phase->MakeConX(in_bytes(Klass::prototype_header_offset()))));
+    mark_node = LoadNode::make(*phase, control, mem, proto_adr, TypeRawPtr::BOTTOM, TypeX_X, TypeX_X->basic_type(), MemNode::unordered);
+  } else {
+    // For now only enable fast locking for non-array types
+    mark_node = phase->MakeConX(markWord::prototype().value());
+  }
   return mark_node;
 }
 
 // Retrieve the length from the AllocateArrayNode. Narrow the type with a
 // CastII, if appropriate.  If we are not allowed to create new nodes, and
diff a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp
--- a/src/hotspot/share/opto/compile.cpp
+++ b/src/hotspot/share/opto/compile.cpp
@@ -1711,10 +1711,14 @@
       if (flat->offset() == TypePtr::OffsetBot) {
         alias_type(idx)->set_element(flat->is_aryptr()->elem());
       }
     }
     if (flat->isa_klassptr()) {
+      if (UseCompactObjectHeaders) {
+        if (flat->offset() == in_bytes(Klass::prototype_header_offset()))
+          alias_type(idx)->set_rewritable(false);
+      }
       if (flat->offset() == in_bytes(Klass::super_check_offset_offset()))
         alias_type(idx)->set_rewritable(false);
       if (flat->offset() == in_bytes(Klass::modifier_flags_offset()))
         alias_type(idx)->set_rewritable(false);
       if (flat->offset() == in_bytes(Klass::access_flags_offset()))
diff a/src/hotspot/share/opto/lcm.cpp b/src/hotspot/share/opto/lcm.cpp
--- a/src/hotspot/share/opto/lcm.cpp
+++ b/src/hotspot/share/opto/lcm.cpp
@@ -266,12 +266,12 @@
              !CompressedOops::use_implicit_null_checks()))
           continue;
         // cannot reason about it; is probably not implicit null exception
       } else {
         const TypePtr* tptr;
-        if ((UseCompressedOops || UseCompressedClassPointers) &&
-            (CompressedOops::shift() == 0 || CompressedKlassPointers::shift() == 0)) {
+        if ((UseCompressedOops && CompressedOops::shift() == 0) ||
+            (UseCompressedClassPointers && CompressedKlassPointers::shift() == 0)) {
           // 32-bits narrow oop can be the base of address expressions
           tptr = base->get_ptr_type();
         } else {
           // only regular oops are expected here
           tptr = base->bottom_type()->is_ptr();
diff a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp
--- a/src/hotspot/share/opto/library_call.cpp
+++ b/src/hotspot/share/opto/library_call.cpp
@@ -4593,12 +4593,12 @@
 
   // Get the hash value and check to see that it has been properly assigned.
   // We depend on hash_mask being at most 32 bits and avoid the use of
   // hash_mask_in_place because it could be larger than 32 bits in a 64-bit
   // vm: see markWord.hpp.
-  Node *hash_mask      = _gvn.intcon(markWord::hash_mask);
-  Node *hash_shift     = _gvn.intcon(markWord::hash_shift);
+  Node *hash_mask      = _gvn.intcon(UseCompactObjectHeaders ? markWord::hash_mask_compact  : markWord::hash_mask);
+  Node *hash_shift     = _gvn.intcon(UseCompactObjectHeaders ? markWord::hash_shift_compact : markWord::hash_shift);
   Node *hshifted_header= _gvn.transform(new URShiftXNode(header, hash_shift));
   // This hack lets the hash bits live anywhere in the mark object now, as long
   // as the shift drops the relevant bits into the low 32 bits.  Note that
   // Java spec says that HashCode is an int so there's no point in capturing
   // an 'X'-sized hashcode (32 in 32-bit build or 64 in 64-bit build).
diff a/src/hotspot/share/opto/machnode.cpp b/src/hotspot/share/opto/machnode.cpp
--- a/src/hotspot/share/opto/machnode.cpp
+++ b/src/hotspot/share/opto/machnode.cpp
@@ -389,11 +389,11 @@
   const Type* t = base->bottom_type();
   if (t->isa_narrowoop() && CompressedOops::shift() == 0) {
     // 32-bit unscaled narrow oop can be the base of any address expression
     t = t->make_ptr();
   }
-  if (t->isa_narrowklass() && CompressedKlassPointers::shift() == 0) {
+  if (t->isa_narrowklass() && UseCompressedClassPointers && CompressedKlassPointers::shift() == 0) {
     // 32-bit unscaled narrow oop can be the base of any address expression
     t = t->make_ptr();
   }
   if (t->isa_intptr_t() && offset != 0 && offset != Type::OffsetBot) {
     // We cannot assert that the offset does not look oop-ish here.
diff a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp
--- a/src/hotspot/share/opto/macro.cpp
+++ b/src/hotspot/share/opto/macro.cpp
@@ -1699,11 +1699,13 @@
   if (!mark_node->is_Con()) {
     transform_later(mark_node);
   }
   rawmem = make_store(control, rawmem, object, oopDesc::mark_offset_in_bytes(), mark_node, TypeX_X->basic_type());
 
-  rawmem = make_store(control, rawmem, object, oopDesc::klass_offset_in_bytes(), klass_node, T_METADATA);
+  if (!UseCompactObjectHeaders) {
+    rawmem = make_store(control, rawmem, object, oopDesc::klass_offset_in_bytes(), klass_node, T_METADATA);
+  }
   int header_size = alloc->minimum_header_size();  // conservatively small
 
   // Array length
   if (length != nullptr) {         // Arrays need length field
     rawmem = make_store(control, rawmem, object, arrayOopDesc::length_offset_in_bytes(), length, T_INT);
diff a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp
--- a/src/hotspot/share/opto/memnode.cpp
+++ b/src/hotspot/share/opto/memnode.cpp
@@ -1932,10 +1932,17 @@
 // Helper to recognize certain Klass fields which are invariant across
 // some group of array types (e.g., int[] or all T[] where T < Object).
 const Type*
 LoadNode::load_array_final_field(const TypeKlassPtr *tkls,
                                  ciKlass* klass) const {
+  if (UseCompactObjectHeaders) {
+    if (tkls->offset() == in_bytes(Klass::prototype_header_offset())) {
+      // The field is Klass::_prototype_header.  Return its (constant) value.
+      assert(this->Opcode() == Op_LoadX, "must load a proper type from _prototype_header");
+      return TypeX::make(klass->prototype_header());
+    }
+  }
   if (tkls->offset() == in_bytes(Klass::modifier_flags_offset())) {
     // The field is Klass::_modifier_flags.  Return its (constant) value.
     // (Folds up the 2nd indirection in aClassConstant.getModifiers().)
     assert(this->Opcode() == Op_LoadI, "must load an int from _modifier_flags");
     return TypeInt::make(klass->modifier_flags());
@@ -2104,10 +2111,17 @@
         // The field is Klass::_super_check_offset.  Return its (constant) value.
         // (Folds up type checking code.)
         assert(Opcode() == Op_LoadI, "must load an int from _super_check_offset");
         return TypeInt::make(klass->super_check_offset());
       }
+      if (UseCompactObjectHeaders) {
+        if (tkls->offset() == in_bytes(Klass::prototype_header_offset())) {
+          // The field is Klass::_prototype_header. Return its (constant) value.
+          assert(this->Opcode() == Op_LoadX, "must load a proper type from _prototype_header");
+          return TypeX::make(klass->prototype_header());
+        }
+      }
       // Compute index into primary_supers array
       juint depth = (tkls->offset() - in_bytes(Klass::primary_supers_offset())) / sizeof(Klass*);
       // Check for overflowing; use unsigned compare to handle the negative case.
       if( depth < ciKlass::primary_super_limit() ) {
         // The field is an element of Klass::_primary_supers.  Return its (constant) value.
@@ -2194,11 +2208,11 @@
       return Type::get_zero_type(_type->basic_type());
     }
   }
 
   Node* alloc = is_new_object_mark_load();
-  if (alloc != nullptr) {
+  if (!UseCompactObjectHeaders && alloc != nullptr) {
     return TypeX::make(markWord::prototype().value());
   }
 
   return _type;
 }
diff a/src/hotspot/share/opto/subtypenode.cpp b/src/hotspot/share/opto/subtypenode.cpp
--- a/src/hotspot/share/opto/subtypenode.cpp
+++ b/src/hotspot/share/opto/subtypenode.cpp
@@ -242,6 +242,6 @@
     st->print(" profiled at:");
     _method->print_short_name(st);
     st->print(":%d", _bci);
   }
 }
-#endif
+#endif
diff a/src/hotspot/share/prims/jvmtiEnvBase.cpp b/src/hotspot/share/prims/jvmtiEnvBase.cpp
--- a/src/hotspot/share/prims/jvmtiEnvBase.cpp
+++ b/src/hotspot/share/prims/jvmtiEnvBase.cpp
@@ -1471,11 +1471,10 @@
     hobj = Handle(current_thread, mirror);
   }
 
   ThreadsListHandle tlh(current_thread);
   JavaThread *owning_thread = nullptr;
-  ObjectMonitor *mon = nullptr;
   jvmtiMonitorUsage ret = {
       nullptr, 0, 0, nullptr, 0, nullptr
   };
 
   uint32_t debug_bits = 0;
@@ -1496,12 +1495,15 @@
   jint nWant = 0, nWait = 0;
   markWord mark = hobj->mark();
   ResourceMark rm(current_thread);
   GrowableArray<JavaThread*>* wantList = nullptr;
 
-  if (mark.has_monitor()) {
-    mon = mark.monitor();
+  ObjectMonitor* mon = mark.has_monitor()
+      ? ObjectSynchronizer::read_monitor(current_thread, hobj(), mark)
+      : nullptr;
+
+  if (mon != nullptr) {
     assert(mon != nullptr, "must have monitor");
     // this object has a heavyweight monitor
     nWant = mon->contentions(); // # of threads contending for monitor entry, but not re-entry
     nWait = mon->waiters();     // # of threads waiting for notification,
                                 // or to re-enter monitor, in Object.wait()
diff a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp
--- a/src/hotspot/share/runtime/arguments.cpp
+++ b/src/hotspot/share/runtime/arguments.cpp
@@ -1425,17 +1425,10 @@
     }
   }
 #endif // _LP64
 }
 
-void Arguments::set_use_compressed_klass_ptrs() {
-#ifdef _LP64
-  assert(!UseCompressedClassPointers || CompressedClassSpaceSize <= KlassEncodingMetaspaceMax,
-         "CompressedClassSpaceSize is too large for UseCompressedClassPointers");
-#endif // _LP64
-}
-
 void Arguments::set_conservative_max_heap_alignment() {
   // The conservative maximum required alignment for the heap is the maximum of
   // the alignments imposed by several sources: any requirements from the heap
   // itself and the maximum page size we may run the VM with.
   size_t heap_alignment = GCConfig::arguments()->conservative_max_heap_alignment();
@@ -1450,11 +1443,10 @@
 
   set_conservative_max_heap_alignment();
 
 #ifdef _LP64
   set_use_compressed_oops();
-  set_use_compressed_klass_ptrs();
 
   // Also checks that certain machines are slower with compressed oops
   // in vm_version initialization code.
 #endif // _LP64
 
@@ -1830,11 +1822,11 @@
     FLAG_SET_CMDLINE(StackReservedPages, 0);
     warning("Reserved Stack Area not supported on this platform");
   }
 #endif
 
-#if !defined(X86) && !defined(AARCH64) && !defined(RISCV64) && !defined(ARM) && !defined(PPC64) && !defined(S390)
+#if !defined(X86) && !defined(AARCH64)
   if (LockingMode == LM_LIGHTWEIGHT) {
     FLAG_SET_CMDLINE(LockingMode, LM_LEGACY);
     warning("New lightweight locking not supported on this platform");
   }
 #endif
@@ -2957,10 +2949,23 @@
 
 #ifndef CAN_SHOW_REGISTERS_ON_ASSERT
   UNSUPPORTED_OPTION(ShowRegistersOnAssert);
 #endif // CAN_SHOW_REGISTERS_ON_ASSERT
 
+#ifdef _LP64
+  if (UseCompactObjectHeaders && FLAG_IS_CMDLINE(UseCompressedClassPointers) && !UseCompressedClassPointers) {
+    warning("Compact object headers require compressed class pointers. Disabling compact object headers.");
+    FLAG_SET_DEFAULT(UseCompactObjectHeaders, false);
+  }
+  if (UseCompactObjectHeaders && LockingMode != LM_LIGHTWEIGHT) {
+    FLAG_SET_DEFAULT(LockingMode, LM_LIGHTWEIGHT);
+  }
+  if (UseCompactObjectHeaders && !UseCompressedClassPointers) {
+    FLAG_SET_DEFAULT(UseCompressedClassPointers, true);
+  }
+#endif
+
   return JNI_OK;
 }
 
 // Helper class for controlling the lifetime of JavaVMInitArgs
 // objects.  The contents of the JavaVMInitArgs are guaranteed to be
@@ -3661,10 +3666,14 @@
   // Set heap size based on available physical memory
   set_heap_size();
 
   GCConfig::arguments()->initialize();
 
+  if (UseCompressedClassPointers) {
+    CompressedKlassPointers::pre_initialize();
+  }
+
   CDSConfig::initialize();
 
   // Initialize Metaspace flags and alignments
   Metaspace::ergo_initialize();
 
diff a/src/hotspot/share/runtime/arguments.hpp b/src/hotspot/share/runtime/arguments.hpp
--- a/src/hotspot/share/runtime/arguments.hpp
+++ b/src/hotspot/share/runtime/arguments.hpp
@@ -259,11 +259,10 @@
   static bool _ClipInlining;
 
   // GC ergonomics
   static void set_conservative_max_heap_alignment();
   static void set_use_compressed_oops();
-  static void set_use_compressed_klass_ptrs();
   static jint set_ergonomics_flags();
   // Limits the given heap size by the maximum amount of virtual
   // memory this process is currently allowed to use. It also takes
   // the virtual-to-physical ratio of the current GC into account.
   static size_t limit_heap_by_allocatable_memory(size_t size);
diff a/src/hotspot/share/runtime/basicLock.cpp b/src/hotspot/share/runtime/basicLock.cpp
--- a/src/hotspot/share/runtime/basicLock.cpp
+++ b/src/hotspot/share/runtime/basicLock.cpp
@@ -22,20 +22,29 @@
  *
  */
 
 #include "precompiled.hpp"
 #include "oops/oop.inline.hpp"
-#include "runtime/basicLock.hpp"
+#include "runtime/basicLock.inline.hpp"
+#include "runtime/objectMonitor.hpp"
 #include "runtime/synchronizer.hpp"
+#include "utilities/globalDefinitions.hpp"
 
 void BasicLock::print_on(outputStream* st, oop owner) const {
   st->print("monitor");
-  markWord mark_word = displaced_header();
-  if (mark_word.value() != 0) {
-    // Print monitor info if there's an owning oop and it refers to this BasicLock.
-    bool print_monitor_info = (owner != nullptr) && (owner->mark() == markWord::from_pointer((void*)this));
-    mark_word.print_on(st, print_monitor_info);
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    ObjectMonitor* mon = object_monitor_cache();
+    if (mon != nullptr) {
+      mon->print_on(st);
+    }
+  } else {
+    markWord mark_word = displaced_header();
+    if (mark_word.value() != 0) {
+      // Print monitor info if there's an owning oop and it refers to this BasicLock.
+      bool print_monitor_info = (owner != nullptr) && (owner->mark() == markWord::from_pointer((void*)this));
+      mark_word.print_on(st, print_monitor_info);
+    }
   }
 }
 
 void BasicLock::move_to(oop obj, BasicLock* dest) {
   // Check to see if we need to inflate the lock. This is only needed
@@ -80,12 +89,17 @@
       // value is either 0, neutral, or 3.  But with the advent of the
       // store-before-CAS avoidance in fast_lock/compiler_lock_object
       // we can find any flavor mark in the displaced mark.
     }
     dest->set_displaced_header(displaced_header());
+  } else if (LockingMode == LM_LIGHTWEIGHT) {
+    // Preserve the ObjectMonitor*, the cache is cleared when a box is reused
+    // and only read while the lock is held, so no stale ObjectMonitor* is
+    // encountered.
+    dest->set_object_monitor_cache(object_monitor_cache());
   }
 #ifdef ASSERT
   else {
-    dest->set_displaced_header(markWord(badDispHeaderDeopt));
+    dest->set_bad_metadata_deopt();
   }
 #endif
 }
diff a/src/hotspot/share/runtime/basicLock.hpp b/src/hotspot/share/runtime/basicLock.hpp
--- a/src/hotspot/share/runtime/basicLock.hpp
+++ b/src/hotspot/share/runtime/basicLock.hpp
@@ -26,34 +26,50 @@
 #define SHARE_RUNTIME_BASICLOCK_HPP
 
 #include "oops/markWord.hpp"
 #include "runtime/atomic.hpp"
 #include "runtime/handles.hpp"
+#include "utilities/globalDefinitions.hpp"
 #include "utilities/sizes.hpp"
 
 class BasicLock {
   friend class VMStructs;
   friend class JVMCIVMStructs;
  private:
+  // * For LM_MONITOR
+  // Unused.
+  // * For LM_LEGACY
   // This is either the actual displaced header from a locked object, or
   // a sentinel zero value indicating a recursive stack-lock.
-  volatile markWord _displaced_header;
+  // * For LM_LIGHTWEIGHT
+  // Used as a cache the ObjectMonitor* used when locking. Must either
+  // be nullptr or the ObjectMonitor* used when locking.
+  volatile uintptr_t _metadata;
+
+  uintptr_t get_metadata() const { return Atomic::load(&_metadata); }
+  void set_metadata(uintptr_t value) { Atomic::store(&_metadata, value); }
+  static int metadata_offset_in_bytes() { return (int)offset_of(BasicLock, _metadata); }
+
  public:
-  markWord displaced_header() const {
-    return Atomic::load(&_displaced_header);
-  }
+  // LM_MONITOR
+  void set_bad_metadata_deopt() { set_metadata(badDispHeaderDeopt); }
 
-  void set_displaced_header(markWord header) {
-    Atomic::store(&_displaced_header, header);
-  }
+  // LM_LEGACY
+  inline markWord displaced_header() const;
+  inline void set_displaced_header(markWord header);
+  static int displaced_header_offset_in_bytes() { return metadata_offset_in_bytes(); }
+
+  // LM_LIGHTWEIGHT
+  inline ObjectMonitor* object_monitor_cache() const;
+  inline void clear_object_monitor_cache();
+  inline void set_object_monitor_cache(ObjectMonitor* mon);
+  static int object_monitor_cache_offset_in_bytes() { return metadata_offset_in_bytes(); }
 
   void print_on(outputStream* st, oop owner) const;
 
   // move a basic lock (used during deoptimization)
   void move_to(oop obj, BasicLock* dest);
-
-  static int displaced_header_offset_in_bytes() { return (int)offset_of(BasicLock, _displaced_header); }
 };
 
 // A BasicObjectLock associates a specific Java object with a BasicLock.
 // It is currently embedded in an interpreter frame.
 
diff a/src/hotspot/share/runtime/basicLock.inline.hpp b/src/hotspot/share/runtime/basicLock.inline.hpp
--- /dev/null
+++ b/src/hotspot/share/runtime/basicLock.inline.hpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef SHARE_RUNTIME_BASICLOCK_INLINE_HPP
+#define SHARE_RUNTIME_BASICLOCK_INLINE_HPP
+
+#include "runtime/basicLock.hpp"
+
+inline markWord BasicLock::displaced_header() const {
+  assert(LockingMode == LM_LEGACY, "must be");
+  return markWord(get_metadata());
+}
+
+inline void BasicLock::set_displaced_header(markWord header) {
+  assert(LockingMode == LM_LEGACY, "must be");
+  Atomic::store(&_metadata, header.value());
+}
+
+inline ObjectMonitor* BasicLock::object_monitor_cache() const {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  return reinterpret_cast<ObjectMonitor*>(get_metadata());
+}
+
+inline void BasicLock::clear_object_monitor_cache() {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  set_metadata(0);
+}
+
+inline void BasicLock::set_object_monitor_cache(ObjectMonitor* mon) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  set_metadata(reinterpret_cast<uintptr_t>(mon));
+}
+
+#endif // SHARE_RUNTIME_BASICLOCK_INLINE_HPP
diff a/src/hotspot/share/runtime/deoptimization.cpp b/src/hotspot/share/runtime/deoptimization.cpp
--- a/src/hotspot/share/runtime/deoptimization.cpp
+++ b/src/hotspot/share/runtime/deoptimization.cpp
@@ -60,10 +60,11 @@
 #include "prims/jvmtiExport.hpp"
 #include "prims/jvmtiThreadState.hpp"
 #include "prims/methodHandles.hpp"
 #include "prims/vectorSupport.hpp"
 #include "runtime/atomic.hpp"
+#include "runtime/basicLock.inline.hpp"
 #include "runtime/continuation.hpp"
 #include "runtime/continuationEntry.inline.hpp"
 #include "runtime/deoptimization.hpp"
 #include "runtime/escapeBarrier.hpp"
 #include "runtime/fieldDescriptor.hpp"
@@ -72,10 +73,12 @@
 #include "runtime/handles.inline.hpp"
 #include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/javaThread.hpp"
 #include "runtime/jniHandles.inline.hpp"
 #include "runtime/keepStackGCProcessed.hpp"
+#include "runtime/lightweightSynchronizer.hpp"
+#include "runtime/lockStack.inline.hpp"
 #include "runtime/objectMonitor.inline.hpp"
 #include "runtime/osThread.hpp"
 #include "runtime/safepointVerifiers.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "runtime/signature.hpp"
@@ -90,10 +93,11 @@
 #include "runtime/vframeArray.hpp"
 #include "runtime/vframe_hp.hpp"
 #include "runtime/vmOperations.hpp"
 #include "utilities/checkedCast.hpp"
 #include "utilities/events.hpp"
+#include "utilities/globalDefinitions.hpp"
 #include "utilities/growableArray.hpp"
 #include "utilities/macros.hpp"
 #include "utilities/preserveException.hpp"
 #include "utilities/xmlstream.hpp"
 #if INCLUDE_JFR
@@ -1647,26 +1651,41 @@
           if (mark.has_monitor()) {
             // defer relocking if the deoptee thread is currently waiting for obj
             ObjectMonitor* waiting_monitor = deoptee_thread->current_waiting_monitor();
             if (waiting_monitor != nullptr && waiting_monitor->object() == obj()) {
               assert(fr.is_deoptimized_frame(), "frame must be scheduled for deoptimization");
-              mon_info->lock()->set_displaced_header(markWord::unused_mark());
+              if (LockingMode == LM_LEGACY) {
+                mon_info->lock()->set_displaced_header(markWord::unused_mark());
+              } else if (LockingMode == LM_LIGHTWEIGHT) {
+                mon_info->lock()->clear_object_monitor_cache();
+              }
+#ifdef ASSERT
+              else {
+                assert(LockingMode == LM_MONITOR, "must be");
+                mon_info->lock()->set_bad_metadata_deopt();
+              }
+#endif
               JvmtiDeferredUpdates::inc_relock_count_after_wait(deoptee_thread);
               continue;
             }
           }
         }
+        BasicLock* lock = mon_info->lock();
         if (LockingMode == LM_LIGHTWEIGHT) {
           // We have lost information about the correct state of the lock stack.
-          // Inflate the locks instead. Enter then inflate to avoid races with
-          // deflation.
-          ObjectSynchronizer::enter_for(obj, nullptr, deoptee_thread);
+          // Entering may create an invalid lock stack. Inflate the lock if it
+          // was fast_locked to restore the valid lock stack.
+          ObjectSynchronizer::enter_for(obj, lock, deoptee_thread);
+          if (deoptee_thread->lock_stack().contains(obj())) {
+            LightweightSynchronizer::inflate_fast_locked_object(obj(), deoptee_thread, thread,
+                                                                ObjectSynchronizer::InflateCause::inflate_cause_vm_internal);
+          }
           assert(mon_info->owner()->is_locked(), "object must be locked now");
-          ObjectMonitor* mon = ObjectSynchronizer::inflate_for(deoptee_thread, obj(), ObjectSynchronizer::inflate_cause_vm_internal);
-          assert(mon->owner() == deoptee_thread, "must be");
+          assert(obj->mark().has_monitor(), "must be");
+          assert(!deoptee_thread->lock_stack().contains(obj()), "must be");
+          assert(LightweightSynchronizer::read_monitor(thread, obj())->owner() == deoptee_thread, "must be");
         } else {
-          BasicLock* lock = mon_info->lock();
           ObjectSynchronizer::enter_for(obj, lock, deoptee_thread);
           assert(mon_info->owner()->is_locked(), "object must be locked now");
         }
       }
     }
diff a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp
--- a/src/hotspot/share/runtime/globals.hpp
+++ b/src/hotspot/share/runtime/globals.hpp
@@ -126,10 +126,15 @@
                                                                             \
   product(bool, UseCompressedClassPointers, true,                           \
           "Use 32-bit class pointers in 64-bit VM. "                        \
           "lp64_product means flag is always constant in 32 bit VM")        \
                                                                             \
+  product(bool, UseCompactObjectHeaders, false, EXPERIMENTAL,               \
+          "Use compact 64-bit object headers in 64-bit VM")                 \
+                                                                            \
+  develop(int, TinyClassPointerShift, 0, "")                                \
+                                                                            \
   product(int, ObjectAlignmentInBytes, 8,                                   \
           "Default object alignment in bytes, 8 is minimum")                \
           range(8, 256)                                                     \
           constraint(ObjectAlignmentInBytesConstraintFunc, AtParse)
 
@@ -142,10 +147,11 @@
                            product_pd,                                      \
                            range,                                           \
                            constraint)
 const bool UseCompressedOops = false;
 const bool UseCompressedClassPointers = false;
+const bool UseCompactObjectHeaders = false;
 const int ObjectAlignmentInBytes = 8;
 
 #endif // _LP64
 
 #define RUNTIME_FLAGS(develop,                                              \
@@ -1978,10 +1984,30 @@
           "0: monitors only (LM_MONITOR), "                                 \
           "1: monitors & legacy stack-locking (LM_LEGACY), "                \
           "2: monitors & new lightweight locking (LM_LIGHTWEIGHT, default)") \
           range(0, 2)                                                       \
                                                                             \
+  product(bool, OMUseC2Cache, true, "")                                     \
+                                                                            \
+  product(bool, OMC2UnrollCacheLookupLoopTail, true, "")                    \
+                                                                            \
+  product(int, OMC2UnrollCacheEntries, 0, "")                               \
+          range(0, OMCache::CAPACITY)                                       \
+                                                                            \
+  product(int, OMCacheSize, 8, "")                                          \
+          range(0, OMCache::CAPACITY)                                       \
+                                                                            \
+  product(bool, OMShrinkCHT, false, "")                                     \
+                                                                            \
+  product(int, OMSpins, 20, "")                                             \
+                                                                            \
+  product(int, OMYields, 5, "")                                             \
+                                                                            \
+  product(bool, OMCacheHitRate, false, "")                                  \
+                                                                            \
+  product(bool, OMRecursiveFastPath, true, "Inflated recursion check first")\
+                                                                            \
   product(uint, TrimNativeHeapInterval, 0,                                  \
           "Interval, in ms, at which the JVM will trim the native heap if " \
           "the platform supports that. Lower values will reclaim memory "   \
           "more eagerly at the cost of higher overhead. A value of 0 "      \
           "(default) disables native heap trimming.")                       \
diff a/src/hotspot/share/runtime/javaThread.cpp b/src/hotspot/share/runtime/javaThread.cpp
--- a/src/hotspot/share/runtime/javaThread.cpp
+++ b/src/hotspot/share/runtime/javaThread.cpp
@@ -96,10 +96,11 @@
 #include "services/threadService.hpp"
 #include "utilities/copy.hpp"
 #include "utilities/defaultStream.hpp"
 #include "utilities/dtrace.hpp"
 #include "utilities/events.hpp"
+#include "utilities/globalDefinitions.hpp"
 #include "utilities/macros.hpp"
 #include "utilities/preserveException.hpp"
 #include "utilities/spinYield.hpp"
 #include "utilities/vmError.hpp"
 #if INCLUDE_JVMCI
@@ -502,11 +503,12 @@
 
   _class_to_be_initialized(nullptr),
 
   _SleepEvent(ParkEvent::Allocate(this)),
 
-  _lock_stack(this) {
+  _lock_stack(this),
+  _om_cache(this) {
   set_jni_functions(jni_functions());
 
 #if INCLUDE_JVMCI
   assert(_jvmci._implicit_exception_pc == nullptr, "must be");
   if (JVMCICounterSize > 0) {
@@ -803,10 +805,12 @@
   elapsedTimer _timer_exit_phase1;
   elapsedTimer _timer_exit_phase2;
   elapsedTimer _timer_exit_phase3;
   elapsedTimer _timer_exit_phase4;
 
+  om_clear_monitor_cache();
+
   if (log_is_enabled(Debug, os, thread, timer)) {
     _timer_exit_phase1.start();
   }
 
   HandleMark hm(this);
diff a/src/hotspot/share/runtime/javaThread.hpp b/src/hotspot/share/runtime/javaThread.hpp
--- a/src/hotspot/share/runtime/javaThread.hpp
+++ b/src/hotspot/share/runtime/javaThread.hpp
@@ -58,10 +58,11 @@
 class JvmtiDeferredUpdates;
 class JvmtiSampledObjectAllocEventCollector;
 class JvmtiThreadState;
 
 class Metadata;
+class ObjectMonitor;
 class OopHandleList;
 class OopStorage;
 class OSThread;
 
 class ThreadsList;
@@ -1153,21 +1154,46 @@
   // the Java code in Object::wait which are not present in RawMonitorWait.
   bool get_and_clear_interrupted();
 
 private:
   LockStack _lock_stack;
+  OMCache _om_cache;
 
 public:
   LockStack& lock_stack() { return _lock_stack; }
+  size_t _unlocked_inflation = 0;
+  size_t _recursive_inflation = 0;
+  size_t _contended_recursive_inflation = 0;
+  size_t _contended_inflation = 0;
+  size_t _wait_inflation = 0;
+  size_t _lock_stack_inflation = 0;
+
+  size_t _lock_lookup = 0;
+  size_t _lock_hit = 0;
+  size_t _unlock_lookup = 0;
+  size_t _unlock_hit = 0;
+
+  static ByteSize lock_lookup_offset() { return byte_offset_of(JavaThread, _lock_lookup); }
+  static ByteSize lock_hit_offset() { return byte_offset_of(JavaThread, _lock_hit); }
+  static ByteSize unlock_lookup_offset() { return byte_offset_of(JavaThread, _unlock_lookup); }
+  static ByteSize unlock_hit_offset() { return byte_offset_of(JavaThread, _unlock_hit); }
 
   static ByteSize lock_stack_offset()      { return byte_offset_of(JavaThread, _lock_stack); }
   // Those offsets are used in code generators to access the LockStack that is embedded in this
   // JavaThread structure. Those accesses are relative to the current thread, which
   // is typically in a dedicated register.
   static ByteSize lock_stack_top_offset()  { return lock_stack_offset() + LockStack::top_offset(); }
   static ByteSize lock_stack_base_offset() { return lock_stack_offset() + LockStack::base_offset(); }
 
+
+  static ByteSize om_cache_offset()        { return byte_offset_of(JavaThread, _om_cache); }
+  static ByteSize om_cache_oops_offset()   { return om_cache_offset() + OMCache::entries(); }
+
+  void om_set_monitor_cache(ObjectMonitor* monitor);
+  void om_clear_monitor_cache();
+  ObjectMonitor* om_get_from_monitor_cache(oop obj);
+
   static OopStorage* thread_oop_storage();
 
   static void verify_cross_modify_fence_failure(JavaThread *thread) PRODUCT_RETURN;
 
   // Helper function to create the java.lang.Thread object for a
diff a/src/hotspot/share/runtime/javaThread.inline.hpp b/src/hotspot/share/runtime/javaThread.inline.hpp
--- a/src/hotspot/share/runtime/javaThread.inline.hpp
+++ b/src/hotspot/share/runtime/javaThread.inline.hpp
@@ -28,19 +28,25 @@
 
 #include "runtime/javaThread.hpp"
 
 #include "classfile/javaClasses.hpp"
 #include "gc/shared/tlab_globals.hpp"
+#include "logging/log.hpp"
+#include "memory/resourceArea.hpp"
 #include "memory/universe.hpp"
 #include "oops/instanceKlass.hpp"
 #include "oops/oopHandle.inline.hpp"
 #include "runtime/atomic.hpp"
 #include "runtime/continuation.hpp"
 #include "runtime/continuationEntry.inline.hpp"
 #include "runtime/nonJavaThread.hpp"
+#include "runtime/objectMonitor.inline.hpp"
 #include "runtime/orderAccess.hpp"
 #include "runtime/safepoint.hpp"
+#include "runtime/synchronizer.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "utilities/sizes.hpp"
 
 inline void JavaThread::set_suspend_flag(SuspendFlags f) {
   uint32_t flags;
   do {
     flags = _suspend_flags;
@@ -237,6 +243,72 @@
 
 inline InstanceKlass* JavaThread::class_to_be_initialized() const {
   return _class_to_be_initialized;
 }
 
+inline void JavaThread::om_set_monitor_cache(ObjectMonitor* monitor) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  assert(monitor != nullptr, "use om_clear_monitor_cache to clear");
+  assert(this == current() || monitor->owner_raw() == this, "only add owned monitors for other threads");
+  assert(this == current() || is_obj_deopt_suspend(), "thread must not run concurrently");
+
+  _om_cache.set_monitor(monitor);
+}
+
+inline void JavaThread::om_clear_monitor_cache() {
+  if (LockingMode != LM_LIGHTWEIGHT) {
+    return;
+  }
+
+  _om_cache.clear();
+
+  LogTarget(Info, monitorinflation, thread) lt;
+  if (!lt.is_enabled()) {
+    return;
+  }
+
+  ResourceMark rm;
+
+  if (_unlocked_inflation != 0 ||
+      _recursive_inflation != 0 ||
+      _contended_recursive_inflation != 0 ||
+      _contended_inflation != 0 ||
+      _wait_inflation != 0 ||
+      _lock_stack_inflation != 0) {
+    lt.print("Mon: %8zu Rec: %8zu CRec: %8zu Cont: %8zu Wait: %8zu Stack: %8zu Thread: %s",
+             _unlocked_inflation,
+             _recursive_inflation,
+             _contended_recursive_inflation,
+             _contended_inflation,
+             _wait_inflation,
+             _lock_stack_inflation,
+             name());
+  }
+  _unlocked_inflation            = 0;
+  _recursive_inflation           = 0;
+  _contended_recursive_inflation = 0;
+  _contended_inflation           = 0;
+  _wait_inflation                = 0;
+  _lock_stack_inflation          = 0;
+
+  if (_lock_lookup != 0 ||
+      _unlock_lookup != 0) {
+    const double lock_hit_rate = (double)_lock_hit / (double)_lock_lookup * 100;
+    const double unlock_hit_rate = (double)_unlock_hit / (double)_unlock_lookup * 100;
+    lt.print("Lock: %3.2lf %% [%6zu / %6zu] Unlock: %3.2lf %% [%6zu / %6zu] Thread: %s",
+             lock_hit_rate, _lock_hit, _lock_lookup,
+             unlock_hit_rate, _unlock_hit, _unlock_lookup,
+             name());
+  }
+  _lock_hit = 0;
+  _lock_lookup = 0;
+  _unlock_hit = 0;
+  _unlock_lookup = 0;
+}
+
+inline ObjectMonitor* JavaThread::om_get_from_monitor_cache(oop obj) {
+  assert(obj != nullptr, "do not look for null objects");
+  assert(this == current(), "only get own thread locals");
+  return _om_cache.get_monitor(obj);
+}
+
 #endif // SHARE_RUNTIME_JAVATHREAD_INLINE_HPP
diff a/src/hotspot/share/runtime/lightweightSynchronizer.cpp b/src/hotspot/share/runtime/lightweightSynchronizer.cpp
--- /dev/null
+++ b/src/hotspot/share/runtime/lightweightSynchronizer.cpp
@@ -0,0 +1,1063 @@
+/*
+ * Copyright (c) 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+
+#include "classfile/vmSymbols.hpp"
+#include "javaThread.inline.hpp"
+#include "jfrfiles/jfrEventClasses.hpp"
+#include "logging/log.hpp"
+#include "logging/logStream.hpp"
+#include "memory/resourceArea.hpp"
+#include "oops/oop.inline.hpp"
+#include "runtime/basicLock.inline.hpp"
+#include "runtime/globals_extension.hpp"
+#include "runtime/javaThread.hpp"
+#include "runtime/lightweightSynchronizer.hpp"
+#include "runtime/lockStack.inline.hpp"
+#include "runtime/mutexLocker.hpp"
+#include "runtime/objectMonitor.inline.hpp"
+#include "runtime/perfData.inline.hpp"
+#include "runtime/safepointVerifiers.hpp"
+#include "runtime/synchronizer.hpp"
+#include "utilities/concurrentHashTable.inline.hpp"
+#include "utilities/globalDefinitions.hpp"
+
+
+//
+// Lightweight synchronization.
+//
+// When the lightweight synchronization needs to use a monitor the link
+// between the object and the monitor is stored in a concurrent hash table
+// instead of in the mark word. This has the benefit that it further decouples
+// the mark word from the synchronization code.
+//
+
+// ConcurrentHashTable storing links from objects to ObjectMonitors
+class ObjectMonitorWorld : public CHeapObj<mtOMWorld> {
+  struct Config {
+    using Value = ObjectMonitor*;
+    static uintx get_hash(Value const& value, bool* is_dead) {
+      return (uintx)value->hash();
+    }
+    static void* allocate_node(void* context, size_t size, Value const& value) {
+      return AllocateHeap(size, mtOMWorld);
+    };
+    static void free_node(void* context, void* memory, Value const& value) {
+      FreeHeap(memory);
+    }
+  };
+  using ConcurrentTable = ConcurrentHashTable<Config, mtOMWorld>;
+
+  ConcurrentTable* _table;
+  volatile bool _resize;
+  uint32_t _shrink_count;
+
+  class Lookup : public StackObj {
+    oop _obj;
+
+  public:
+    Lookup(oop obj) : _obj(obj) {}
+
+    uintx get_hash() const {
+      uintx hash = _obj->mark().hash();
+      assert(hash != 0, "should have a hash");
+      return hash;
+    }
+
+    bool equals(ObjectMonitor** value) {
+      // The entry is going to be removed soon.
+      assert(*value != nullptr, "must be");
+      return (*value)->object_refers_to(_obj);
+    }
+
+    bool is_dead(ObjectMonitor** value) {
+      assert(*value != nullptr, "must be");
+      return (*value)->object_is_cleared();
+    }
+  };
+
+  class LookupMonitor : public StackObj {
+    ObjectMonitor* _monitor;
+
+  public:
+    LookupMonitor(ObjectMonitor* monitor) : _monitor(monitor) {}
+
+    uintx get_hash() const {
+      return _monitor->hash();
+    }
+
+    bool equals(ObjectMonitor** value) {
+      return (*value) == _monitor;
+    }
+
+    bool is_dead(ObjectMonitor** value) {
+      assert(*value != nullptr, "must be");
+      return (*value)->object_is_dead();
+    }
+  };
+
+  static size_t max_log_size() {
+    // TODO[OMWorld]: Evaluate the max size.
+    // TODO[OMWorld]: Need to fix init order to use Universe::heap()->max_capacity();
+    //                Using MaxHeapSize directly this early may be wrong, and there
+    //                are definitely rounding errors (alignment).
+    const size_t max_capacity = MaxHeapSize;
+    const size_t min_object_size = CollectedHeap::min_dummy_object_size() * HeapWordSize;
+    const size_t max_objects = max_capacity / MAX2(MinObjAlignmentInBytes, checked_cast<int>(min_object_size));
+    const size_t log_max_objects = log2i_graceful(max_objects);
+
+    return MAX2(MIN2<size_t>(SIZE_BIG_LOG2, log_max_objects), min_log_size());
+  }
+
+  static size_t min_log_size() {
+    // TODO[OMWorld]: Evaluate the min size, currently ~= log(AvgMonitorsPerThreadEstimate default)
+    return 10;
+  }
+
+  template<typename V>
+  static size_t clamp_log_size(V log_size) {
+    return MAX2(MIN2(log_size, checked_cast<V>(max_log_size())), checked_cast<V>(min_log_size()));
+  }
+
+  static size_t initial_log_size() {
+    const size_t estimate = log2i(MAX2(os::processor_count(), 1)) + log2i(MAX2(AvgMonitorsPerThreadEstimate, size_t(1)));
+    return clamp_log_size(estimate);
+  }
+
+  static size_t grow_hint () {
+    // TODO[OMWorld]: Evaluate why 4 is a good grow hint.
+    //                Have seen grow hint hits when lower with a
+    //                load factor as low as 0.1. (Grow Hint = 3)
+    // TODO[OMWorld]: Evaluate the hash code used, are large buckets
+    //                expected even with a low load factor. Or is it
+    //                something with the hashing used.
+    return ConcurrentTable::DEFAULT_GROW_HINT;
+  }
+
+  static size_t log_shrink_difference() {
+    // TODO[OMWorld]: Evaluate shrink heuristics, currently disabled by
+    //                default, and only really shrinks if AvgMonitorsPerThreadEstimate
+    //                is also set to a none default value
+    return 2;
+  }
+
+public:
+  ObjectMonitorWorld()
+  : _table(new ConcurrentTable(initial_log_size(), max_log_size(), grow_hint())),
+    _resize(false),
+    _shrink_count(0) {}
+
+  void verify_monitor_get_result(oop obj, ObjectMonitor* monitor) {
+#ifdef ASSERT
+    if (SafepointSynchronize::is_at_safepoint()) {
+      bool has_monitor = obj->mark().has_monitor();
+      assert(has_monitor == (monitor != nullptr),
+          "Inconsistency between markWord and OMW table has_monitor: %s monitor: " PTR_FORMAT,
+          BOOL_TO_STR(has_monitor), p2i(monitor));
+    }
+#endif
+  }
+
+  ObjectMonitor* monitor_get(Thread* current, oop obj) {
+    ObjectMonitor* result = nullptr;
+    Lookup lookup_f(obj);
+    auto found_f = [&](ObjectMonitor** found) {
+      assert((*found)->object_peek() == obj, "must be");
+      result = *found;
+    };
+    _table->get(current, lookup_f, found_f);
+    verify_monitor_get_result(obj, result);
+    return result;
+  }
+
+  void try_notify_grow() {
+    if (!_table->is_max_size_reached() && !Atomic::load(&_resize)) {
+      Atomic::store(&_resize, true);
+      if (MonitorDeflation_lock->try_lock()) {
+        MonitorDeflation_lock->notify();
+        MonitorDeflation_lock->unlock();
+      }
+    }
+  }
+
+  void set_table_max(JavaThread* current) {
+    while (!_table->is_max_size_reached()) {
+      _table->grow(current);
+    }
+  }
+
+  bool needs_shrink(size_t log_target, size_t log_size) {
+    return OMShrinkCHT && log_target + log_shrink_difference() <= log_size;
+  }
+
+  bool needs_grow(size_t log_target, size_t log_size) {
+    return log_size < log_target;
+  }
+
+  bool needs_resize(JavaThread* current, size_t ceiling, size_t count, size_t max) {
+    const size_t log_size = _table->get_size_log2(current);
+    const int log_ceiling = log2i_graceful(ceiling);
+    const int log_max = log2i_graceful(max);
+    const size_t log_count = log2i(MAX2(count, size_t(1)));
+    const size_t log_target = clamp_log_size(MAX2(log_ceiling, log_max) + 2);
+
+    return needs_grow(log_target, log_size) || needs_shrink(log_target, log_size) || Atomic::load(&_resize);
+  }
+
+  bool resize(JavaThread* current, size_t ceiling, size_t count, size_t max) {
+    const size_t log_size = _table->get_size_log2(current);
+    const int log_ceiling = log2i_graceful(ceiling);
+    const int log_max = log2i_graceful(max);
+    const size_t log_count = log2i(MAX2(count, size_t(1)));
+    const size_t log_target = clamp_log_size(MAX2(log_ceiling, log_max) + 2);
+    LogTarget(Info, monitorinflation) lt;
+
+    auto print_table_stats = [&]() {
+      ResourceMark rm;
+      LogStream ls(lt);
+      auto vs_f = [](Config::Value* v) { return sizeof(Config::Value); };
+      _table->statistics_to(current, vs_f, &ls, "ObjectMonitorWorld");
+    };
+
+    bool success = true;
+
+    if (needs_grow(log_target, log_size)) {
+      // Grow
+      lt.print("Growing to %02zu->%02zu", log_size, log_target);
+      success = _table->grow(current, log_target);
+      print_table_stats();
+    } else if (!_table->is_max_size_reached() && Atomic::load(&_resize)) {
+      lt.print("WARNING: Getting resize hints with Size: %02zu Ceiling: %2i Target: %02zu", log_size, log_ceiling, log_target);
+      print_table_stats();
+      success = false;
+    }
+
+    if (needs_shrink(log_target, log_size)) {
+      _shrink_count++;
+      // Shrink
+      lt.print("Shrinking to %02zu->%02zu", log_size, log_target);
+      success = _table->shrink(current, log_target);
+      print_table_stats();
+    }
+
+    if (success) {
+      Atomic::store(&_resize, _table->is_max_size_reached());
+    }
+
+    return success;
+  }
+
+  ObjectMonitor* monitor_put_get(Thread* current, ObjectMonitor* monitor, oop obj) {
+    // Enter the monitor into the concurrent hashtable.
+    ObjectMonitor* result = monitor;
+    Lookup lookup_f(obj);
+    auto found_f = [&](ObjectMonitor** found) {
+      assert((*found)->object_peek() == obj, "must be");
+      result = *found;
+    };
+    bool grow;
+    _table->insert_get(current, lookup_f, monitor, found_f, &grow);
+    verify_monitor_get_result(obj, result);
+    if (grow) {
+      try_notify_grow();
+    }
+    return result;
+  }
+
+  bool remove_monitor_entry(Thread* current, ObjectMonitor* monitor) {
+    LookupMonitor lookup_f(monitor);
+    return _table->remove(current, lookup_f);
+  }
+
+  bool contains_monitor(Thread* current, ObjectMonitor* monitor) {
+    LookupMonitor lookup_f(monitor);
+    bool result = false;
+    auto found_f = [&](ObjectMonitor** found) {
+      result = true;
+    };
+    _table->get(current, lookup_f, found_f);
+    return result;
+  }
+
+  void print_on(outputStream* st) {
+    auto printer = [&] (ObjectMonitor** entry) {
+       ObjectMonitor* om = *entry;
+       oop obj = om->object_peek();
+       st->print("monitor " PTR_FORMAT " ", p2i(om));
+       st->print("object " PTR_FORMAT, p2i(obj));
+       assert(obj->mark().hash() == om->hash(), "hash must match");
+       st->cr();
+       return true;
+    };
+    if (SafepointSynchronize::is_at_safepoint()) {
+      _table->do_safepoint_scan(printer);
+    } else {
+      _table->do_scan(Thread::current(), printer);
+    }
+  }
+};
+
+ObjectMonitorWorld* LightweightSynchronizer::_omworld = nullptr;
+
+ObjectMonitor* LightweightSynchronizer::get_or_insert_monitor_from_table(oop object, JavaThread* current, bool try_read, bool* inserted) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+
+  if (try_read) {
+    ObjectMonitor* monitor = read_monitor(current, object);
+    if (monitor != nullptr) {
+      *inserted = false;
+      return monitor;
+    }
+  }
+
+  ObjectMonitor* alloced_monitor = new ObjectMonitor(object);
+  alloced_monitor->set_owner_anonymous();
+
+  // Try insert monitor
+  ObjectMonitor* monitor = add_monitor(current, alloced_monitor, object);
+
+  *inserted = alloced_monitor == monitor;
+  if (!*inserted) {
+    delete alloced_monitor;
+  }
+
+  return monitor;
+}
+
+static void log_inflate(Thread* current, oop object, const ObjectSynchronizer::InflateCause cause) {
+  if (log_is_enabled(Trace, monitorinflation)) {
+    ResourceMark rm(current);
+    log_info(monitorinflation)("inflate(has_locker): object=" INTPTR_FORMAT ", mark="
+                               INTPTR_FORMAT ", type='%s' cause %s", p2i(object),
+                               object->mark().value(), object->klass()->external_name(),
+                               ObjectSynchronizer::inflate_cause_name(cause));
+  }
+}
+
+static void post_monitor_inflate_event(EventJavaMonitorInflate* event,
+                                       const oop obj,
+                                       ObjectSynchronizer::InflateCause cause) {
+  assert(event != nullptr, "invariant");
+  event->set_monitorClass(obj->klass());
+  event->set_address((uintptr_t)(void*)obj);
+  event->set_cause((u1)cause);
+  event->commit();
+}
+
+ObjectMonitor* LightweightSynchronizer::get_or_insert_monitor(oop object, JavaThread* current, const ObjectSynchronizer::InflateCause cause, bool try_read) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+
+  EventJavaMonitorInflate event;
+
+  bool inserted;
+  ObjectMonitor* monitor = get_or_insert_monitor_from_table(object, current, try_read, &inserted);
+
+  if (inserted) {
+    // Hopefully the performance counters are allocated on distinct
+    // cache lines to avoid false sharing on MP systems ...
+    OM_PERFDATA_OP(Inflations, inc());
+    log_inflate(current, object, cause);
+    if (event.should_commit()) {
+      post_monitor_inflate_event(&event, object, cause);
+    }
+
+    // The monitor has an anonymous owner so it is safe from async deflation.
+    ObjectSynchronizer::_in_use_list.add(monitor);
+  }
+
+  return monitor;
+}
+
+// Add the hashcode to the monitor to match the object and put it in the hashtable.
+ObjectMonitor* LightweightSynchronizer::add_monitor(JavaThread* current, ObjectMonitor* monitor, oop obj) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  assert(obj == monitor->object(), "must be");
+
+  intptr_t hash = obj->mark().hash();
+  assert(hash != 0, "must be set when claiming the object monitor");
+  monitor->set_hash(hash);
+
+  return _omworld->monitor_put_get(current, monitor, obj);
+}
+
+bool LightweightSynchronizer::remove_monitor(Thread* current, oop obj, ObjectMonitor* monitor) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  assert(monitor->object_peek() == obj, "must be, cleared objects are removed by is_dead");
+
+  return _omworld->remove_monitor_entry(current, monitor);
+}
+
+void LightweightSynchronizer::deflate_mark_word(oop obj) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must use lightweight locking");
+
+  markWord mark = obj->mark_acquire();
+  assert(!mark.has_no_hash(), "obj with inflated monitor must have had a hash");
+
+  while (mark.has_monitor()) {
+    const markWord new_mark = mark.clear_lock_bits().set_unlocked();
+    mark = obj->cas_set_mark(new_mark, mark);
+  }
+}
+
+void LightweightSynchronizer::initialize() {
+  _omworld = new ObjectMonitorWorld();
+
+  if (!FLAG_IS_CMDLINE(AvgMonitorsPerThreadEstimate)) {
+    // This is updated after ceiling is set and ObjectMonitorWorld is created;
+    // TODO[OMWorld]: Clean this up and find a good initial ceiling,
+    //                and initial HashTable size
+    FLAG_SET_ERGO(AvgMonitorsPerThreadEstimate, 0);
+  }
+}
+
+void LightweightSynchronizer::set_table_max(JavaThread* current) {
+  if (LockingMode != LM_LIGHTWEIGHT) {
+    return;
+  }
+  _omworld->set_table_max(current);
+}
+
+bool LightweightSynchronizer::needs_resize(JavaThread *current) {
+  if (LockingMode != LM_LIGHTWEIGHT) {
+    return false;
+  }
+  return _omworld->needs_resize(current,
+                                  ObjectSynchronizer::in_use_list_ceiling(),
+                                  ObjectSynchronizer::_in_use_list.count(),
+                                  ObjectSynchronizer::_in_use_list.max());
+}
+
+bool LightweightSynchronizer::resize_table(JavaThread* current) {
+  if (LockingMode != LM_LIGHTWEIGHT) {
+    return true;
+  }
+  return _omworld->resize(current,
+                          ObjectSynchronizer::in_use_list_ceiling(),
+                          ObjectSynchronizer::_in_use_list.count(),
+                          ObjectSynchronizer::_in_use_list.max());
+}
+
+class LockStackInflateContendedLocks : private OopClosure {
+ private:
+  oop _contended_oops[LockStack::CAPACITY];
+  int _length;
+
+  void do_oop(oop* o) final {
+    oop obj = *o;
+    if (obj->mark_acquire().has_monitor()) {
+      if (_length > 0 && _contended_oops[_length-1] == obj) {
+        // assert(VM_Version::supports_recursive_lightweight_locking(), "must be");
+        // Recursive
+        return;
+      }
+      _contended_oops[_length++] = obj;
+    }
+  }
+
+  void do_oop(narrowOop* o) final {
+    ShouldNotReachHere();
+  }
+
+ public:
+  LockStackInflateContendedLocks() :
+    _contended_oops(),
+    _length(0) {};
+
+  void inflate(JavaThread* locking_thread, JavaThread* current) {
+    locking_thread->lock_stack().oops_do(this);
+    for (int i = 0; i < _length; i++) {
+      LightweightSynchronizer::
+        inflate_fast_locked_object(_contended_oops[i], locking_thread, current, ObjectSynchronizer::inflate_cause_vm_internal);
+    }
+  }
+};
+
+void LightweightSynchronizer::ensure_lock_stack_space(JavaThread* current) {
+  assert(current == JavaThread::current(), "must be");
+  LockStack& lock_stack = current->lock_stack();
+
+  // Make room on lock_stack
+  if (lock_stack.is_full()) {
+    // Inflate contented objects
+    LockStackInflateContendedLocks().inflate(current, current);
+    if (lock_stack.is_full()) {
+      // Inflate the oldest object
+      inflate_fast_locked_object(lock_stack.bottom(), current, current, ObjectSynchronizer::inflate_cause_vm_internal);
+    }
+  }
+}
+
+class LightweightSynchronizer::CacheSetter : StackObj {
+  JavaThread* const _thread;
+  BasicLock* const _lock;
+  ObjectMonitor* _monitor;
+
+  NONCOPYABLE(CacheSetter);
+
+public:
+  CacheSetter(JavaThread* thread, BasicLock* lock) :
+    _thread(thread),
+    _lock(lock),
+    _monitor(nullptr) {}
+
+  ~CacheSetter() {
+    if (_monitor != nullptr) {
+      _thread->om_set_monitor_cache(_monitor);
+      _lock->set_object_monitor_cache(_monitor);
+    } else {
+      _lock->clear_object_monitor_cache();
+    }
+  }
+
+  void set_monitor(ObjectMonitor* monitor) {
+    assert(_monitor == nullptr, "only set once");
+    _monitor = monitor;
+  }
+
+};
+
+class VerifyThreadState {
+  bool _no_safepoint;
+  union {
+    struct {} _dummy;
+    NoSafepointVerifier _nsv;
+  };
+
+public:
+  VerifyThreadState(JavaThread* locking_thread, JavaThread* current) : _no_safepoint(locking_thread != current) {
+    assert(current == Thread::current(), "must be");
+    assert(locking_thread == current || locking_thread->is_obj_deopt_suspend(), "locking_thread may not run concurrently");
+    if (_no_safepoint) {
+      ::new (&_nsv) NoSafepointVerifier();
+    }
+  }
+  ~VerifyThreadState() {
+    if (_no_safepoint){
+      _nsv.~NoSafepointVerifier();
+    }
+  }
+};
+
+void LightweightSynchronizer::enter_for(Handle obj, BasicLock* lock, JavaThread* locking_thread) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  JavaThread* current = JavaThread::current();
+  VerifyThreadState vts(locking_thread, current);
+
+  // TODO[OMWorld]: Is this necessary?
+  if (obj->klass()->is_value_based()) {
+    ObjectSynchronizer::handle_sync_on_value_based_class(obj, locking_thread);
+  }
+
+  locking_thread->inc_held_monitor_count();
+
+  CacheSetter cache_setter(locking_thread, lock);
+
+  LockStack& lock_stack = locking_thread->lock_stack();
+
+  ObjectMonitor* monitor = nullptr;
+  if (lock_stack.contains(obj())) {
+    monitor = inflate_fast_locked_object(obj(), locking_thread, current, ObjectSynchronizer::inflate_cause_monitor_enter);
+    bool entered = monitor->enter_for(locking_thread);
+    assert(entered, "recursive ObjectMonitor::enter_for must succeed");
+  } else {
+    // It is assumed that enter_for must enter on an object without contention.
+    // TODO[OMWorld]: We also assume that this re-lock is on either a new never
+    //                inflated monitor, or one that is already locked by the
+    //                locking_thread. Should we have this stricter restriction?
+    monitor = inflate_and_enter(obj(), locking_thread, current, ObjectSynchronizer::inflate_cause_monitor_enter);
+  }
+
+  assert(monitor != nullptr, "LightweightSynchronizer::enter_for must succeed");
+  cache_setter.set_monitor(monitor);
+}
+
+void LightweightSynchronizer::enter(Handle obj, BasicLock* lock, JavaThread* current) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  assert(current == JavaThread::current(), "must be");
+
+  if (obj->klass()->is_value_based()) {
+    ObjectSynchronizer::handle_sync_on_value_based_class(obj, current);
+  }
+
+  current->inc_held_monitor_count();
+
+  CacheSetter cache_setter(current, lock);
+
+  SpinYield spin_yield(0, 2);
+  bool first_time = true;
+
+  LockStack& lock_stack = current->lock_stack();
+
+  if (!lock_stack.is_full() && lock_stack.try_recursive_enter(obj())) {
+    // TODO[OMWorld]: Maybe guard this by the value in the markWord (only is fast locked)
+    //                Currently this is done when exiting. Doing it early could remove,
+    //                LockStack::CAPACITY - 1 slow paths in the best case. But need to fix
+    //                some of the inflation counters for this change.
+
+    // Recursively fast locked
+    return;
+  }
+
+  if (lock_stack.contains(obj())) {
+    ObjectMonitor* monitor = inflate_fast_locked_object(obj(), current, current, ObjectSynchronizer::inflate_cause_monitor_enter);
+    bool entered = monitor->enter(current);
+    assert(entered, "recursive ObjectMonitor::enter must succeed");
+    cache_setter.set_monitor(monitor);
+    return;
+  }
+
+  const int spins = OMSpins;
+  const int yields = OMYields;
+
+  while (true) {
+
+    SpinYield fast_lock_spin_yield(spins, yields);
+    // Fast-locking does not use the 'lock' argument.
+    markWord mark = obj()->mark_acquire();
+    const bool try_spin = !first_time || !mark.has_monitor();
+    for (int attempts = spins + yields; try_spin && attempts > 0; attempts--) {
+      while (mark.is_unlocked()) {
+        ensure_lock_stack_space(current);
+        assert(!lock_stack.is_full(), "must have made room on the lock stack");
+        assert(!lock_stack.contains(obj()), "thread must not already hold the lock");
+        // Try to swing into 'fast-locked' state.
+        markWord locked_mark = mark.set_fast_locked();
+        markWord old_mark = mark;
+        mark = obj()->cas_set_mark(locked_mark, old_mark);
+        if (old_mark == mark) {
+          // Successfully fast-locked, push object to lock-stack and return.
+          lock_stack.push(obj());
+          return;
+        }
+      }
+
+      fast_lock_spin_yield.wait();
+      mark = obj()->mark_acquire();
+    }
+
+    if (!first_time) {
+      spin_yield.wait();
+    }
+
+    ObjectMonitor* monitor = inflate_and_enter(obj(), current, current, ObjectSynchronizer::inflate_cause_monitor_enter);
+    if (monitor != nullptr) {
+      cache_setter.set_monitor(monitor);
+      return;
+    }
+
+    first_time = false;
+  }
+}
+
+void LightweightSynchronizer::exit(oop object, JavaThread* current) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  assert(current == Thread::current(), "must be");
+
+  markWord mark = object->mark();
+  assert(!mark.is_unlocked(), "must be unlocked");
+
+  LockStack& lock_stack = current->lock_stack();
+  if (mark.is_fast_locked()) {
+    if (lock_stack.try_recursive_exit(object)) {
+      // This is a recursive exit which succeeded
+      return;
+    }
+    if (lock_stack.is_recursive(object)) {
+      // Must inflate recursive locks if try_recursive_exit fails
+      // This happens for un-structured unlocks, could potentially
+      // fix try_recursive_exit to handle these.
+      inflate_fast_locked_object(object, current, current, ObjectSynchronizer::inflate_cause_vm_internal);
+    }
+  }
+
+  // Fast-locking does not use the 'lock' argument.
+  while (mark.is_fast_locked()) {
+    markWord unlocked_mark = mark.set_unlocked();
+    markWord old_mark = mark;
+    mark = object->cas_set_mark(unlocked_mark, old_mark);
+    if (old_mark == mark) {
+      // CAS successful, remove from lock_stack
+      size_t recursion = lock_stack.remove(object) - 1;
+      assert(recursion == 0, "Should not have unlocked here");
+      return;
+    }
+  }
+
+  assert(mark.has_monitor(), "must be");
+  // The monitor is
+  ObjectMonitor* monitor = read_monitor(current, object);
+  if (monitor->is_owner_anonymous()) {
+    assert(current->lock_stack().contains(object), "current must have object on its lock stack");
+    monitor->set_owner_from_anonymous(current);
+    monitor->set_recursions(current->lock_stack().remove(object) - 1);
+    current->_contended_inflation++;
+  }
+
+  monitor->exit(current);
+}
+
+// TODO[OMWorld]: Rename this. No idea what to call it, used by notify/notifyall/wait and jni exit
+ObjectMonitor* LightweightSynchronizer::inflate_locked_or_imse(oop obj, const ObjectSynchronizer::InflateCause cause, TRAPS) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  JavaThread* current = THREAD;
+
+  for(;;) {
+    markWord mark = obj->mark_acquire();
+    if (mark.is_unlocked()) {
+      // No lock, IMSE.
+      THROW_MSG_(vmSymbols::java_lang_IllegalMonitorStateException(),
+                "current thread is not owner", nullptr);
+    }
+
+    if (mark.is_fast_locked()) {
+      if (!current->lock_stack().contains(obj)) {
+        // Fast locked by other thread, IMSE.
+        THROW_MSG_(vmSymbols::java_lang_IllegalMonitorStateException(),
+                  "current thread is not owner", nullptr);
+      } else {
+        // Current thread owns the lock, must inflate
+        return inflate_fast_locked_object(obj, current, current, cause);
+      }
+    }
+
+    assert(mark.has_monitor(), "must be");
+    ObjectMonitor* monitor = read_monitor(current, obj);
+    if (monitor != nullptr) {
+      if (monitor->is_owner_anonymous()) {
+        LockStack& lock_stack = current->lock_stack();
+        if (lock_stack.contains(obj)) {
+          // Current thread owns the lock but someone else inflated
+          // fix owner and pop lock stack
+          monitor->set_owner_from_anonymous(current);
+          monitor->set_recursions(lock_stack.remove(obj) - 1);
+          current->_contended_inflation++;
+        } else {
+          // Fast locked (and inflated) by other thread, or deflation in progress, IMSE.
+          THROW_MSG_(vmSymbols::java_lang_IllegalMonitorStateException(),
+                    "current thread is not owner", nullptr);
+        }
+      }
+      return monitor;
+    }
+  }
+}
+
+ObjectMonitor* LightweightSynchronizer::inflate_fast_locked_object(oop object, JavaThread* locking_thread, JavaThread* current, const ObjectSynchronizer::InflateCause cause) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "only used for lightweight");
+  VerifyThreadState vts(locking_thread, current);
+  assert(locking_thread->lock_stack().contains(object), "locking_thread must have object on its lock stack");
+
+  // Inflating requires a hash code
+  FastHashCode(current, object);
+
+  markWord mark = object->mark_acquire();
+  assert(!mark.is_unlocked(), "Cannot be unlocked");
+
+  ObjectMonitor* monitor;
+
+  for (;;) {
+  // Fetch the monitor from the table
+    monitor = get_or_insert_monitor(object, current, cause, true /* try_read */);
+
+    if (monitor->is_owner_anonymous()) {
+      assert(monitor == read_monitor(current, object), "The monitor must be in the table");
+      // New fresh monitor
+      break;
+    }
+
+    os::naked_yield();
+    assert(monitor->is_being_async_deflated(), "Should be the reason");
+  }
+
+  // Set the mark word; loop to handle concurrent updates to other parts of the mark word
+  while (mark.is_fast_locked()) {
+    mark = object->cas_set_mark(mark.set_has_monitor(), mark);
+  }
+
+  // Indicate that the monitor now has a known owner
+  monitor->set_owner_from_anonymous(locking_thread);
+
+  // Remove the entry from the thread's lock stack
+  monitor->set_recursions(locking_thread->lock_stack().remove(object) - 1);
+
+  if (locking_thread == current) {
+    locking_thread->om_set_monitor_cache(monitor);
+  }
+
+  if (cause == ObjectSynchronizer::inflate_cause_wait) {
+    locking_thread->_wait_inflation++;
+  } else if (cause == ObjectSynchronizer::inflate_cause_monitor_enter) {
+    locking_thread->_recursive_inflation++;
+  } else if (cause == ObjectSynchronizer::inflate_cause_vm_internal) {
+    locking_thread->_lock_stack_inflation++;
+  }
+
+  return monitor;
+}
+
+ObjectMonitor* LightweightSynchronizer::inflate_and_enter(oop object, JavaThread* locking_thread, JavaThread* current, const ObjectSynchronizer::InflateCause cause) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "only used for lightweight");
+  VerifyThreadState vts(locking_thread, current);
+  NoSafepointVerifier nsv;
+
+  // Note: In some paths (deoptimization) the 'current' thread inflates and
+  // enters the lock on behalf of the 'locking_thread' thread.
+
+  // Lightweight monitors require that hash codes are installed first
+  FastHashCode(locking_thread, object);
+
+  ObjectMonitor* monitor = nullptr;
+
+  // Try to get the monitor from the thread-local cache.
+  // There's no need to use the cache if we are locking
+  // on behalf of another thread.
+  if (current == locking_thread) {
+    monitor = current->om_get_from_monitor_cache(object);
+  }
+
+  // Get or create the monitor
+  if (monitor == nullptr) {
+    monitor = get_or_insert_monitor(object, current, cause, true /* try_read */);
+  }
+
+  if (monitor->try_enter(locking_thread)) {
+    return monitor;
+  }
+
+  // Holds is_being_async_deflated() stable throughout this function.
+  ObjectMonitorContentionMark contention_mark(monitor);
+
+  /// First handle the case where the monitor from the table is deflated
+  if (monitor->is_being_async_deflated()) {
+    // The MonitorDeflation thread is deflating the monitor. The locking thread
+    // can either help transition the mark word or yield / spin until further
+    // progress have been made.
+
+    const markWord mark = object->mark_acquire();
+
+    if (mark.has_monitor()) {
+      // Waiting on the deflation thread to remove the deflated monitor from the table.
+      os::naked_yield();
+
+    } else if (mark.is_fast_locked()) {
+      // Some other thread managed to fast-lock the lock, or this is a
+      // recursive lock from the same thread; yield for the deflation
+      // thread to remove the deflated monitor from the table.
+      os::naked_yield();
+
+    } else {
+      assert(mark.is_unlocked(), "Implied");
+      // Retry immediately
+    }
+
+    // Retry
+    return nullptr;
+  }
+
+  for (;;) {
+    const markWord mark = object->mark_acquire();
+    // The mark can be in one of the following states:
+    // *  inflated     - If the ObjectMonitor owner is anonymous
+    //                   and the locking_thread thread owns the object
+    //                   lock, then we make the locking_thread thread
+    //                   the ObjectMonitor owner and remove the
+    //                   lock from the locking_thread thread's lock stack.
+    // *  fast-locked  - Coerce it to inflated from fast-locked.
+    // *  neutral      - Inflate the object. Successful CAS is locked
+
+    // CASE: inflated
+    if (mark.has_monitor()) {
+      LockStack& lock_stack = locking_thread->lock_stack();
+      if (monitor->is_owner_anonymous() && lock_stack.contains(object)) {
+        // The lock is fast-locked by the locking thread,
+        // convert it to a held monitor with a known owner.
+        monitor->set_owner_from_anonymous(locking_thread);
+        monitor->set_recursions(lock_stack.remove(object) - 1);
+        locking_thread->_contended_recursive_inflation++;
+      }
+
+      break; // Success
+    }
+
+    // CASE: fast-locked
+    // Could be fast-locked either by locking_thread or by some other thread.
+    //
+    if (mark.is_fast_locked()) {
+      markWord old_mark = object->cas_set_mark(mark.set_has_monitor(), mark);
+      if (old_mark != mark) {
+        // CAS failed
+        continue;
+      }
+
+      // Success! Return inflated monitor.
+      LockStack& lock_stack = locking_thread->lock_stack();
+      if (lock_stack.contains(object)) {
+        // The lock is fast-locked by the locking thread,
+        // convert it to a held monitor with a known owner.
+        monitor->set_owner_from_anonymous(locking_thread);
+        monitor->set_recursions(lock_stack.remove(object) - 1);
+        locking_thread->_recursive_inflation++;
+      }
+
+      break; // Success
+    }
+
+    // CASE: neutral (unlocked)
+
+    // Catch if the object's header is not neutral (not locked and
+    // not marked is what we care about here).
+    assert(mark.is_neutral(), "invariant: header=" INTPTR_FORMAT, mark.value());
+    markWord old_mark = object->cas_set_mark(mark.set_has_monitor(), mark);
+    if (old_mark != mark) {
+      // CAS failed
+      continue;
+    }
+
+    // Transitioned from unlocked to monitor means locking_thread owns the lock.
+    monitor->set_owner_from_anonymous(locking_thread);
+
+    locking_thread->_unlocked_inflation++;
+
+    return monitor;
+  }
+
+  if (current == locking_thread) {
+    // One round of spinning
+    if (monitor->spin_enter(locking_thread)) {
+      return monitor;
+    }
+
+    // Monitor is contended, take the time befor entering to fix the lock stack.
+    LockStackInflateContendedLocks().inflate(locking_thread, current);
+  }
+
+  // enter can block for safepoints; clear the unhandled object oop
+  PauseNoSafepointVerifier pnsv(&nsv);
+  object = nullptr;
+
+  if (current == locking_thread) {
+    monitor->enter_with_contention_mark(locking_thread, contention_mark);
+  } else {
+    monitor->enter_for_with_contention_mark(locking_thread, contention_mark);
+  }
+
+  return monitor;
+}
+
+void LightweightSynchronizer::deflate_monitor(Thread* current, oop obj, ObjectMonitor* monitor) {
+  if (obj != nullptr) {
+    deflate_mark_word(obj);
+  }
+  bool removed = remove_monitor(current, obj, monitor);
+  if (obj != nullptr) {
+    assert(removed, "Should have removed the entry if obj was alive");
+  }
+}
+
+void LightweightSynchronizer::deflate_anon_monitor(Thread* current, oop obj, ObjectMonitor* monitor) {
+  markWord mark = obj->mark_acquire();
+  assert(!mark.has_no_hash(), "obj with inflated monitor must have had a hash");
+
+  while (mark.has_monitor()) {
+    const markWord new_mark = mark.set_fast_locked();
+    mark = obj->cas_set_mark(new_mark, mark);
+  }
+
+  bool removed = remove_monitor(current, obj, monitor);
+  assert(removed, "Should have removed the entry");
+}
+
+ObjectMonitor* LightweightSynchronizer::read_monitor(Thread* current, oop obj) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  return _omworld->monitor_get(current, obj);
+}
+
+bool LightweightSynchronizer::contains_monitor(Thread* current, ObjectMonitor* monitor) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  return _omworld->contains_monitor(current, monitor);
+}
+
+intptr_t LightweightSynchronizer::FastHashCode(Thread* current, oop obj) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+
+  markWord mark = obj->mark_acquire();
+  for(;;) {
+    intptr_t hash = mark.hash();
+    if (hash != 0) {
+      return hash;
+    }
+
+    hash = ObjectSynchronizer::get_next_hash(current, obj);
+    const markWord old_mark = mark;
+    const markWord new_mark = old_mark.copy_set_hash(hash);
+
+    mark = obj->cas_set_mark(new_mark, old_mark);
+    if (old_mark == mark) {
+      return hash;
+    }
+  }
+}
+
+bool LightweightSynchronizer::quick_enter(oop obj, JavaThread* current, BasicLock* lock) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  assert(current->thread_state() == _thread_in_Java, "must be");
+  assert(obj != nullptr, "must be");
+  NoSafepointVerifier nsv;
+
+  CacheSetter cache_setter(current, lock);
+
+  LockStack& lock_stack = current->lock_stack();
+  if (lock_stack.is_full()) {
+    // Always go into runtime if the lock stack is full.
+    return false;
+  }
+
+  if (lock_stack.try_recursive_enter(obj)) {
+    // Recursive lock successful.
+    current->inc_held_monitor_count();
+    return true;
+  }
+
+  const markWord mark = obj->mark();
+
+  if (mark.has_monitor()) {
+    ObjectMonitor* const monitor = current->om_get_from_monitor_cache(obj);
+
+    if (monitor == nullptr) {
+      // Take the slow-path on a cache miss.
+      return false;
+    }
+
+    if (monitor->try_enter(current)) {
+      // ObjectMonitor enter successful.
+      cache_setter.set_monitor(monitor);
+      current->inc_held_monitor_count();
+      return true;
+    }
+  }
+
+  // Slow-path.
+  return false;
+}
diff a/src/hotspot/share/runtime/lightweightSynchronizer.hpp b/src/hotspot/share/runtime/lightweightSynchronizer.hpp
--- /dev/null
+++ b/src/hotspot/share/runtime/lightweightSynchronizer.hpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef SHARE_RUNTIME_LIGHTWEIGHTSYNCHRONIZER_HPP
+#define SHARE_RUNTIME_LIGHTWEIGHTSYNCHRONIZER_HPP
+
+#include "memory/allStatic.hpp"
+#include "runtime/javaThread.hpp"
+#include "runtime/objectMonitor.hpp"
+#include "runtime/synchronizer.hpp"
+
+class ObjectMonitorWorld;
+
+class LightweightSynchronizer : AllStatic {
+private:
+  static ObjectMonitorWorld* _omworld;
+
+  static ObjectMonitor* get_or_insert_monitor_from_table(oop object, JavaThread* current, bool try_read, bool* inserted);
+  static ObjectMonitor* get_or_insert_monitor(oop object, JavaThread* current, const ObjectSynchronizer::InflateCause cause, bool try_read);
+
+  static ObjectMonitor* add_monitor(JavaThread* current, ObjectMonitor* monitor, oop obj);
+  static bool remove_monitor(Thread* current, oop obj, ObjectMonitor* monitor);
+
+  static void deflate_mark_word(oop object);
+
+  static void ensure_lock_stack_space(JavaThread* current);
+
+  class CacheSetter;
+
+ public:
+  static void initialize();
+
+  static bool needs_resize(JavaThread* current);
+  static bool resize_table(JavaThread* current);
+  static void set_table_max(JavaThread* current);
+
+  static void enter_for(Handle obj, BasicLock* lock, JavaThread* locking_thread);
+  static void enter(Handle obj, BasicLock* lock, JavaThread* current);
+  static void exit(oop object, JavaThread* current);
+
+  static ObjectMonitor* inflate_locked_or_imse(oop object, const ObjectSynchronizer::InflateCause cause, TRAPS);
+  static ObjectMonitor* inflate_fast_locked_object(oop object, JavaThread* locking_thread, JavaThread* current, const ObjectSynchronizer::InflateCause cause);
+  static ObjectMonitor* inflate_and_enter(oop object, JavaThread* locking_thread, JavaThread* current, const ObjectSynchronizer::InflateCause cause);
+
+  static void deflate_monitor(Thread* current, oop obj, ObjectMonitor* monitor);
+  static void deflate_anon_monitor(Thread* current, oop obj, ObjectMonitor* monitor);
+
+  static ObjectMonitor* read_monitor(Thread* current, oop obj);
+
+  static bool contains_monitor(Thread* current, ObjectMonitor* monitor);
+
+  // NOTE: May not cause monitor inflation
+  static intptr_t FastHashCode(Thread* current, oop obj);
+
+  static bool quick_enter(oop obj, JavaThread* current, BasicLock* Lock);
+};
+
+#endif // SHARE_RUNTIME_LIGHTWEIGHTSYNCHRONIZER_HPP
diff a/src/hotspot/share/runtime/lockStack.cpp b/src/hotspot/share/runtime/lockStack.cpp
--- a/src/hotspot/share/runtime/lockStack.cpp
+++ b/src/hotspot/share/runtime/lockStack.cpp
@@ -38,10 +38,11 @@
 #include "utilities/copy.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/globalDefinitions.hpp"
 #include "utilities/growableArray.hpp"
 #include "utilities/ostream.hpp"
+#include "utilities/sizes.hpp"
 
 #include <type_traits>
 
 const int LockStack::lock_stack_offset =      in_bytes(JavaThread::lock_stack_offset());
 const int LockStack::lock_stack_top_offset =  in_bytes(JavaThread::lock_stack_top_offset());
@@ -136,22 +137,28 @@
     }
 
     lock_index = lock_order.length();
   }
 
+  JavaThread* thread = get_thread();
+  Thread* current = Thread::current();
   while (lock_index-- > 0) {
     const oop obj = lock_order.at(lock_index);
     const markWord mark = obj->mark_acquire();
     assert(obj->is_locked(), "must be locked");
+    ObjectMonitor* monitor = nullptr;
+    if (mark.has_monitor()) {
+      monitor = LightweightSynchronizer::read_monitor(current, obj);
+    }
     if (top_index > 0 && obj == _base[top_index - 1]) {
-      assert(mark.is_fast_locked() || mark.monitor()->is_owner_anonymous(),
+      assert(mark.is_fast_locked() || monitor->is_owner_anonymous(),
              "must be fast_locked or inflated by other thread");
       top_index--;
     } else {
       assert(!mark.is_fast_locked(), "must be inflated");
-      assert(mark.monitor()->owner_raw() == get_thread() ||
-             (!leaf_frame && get_thread()->current_waiting_monitor() == mark.monitor()),
+      assert(monitor->owner_raw() == thread ||
+             (!leaf_frame && thread->current_waiting_monitor() == monitor),
              "must be owned by (or waited on by) thread");
       assert(!contains(obj), "must not be on lock_stack");
     }
   }
 }
@@ -166,5 +173,13 @@
     } else {
       st->print_cr("not an oop: " PTR_FORMAT, p2i(o));
     }
   }
 }
+
+OMCache::OMCache(JavaThread* jt) : _entries() {
+  STATIC_ASSERT(std::is_standard_layout<OMCache>::value);
+  STATIC_ASSERT(std::is_standard_layout<OMCache::OMCacheEntry>::value);
+  STATIC_ASSERT(offsetof(OMCache, _null_sentinel) == offsetof(OMCache, _entries) +
+                offsetof(OMCache::OMCacheEntry, _oop) +
+                OMCache::CAPACITY * in_bytes(oop_to_oop_difference()));
+}
diff a/src/hotspot/share/runtime/lockStack.hpp b/src/hotspot/share/runtime/lockStack.hpp
--- a/src/hotspot/share/runtime/lockStack.hpp
+++ b/src/hotspot/share/runtime/lockStack.hpp
@@ -30,14 +30,16 @@
 #include "oops/oopsHierarchy.hpp"
 #include "utilities/globalDefinitions.hpp"
 #include "utilities/sizes.hpp"
 
 class JavaThread;
+class ObjectMonitor;
 class OopClosure;
 class outputStream;
 template<typename>
 class GrowableArray;
+class Thread;
 
 class LockStack {
   friend class LockStackTest;
   friend class VMStructs;
   JVMCI_ONLY(friend class JVMCIVMStructs;)
@@ -81,10 +83,13 @@
 
   // The boundary indicies of the lock-stack.
   static uint32_t start_offset();
   static uint32_t end_offset();
 
+  // Return true if we have room to push n oops onto this lock-stack, false otherwise.
+  inline bool can_push(int n = 1) const;
+
   // Returns true if the lock-stack is full. False otherwise.
   inline bool is_full() const;
 
   // Pushes an oop on this lock-stack.
   inline void push(oop o);
@@ -94,10 +99,13 @@
   inline oop bottom() const;
 
   // Is the lock-stack empty.
   inline bool is_empty() const;
 
+  // Get the oldest oop in the stack
+  inline oop top();
+
   // Check if object is recursive.
   // Precondition: This lock-stack must contain the oop.
   inline bool is_recursive(oop o) const;
 
   // Try recursive enter.
@@ -124,6 +132,31 @@
 
   // Verify Lock Stack consistent with lock order
   void verify_consistent_lock_order(GrowableArray<oop>& lock_order, bool leaf_frame) const NOT_DEBUG_RETURN;
 };
 
+class OMCache {
+  friend class VMStructs;
+public:
+  static constexpr int CAPACITY = 8;
+
+private:
+  struct OMCacheEntry {
+    oop _oop = nullptr;
+    ObjectMonitor* _monitor = nullptr;
+  } _entries[CAPACITY];
+  const oop _null_sentinel = nullptr;
+
+public:
+  static ByteSize entries() { return byte_offset_of(OMCache, _entries); }
+  static constexpr ByteSize oop_to_oop_difference() { return in_ByteSize(sizeof(OMCacheEntry)); }
+  static constexpr ByteSize oop_to_monitor_difference() { return in_ByteSize(sizeof(oop)); }
+
+  explicit OMCache(JavaThread* jt);
+
+  inline ObjectMonitor* get_monitor(oop o);
+  inline void set_monitor(ObjectMonitor* monitor);
+  inline void clear();
+
+};
+
 #endif // SHARE_RUNTIME_LOCKSTACK_HPP
diff a/src/hotspot/share/runtime/lockStack.inline.hpp b/src/hotspot/share/runtime/lockStack.inline.hpp
--- a/src/hotspot/share/runtime/lockStack.inline.hpp
+++ b/src/hotspot/share/runtime/lockStack.inline.hpp
@@ -28,14 +28,19 @@
 #define SHARE_RUNTIME_LOCKSTACK_INLINE_HPP
 
 #include "runtime/lockStack.hpp"
 
 #include "memory/iterator.hpp"
+#include "oops/oop.inline.hpp"
+#include "runtime/globals.hpp"
 #include "runtime/javaThread.hpp"
+#include "runtime/lightweightSynchronizer.hpp"
+#include "runtime/objectMonitor.inline.hpp"
 #include "runtime/safepoint.hpp"
 #include "runtime/stackWatermark.hpp"
 #include "runtime/stackWatermarkSet.inline.hpp"
+#include "runtime/synchronizer.hpp"
 #include "utilities/align.hpp"
 #include "utilities/globalDefinitions.hpp"
 
 inline int LockStack::to_index(uint32_t offset) {
   assert(is_aligned(offset, oopSize), "Bad alignment: %u", offset);
@@ -51,10 +56,14 @@
 
 inline bool LockStack::is_full() const {
   return to_index(_top) == CAPACITY;
 }
 
+inline bool LockStack::can_push(int n) const {
+  return (CAPACITY - to_index(_top)) >= n;
+}
+
 inline bool LockStack::is_owning_thread() const {
   Thread* current = Thread::current();
   if (current->is_Java_thread()) {
     JavaThread* thread = JavaThread::cast(current);
     bool is_owning = &thread->lock_stack() == this;
@@ -78,10 +87,15 @@
 inline oop LockStack::bottom() const {
   assert(to_index(_top) > 0, "must contain an oop");
   return _base[0];
 }
 
+inline oop LockStack::top() {
+  assert(to_index(_top) > 0, "may only call with at least one element in the stack");
+  return _base[to_index(_top) - 1];
+}
+
 inline bool LockStack::is_empty() const {
   return to_index(_top) == 0;
 }
 
 inline bool LockStack::is_recursive(oop o) const {
@@ -220,6 +234,59 @@
     cl->do_oop(&_base[i]);
   }
   verify("post-oops-do");
 }
 
+inline void OMCache::set_monitor(ObjectMonitor *monitor) {
+  const int end = OMCacheSize - 1;
+  if (end < 0) {
+    return;
+  }
+
+  oop obj = monitor->object_peek();
+  assert(obj != nullptr, "must be alive");
+  assert(monitor == LightweightSynchronizer::read_monitor(JavaThread::current(), obj), "must be exist in table");
+
+  OMCacheEntry to_insert = {obj, monitor};
+
+  for (int i = 0; i < end; ++i) {
+    if (_entries[i]._oop == obj ||
+        _entries[i]._monitor == nullptr ||
+        _entries[i]._monitor->is_being_async_deflated()) {
+      // Use stale slot.
+      _entries[i] = to_insert;
+      return;
+    }
+    // Swap with the most recent value.
+    ::swap(to_insert, _entries[i]);
+  }
+  _entries[end] = to_insert;
+}
+
+inline ObjectMonitor* OMCache::get_monitor(oop o) {
+  for (int i = 0; i < OMCacheSize; ++i) {
+    if (_entries[i]._oop == o) {
+      assert(_entries[i]._monitor != nullptr, "monitor must exist");
+      if (_entries[i]._monitor->is_being_async_deflated()) {
+        // Bad monitor
+        // Shift down rest
+        for (; i < OMCacheSize - 1; ++i) {
+          _entries[i] = _entries[i + 1];
+        }
+        // Clear end
+        _entries[i] = {};
+        return nullptr;
+      }
+      return _entries[i]._monitor;
+    }
+  }
+  return nullptr;
+}
+
+inline void OMCache::clear() {
+  for (size_t i = 0 , r = 0; i < CAPACITY; ++i) {
+    // Clear
+    _entries[i] = {};
+  }
+}
+
 #endif // SHARE_RUNTIME_LOCKSTACK_INLINE_HPP
diff a/src/hotspot/share/runtime/monitorDeflationThread.cpp b/src/hotspot/share/runtime/monitorDeflationThread.cpp
--- a/src/hotspot/share/runtime/monitorDeflationThread.cpp
+++ b/src/hotspot/share/runtime/monitorDeflationThread.cpp
@@ -30,13 +30,15 @@
 #include "logging/logStream.hpp"
 #include "memory/universe.hpp"
 #include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/java.hpp"
 #include "runtime/javaCalls.hpp"
+#include "runtime/lightweightSynchronizer.hpp"
 #include "runtime/monitorDeflationThread.hpp"
 #include "runtime/mutexLocker.hpp"
 #include "runtime/synchronizer.hpp"
+#include "utilities/checkedCast.hpp"
 
 void MonitorDeflationThread::initialize() {
   EXCEPTION_MARK;
 
   const char* name = "Monitor Deflation Thread";
@@ -60,44 +62,78 @@
   //      See is_async_deflation_needed().
   //  - GuaranteedAsyncDeflationInterval
   //      Backup deflation heuristic checks the conditions at this interval.
   //      See is_async_deflation_needed().
   //
-  intx wait_time = max_intx;
+  intx deflation_interval = max_intx;
   if (GuaranteedSafepointInterval > 0) {
-    wait_time = MIN2(wait_time, GuaranteedSafepointInterval);
+    deflation_interval = MIN2(deflation_interval, GuaranteedSafepointInterval);
   }
   if (AsyncDeflationInterval > 0) {
-    wait_time = MIN2(wait_time, AsyncDeflationInterval);
+    deflation_interval = MIN2(deflation_interval, AsyncDeflationInterval);
   }
   if (GuaranteedAsyncDeflationInterval > 0) {
-    wait_time = MIN2(wait_time, GuaranteedAsyncDeflationInterval);
+    deflation_interval = MIN2(deflation_interval, GuaranteedAsyncDeflationInterval);
   }
 
   // If all options are disabled, then wait time is not defined, and the deflation
   // is effectively disabled. In that case, exit the thread immediately after printing
   // a warning message.
-  if (wait_time == max_intx) {
+  if (deflation_interval == max_intx) {
     warning("Async deflation is disabled");
+    LightweightSynchronizer::set_table_max(jt);
     return;
   }
 
+    intx time_to_wait = deflation_interval;
   while (true) {
+    bool resize = false;
     {
+      // TODO[OMWorld]: This is all being rewritten.
       // Need state transition ThreadBlockInVM so that this thread
       // will be handled by safepoint correctly when this thread is
       // notified at a safepoint.
 
       ThreadBlockInVM tbivm(jt);
 
       MonitorLocker ml(MonitorDeflation_lock, Mutex::_no_safepoint_check_flag);
       while (!ObjectSynchronizer::is_async_deflation_needed()) {
         // Wait until notified that there is some work to do.
-        ml.wait(wait_time);
+        ml.wait(time_to_wait);
+
+        // Handle LightweightSynchronizer Hash Table Resizing
+        if (LightweightSynchronizer::needs_resize(jt)) {
+          resize = true;
+          break;
+        }
+      }
+    }
+
+    if (resize) {
+      // TODO[OMWorld]: Recheck this logic, especially !resize_successful and LightweightSynchronizer::needs_resize when is_max_size_reached == true
+      const intx time_since_last_deflation = checked_cast<intx>(ObjectSynchronizer::time_since_last_async_deflation_ms());
+      const bool resize_successful = LightweightSynchronizer::resize_table(jt);
+      const bool deflation_interval_passed = time_since_last_deflation >= deflation_interval;
+      const bool deflation_needed = deflation_interval_passed && ObjectSynchronizer::is_async_deflation_needed();
+
+      if (!resize_successful) {
+        // Resize failed, try again in 250 ms
+        time_to_wait = 250;
+      } else if (deflation_interval_passed) {
+        time_to_wait = deflation_interval;
+      } else {
+        time_to_wait = deflation_interval - time_since_last_deflation;
+      }
+
+      if (!deflation_needed) {
+        continue;
       }
+    } else {
+      time_to_wait = deflation_interval;
     }
 
+
     (void)ObjectSynchronizer::deflate_idle_monitors();
 
     if (log_is_enabled(Debug, monitorinflation)) {
       // The VMThread calls do_final_audit_and_print_stats() which calls
       // audit_and_print_stats() at the Info level at VM exit time.
diff a/src/hotspot/share/runtime/objectMonitor.cpp b/src/hotspot/share/runtime/objectMonitor.cpp
--- a/src/hotspot/share/runtime/objectMonitor.cpp
+++ b/src/hotspot/share/runtime/objectMonitor.cpp
@@ -41,20 +41,23 @@
 #include "runtime/atomic.hpp"
 #include "runtime/globals.hpp"
 #include "runtime/handles.inline.hpp"
 #include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/javaThread.inline.hpp"
+#include "runtime/lightweightSynchronizer.hpp"
 #include "runtime/mutexLocker.hpp"
 #include "runtime/objectMonitor.hpp"
 #include "runtime/objectMonitor.inline.hpp"
 #include "runtime/orderAccess.hpp"
 #include "runtime/osThread.hpp"
 #include "runtime/perfData.hpp"
 #include "runtime/safefetch.hpp"
 #include "runtime/safepointMechanism.inline.hpp"
 #include "runtime/sharedRuntime.hpp"
+#include "runtime/synchronizer.hpp"
 #include "services/threadService.hpp"
+#include "utilities/debug.hpp"
 #include "utilities/dtrace.hpp"
 #include "utilities/globalDefinitions.hpp"
 #include "utilities/macros.hpp"
 #include "utilities/preserveException.hpp"
 #if INCLUDE_JFR
@@ -257,11 +260,11 @@
   }
 #endif // ASSERT
 }
 
 ObjectMonitor::ObjectMonitor(oop object) :
-  _header(markWord::zero()),
+  _metadata(0),
   _object(_oop_storage, object),
   _owner(nullptr),
   _previous_owner_tid(0),
   _next_om(nullptr),
   _recursions(0),
@@ -283,14 +286,10 @@
 oop ObjectMonitor::object() const {
   check_object_context();
   return _object.resolve();
 }
 
-oop ObjectMonitor::object_peek() const {
-  return _object.peek();
-}
-
 void ObjectMonitor::ExitOnSuspend::operator()(JavaThread* current) {
   if (current->is_suspended()) {
     _om->_recursions = 0;
     _om->_succ = nullptr;
     // Don't need a full fence after clearing successor here because of the call to exit().
@@ -308,88 +307,103 @@
       OrderAccess::fence(); // always do a full fence when successor is cleared
     }
   }
 }
 
+#define assert_mark_word_concistency()                                                 \
+  assert(LockingMode == LM_LIGHTWEIGHT || object()->mark() == markWord::encode(this),  \
+         "object mark must match encoded this: mark=" INTPTR_FORMAT                    \
+         ", encoded this=" INTPTR_FORMAT, object()->mark().value(),                    \
+         markWord::encode(this).value());
+
 // -----------------------------------------------------------------------------
 // Enter support
 
-bool ObjectMonitor::enter_for(JavaThread* locking_thread) {
+bool ObjectMonitor::enter_is_async_deflating() {
+  if (is_being_async_deflated()) {
+    if (LockingMode != LM_LIGHTWEIGHT) {
+      const oop l_object = object();
+      if (l_object != nullptr) {
+        // Attempt to restore the header/dmw to the object's header so that
+        // we only retry once if the deflater thread happens to be slow.
+        install_displaced_markword_in_object(l_object);
+      }
+    }
+    return true;
+  }
+
+  return false;
+}
+
+void ObjectMonitor::enter_for_with_contention_mark(JavaThread* locking_thread, ObjectMonitorContentionMark& contention_mark) {
   // Used by ObjectSynchronizer::enter_for to enter for another thread.
   // The monitor is private to or already owned by locking_thread which must be suspended.
   // So this code may only contend with deflation.
   assert(locking_thread == Thread::current() || locking_thread->is_obj_deopt_suspend(), "must be");
+  assert(contention_mark._monitor == this, "must be");
+  assert(!is_being_async_deflated(), "must be");
 
-  // Block out deflation as soon as possible.
-  add_to_contentions(1);
+
+  void* prev_owner = try_set_owner_from(nullptr, locking_thread);
 
   bool success = false;
-  if (!is_being_async_deflated()) {
-    void* prev_owner = try_set_owner_from(nullptr, locking_thread);
 
-    if (prev_owner == nullptr) {
-      assert(_recursions == 0, "invariant");
-      success = true;
-    } else if (prev_owner == locking_thread) {
-      _recursions++;
-      success = true;
-    } else if (prev_owner == DEFLATER_MARKER) {
-      // Racing with deflation.
-      prev_owner = try_set_owner_from(DEFLATER_MARKER, locking_thread);
-      if (prev_owner == DEFLATER_MARKER) {
-        // Cancelled deflation. Increment contentions as part of the deflation protocol.
-        add_to_contentions(1);
-        success = true;
-      } else if (prev_owner == nullptr) {
-        // At this point we cannot race with deflation as we have both incremented
-        // contentions, seen contention > 0 and seen a DEFLATER_MARKER.
-        // success will only be false if this races with something other than
-        // deflation.
-        prev_owner = try_set_owner_from(nullptr, locking_thread);
-        success = prev_owner == nullptr;
-      }
-    } else if (LockingMode == LM_LEGACY && locking_thread->is_lock_owned((address)prev_owner)) {
-      assert(_recursions == 0, "must be");
-      _recursions = 1;
-      set_owner_from_BasicLock(prev_owner, locking_thread);
+  if (prev_owner == nullptr) {
+    assert(_recursions == 0, "invariant");
+    success = true;
+  } else if (prev_owner == locking_thread) {
+    _recursions++;
+    success = true;
+  } else if (prev_owner == DEFLATER_MARKER) {
+    // Racing with deflation.
+    prev_owner = try_set_owner_from(DEFLATER_MARKER, locking_thread);
+    if (prev_owner == DEFLATER_MARKER) {
+      // Cancelled deflation. Increment contentions as part of the deflation protocol.
+      add_to_contentions(1);
       success = true;
+    } else if (prev_owner == nullptr) {
+      // At this point we cannot race with deflation as we have both incremented
+      // contentions, seen contention > 0 and seen a DEFLATER_MARKER.
+      // success will only be false if this races with something other than
+      // deflation.
+      prev_owner = try_set_owner_from(nullptr, locking_thread);
+      success = prev_owner == nullptr;
     }
-    assert(success, "Failed to enter_for: locking_thread=" INTPTR_FORMAT
-           ", this=" INTPTR_FORMAT "{owner=" INTPTR_FORMAT "}, observed owner: " INTPTR_FORMAT,
-           p2i(locking_thread), p2i(this), p2i(owner_raw()), p2i(prev_owner));
-  } else {
-    // Async deflation is in progress and our contentions increment
-    // above lost the race to async deflation. Undo the work and
-    // force the caller to retry.
-    const oop l_object = object();
-    if (l_object != nullptr) {
-      // Attempt to restore the header/dmw to the object's header so that
-      // we only retry once if the deflater thread happens to be slow.
-      install_displaced_markword_in_object(l_object);
-    }
+  } else if (LockingMode == LM_LEGACY && locking_thread->is_lock_owned((address)prev_owner)) {
+    assert(_recursions == 0, "must be");
+    _recursions = 1;
+    set_owner_from_BasicLock(prev_owner, locking_thread);
+    success = true;
   }
+  assert(success, "Failed to enter_for: locking_thread=" INTPTR_FORMAT
+          ", this=" INTPTR_FORMAT "{owner=" INTPTR_FORMAT "}, observed owner: " INTPTR_FORMAT,
+          p2i(locking_thread), p2i(this), p2i(owner_raw()), p2i(prev_owner));
+}
 
-  add_to_contentions(-1);
+bool ObjectMonitor::enter_for(JavaThread* locking_thread) {
 
-  assert(!success || owner_raw() == locking_thread, "must be");
+  // Block out deflation as soon as possible.
+  ObjectMonitorContentionMark contention_mark(this);
 
-  return success;
-}
+  // Check for deflation.
+  if (enter_is_async_deflating()) {
+    return false;
+  }
 
-bool ObjectMonitor::enter(JavaThread* current) {
-  assert(current == JavaThread::current(), "must be");
-  // The following code is ordered to check the most common cases first
-  // and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.
+  enter_for_with_contention_mark(locking_thread, contention_mark);
+  assert(owner_raw() == locking_thread, "must be");
+  return true;
+}
 
+bool ObjectMonitor::try_enter(JavaThread* current) {
   void* cur = try_set_owner_from(nullptr, current);
   if (cur == nullptr) {
     assert(_recursions == 0, "invariant");
     return true;
   }
 
   if (cur == current) {
-    // TODO-FIXME: check for integer overflow!  BUGID 6557169.
     _recursions++;
     return true;
   }
 
   if (LockingMode != LM_LIGHTWEIGHT && current->is_lock_owned((address)cur)) {
@@ -397,48 +411,72 @@
     _recursions = 1;
     set_owner_from_BasicLock(cur, current);  // Convert from BasicLock* to Thread*.
     return true;
   }
 
+  return false;
+}
+
+bool ObjectMonitor::spin_enter(JavaThread* current) {
+  assert(current == JavaThread::current(), "must be");
+
+  // Check for recursion.
+  if (try_enter(current)) {
+    return true;
+  }
+
+  // Check for deflation.
+  if (enter_is_async_deflating()) {
+    return false;
+  }
+
   // We've encountered genuine contention.
 
-  // Try one round of spinning *before* enqueueing current
-  // and before going through the awkward and expensive state
-  // transitions.  The following spin is strictly optional ...
+  // Do one round of spinning.
   // Note that if we acquire the monitor from an initial spin
   // we forgo posting JVMTI events and firing DTRACE probes.
   if (TrySpin(current) > 0) {
     assert(owner_raw() == current, "must be current: owner=" INTPTR_FORMAT, p2i(owner_raw()));
     assert(_recursions == 0, "must be 0: recursions=" INTX_FORMAT, _recursions);
-    assert(object()->mark() == markWord::encode(this),
-           "object mark must match encoded this: mark=" INTPTR_FORMAT
-           ", encoded this=" INTPTR_FORMAT, object()->mark().value(),
-           markWord::encode(this).value());
+    assert_mark_word_concistency();
+    return true;
+  }
+
+  return false;
+}
+
+bool ObjectMonitor::enter(JavaThread* current) {
+  assert(current == JavaThread::current(), "must be");
+
+  if (spin_enter(current)) {
     return true;
   }
 
   assert(owner_raw() != current, "invariant");
   assert(_succ != current, "invariant");
   assert(!SafepointSynchronize::is_at_safepoint(), "invariant");
   assert(current->thread_state() != _thread_blocked, "invariant");
 
-  // Keep track of contention for JVM/TI and M&M queries.
-  add_to_contentions(1);
-  if (is_being_async_deflated()) {
-    // Async deflation is in progress and our contentions increment
-    // above lost the race to async deflation. Undo the work and
-    // force the caller to retry.
-    const oop l_object = object();
-    if (l_object != nullptr) {
-      // Attempt to restore the header/dmw to the object's header so that
-      // we only retry once if the deflater thread happens to be slow.
-      install_displaced_markword_in_object(l_object);
-    }
-    add_to_contentions(-1);
+  // Keep is_being_async_deflated stable across the rest of enter
+  ObjectMonitorContentionMark contention_mark(this);
+
+  // Check for deflation.
+  if (enter_is_async_deflating()) {
     return false;
   }
 
+  // At this point this ObjectMonitor cannot be deflated, finish contended enter
+  enter_with_contention_mark(current, contention_mark);
+  return true;
+}
+
+void ObjectMonitor::enter_with_contention_mark(JavaThread *current, ObjectMonitorContentionMark &cm) {
+  assert(current == JavaThread::current(), "must be");
+  assert(owner_raw() != current, "must be");
+  assert(cm._monitor == this, "must be");
+  assert(!is_being_async_deflated(), "must be");
+
   JFR_ONLY(JfrConditionalFlush<EventJavaMonitorEnter> flush(current);)
   EventJavaMonitorEnter event;
   if (event.is_started()) {
     event.set_monitorClass(object()->klass());
     // Set an address that is 'unique enough', such that events close in
@@ -491,18 +529,17 @@
 
     // We've just gotten past the enter-check-for-suspend dance and we now own
     // the monitor free and clear.
   }
 
-  add_to_contentions(-1);
   assert(contentions() >= 0, "must not be negative: contentions=%d", contentions());
 
   // Must either set _recursions = 0 or ASSERT _recursions == 0.
   assert(_recursions == 0, "invariant");
   assert(owner_raw() == current, "invariant");
   assert(_succ != current, "invariant");
-  assert(object()->mark() == markWord::encode(this), "invariant");
+  assert_mark_word_concistency();
 
   // The thread -- now the owner -- is back in vm mode.
   // Report the glorious news via TI,DTrace and jvmstat.
   // The probe effect is non-trivial.  All the reportage occurs
   // while we hold the monitor, increasing the length of the critical
@@ -527,11 +564,10 @@
   if (event.should_commit()) {
     event.set_previousOwner(_previous_owner_tid);
     event.commit();
   }
   OM_PERFDATA_OP(ContendedLockAttempts, inc());
-  return true;
 }
 
 // Caveat: TryLock() is not necessarily serializing if it returns failure.
 // Callers must compensate as needed.
 
@@ -560,11 +596,11 @@
 //
 // The ObjectMonitor has been successfully async deflated when:
 //   (contentions < 0)
 // Contending threads that see that condition know to retry their operation.
 //
-bool ObjectMonitor::deflate_monitor() {
+bool ObjectMonitor::deflate_monitor(Thread* current) {
   if (is_busy()) {
     // Easy checks are first - the ObjectMonitor is busy so no deflation.
     return false;
   }
 
@@ -631,13 +667,19 @@
       log_trace(monitorinflation)("deflate_monitor: object=" INTPTR_FORMAT
                                   ", mark=" INTPTR_FORMAT ", type='%s'",
                                   p2i(obj), obj->mark().value(),
                                   obj->klass()->external_name());
     }
+  }
 
-    // Install the old mark word if nobody else has already done it.
-    install_displaced_markword_in_object(obj);
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    LightweightSynchronizer::deflate_monitor(current, obj, this);
+  } else {
+    if (obj != nullptr) {
+      // Install the old mark word if nobody else has already done it.
+      install_displaced_markword_in_object(obj);
+    }
   }
 
   // We leave owner == DEFLATER_MARKER and contentions < 0
   // to force any racing threads to retry.
   return true;  // Success, ObjectMonitor has been deflated.
@@ -647,10 +689,11 @@
 // into the header of the object associated with the monitor. This
 // idempotent method is called by a thread that is deflating a
 // monitor and by other threads that have detected a race with the
 // deflation process.
 void ObjectMonitor::install_displaced_markword_in_object(const oop obj) {
+  assert(LockingMode != LM_LIGHTWEIGHT, "Lightweight has no dmw");
   // This function must only be called when (owner == DEFLATER_MARKER
   // && contentions <= 0), but we can't guarantee that here because
   // those values could change when the ObjectMonitor gets moved from
   // the global free list to a per-thread free list.
 
@@ -979,16 +1022,15 @@
 //
 // In the future we should reconcile EnterI() and ReenterI().
 
 void ObjectMonitor::ReenterI(JavaThread* current, ObjectWaiter* currentNode) {
   assert(current != nullptr, "invariant");
+  assert(current->thread_state() != _thread_blocked, "invariant");
   assert(currentNode != nullptr, "invariant");
   assert(currentNode->_thread == current, "invariant");
   assert(_waiters > 0, "invariant");
-  assert(object()->mark() == markWord::encode(this), "invariant");
-
-  assert(current->thread_state() != _thread_blocked, "invariant");
+  assert_mark_word_concistency();
 
   int nWakeups = 0;
   for (;;) {
     ObjectWaiter::TStates v = currentNode->TState;
     guarantee(v == ObjectWaiter::TS_ENTER || v == ObjectWaiter::TS_CXQ, "invariant");
@@ -1041,11 +1083,11 @@
   // EntryList is stable and cxq is prepend-only.
   // The head of cxq is volatile but the interior is stable.
   // In addition, current.TState is stable.
 
   assert(owner_raw() == current, "invariant");
-  assert(object()->mark() == markWord::encode(this), "invariant");
+  assert_mark_word_concistency();
   UnlinkAfterAcquire(current, currentNode);
   if (_succ == current) _succ = nullptr;
   assert(_succ != current, "invariant");
   currentNode->TState = ObjectWaiter::TS_RUN;
   OrderAccess::fence();      // see comments at the end of EnterI()
@@ -1667,11 +1709,11 @@
   _waiters--;             // decrement the number of waiters
 
   // Verify a few postconditions
   assert(owner_raw() == current, "invariant");
   assert(_succ != current, "invariant");
-  assert(object()->mark() == markWord::encode(this), "invariant");
+  assert_mark_word_concistency();
 
   // check if the notification happened
   if (!WasNotified) {
     // no, it could be timeout or Thread.interrupt() or both
     // check for interrupt event, otherwise it is timeout
@@ -2157,11 +2199,11 @@
 
 #ifdef ASSERT
 // Print the ObjectMonitor like a debugger would:
 //
 // (ObjectMonitor) 0x00007fdfb6012e40 = {
-//   _header = 0x0000000000000001
+//   _metadata = 0x0000000000000001
 //   _object = 0x000000070ff45fd0
 //   _pad_buf0 = {
 //     [0] = '\0'
 //     ...
 //     [43] = '\0'
@@ -2186,11 +2228,11 @@
 //   _WaitSetLock = 0
 // }
 //
 void ObjectMonitor::print_debug_style_on(outputStream* st) const {
   st->print_cr("(ObjectMonitor*) " INTPTR_FORMAT " = {", p2i(this));
-  st->print_cr("  _header = " INTPTR_FORMAT, header().value());
+  st->print_cr("  _metadata = " INTPTR_FORMAT, _metadata);
   st->print_cr("  _object = " INTPTR_FORMAT, p2i(object_peek()));
   st->print_cr("  _pad_buf0 = {");
   st->print_cr("    [0] = '\\0'");
   st->print_cr("    ...");
   st->print_cr("    [%d] = '\\0'", (int)sizeof(_pad_buf0) - 1);
diff a/src/hotspot/share/runtime/objectMonitor.hpp b/src/hotspot/share/runtime/objectMonitor.hpp
--- a/src/hotspot/share/runtime/objectMonitor.hpp
+++ b/src/hotspot/share/runtime/objectMonitor.hpp
@@ -31,10 +31,11 @@
 #include "oops/weakHandle.hpp"
 #include "runtime/perfDataTypes.hpp"
 #include "utilities/checkedCast.hpp"
 
 class ObjectMonitor;
+class ObjectMonitorContentionMark;
 class ParkEvent;
 
 // ObjectWaiter serves as a "proxy" or surrogate thread.
 // TODO-FIXME: Eliminate ObjectWaiter and use the thread-specific
 // ParkEvent instead.  Beware, however, that the JVMTI code
@@ -67,24 +68,25 @@
 // WARNING: This is a very sensitive and fragile class. DO NOT make any
 // changes unless you are fully aware of the underlying semantics.
 //
 // ObjectMonitor Layout Overview/Highlights/Restrictions:
 //
-// - The _header field must be at offset 0 because the displaced header
+// - The _metadata field must be at offset 0 because the displaced header
 //   from markWord is stored there. We do not want markWord.hpp to include
 //   ObjectMonitor.hpp to avoid exposing ObjectMonitor everywhere. This
 //   means that ObjectMonitor cannot inherit from any other class nor can
 //   it use any virtual member functions. This restriction is critical to
 //   the proper functioning of the VM.
-// - The _header and _owner fields should be separated by enough space
+// - The _metadata and _owner fields should be separated by enough space
 //   to avoid false sharing due to parallel access by different threads.
 //   This is an advisory recommendation.
 // - The general layout of the fields in ObjectMonitor is:
-//     _header
+//     _metadata
 //     <lightly_used_fields>
 //     <optional padding>
 //     _owner
+//     <optional padding>
 //     <remaining_fields>
 // - The VM assumes write ordering and machine word alignment with
 //   respect to the _owner field and the <remaining_fields> that can
 //   be read in parallel by other threads.
 // - Generally fields that are accessed closely together in time should
@@ -104,24 +106,23 @@
 // - Adjacent ObjectMonitors should be separated by enough space to avoid
 //   false sharing. This is handled by the ObjectMonitor allocation code
 //   in synchronizer.cpp. Also see TEST_VM(SynchronizerTest, sanity) gtest.
 //
 // Futures notes:
-//   - Separating _owner from the <remaining_fields> by enough space to
-//     avoid false sharing might be profitable. Given
-//     http://blogs.oracle.com/dave/entry/cas_and_cache_trivia_invalidate
-//     we know that the CAS in monitorenter will invalidate the line
-//     underlying _owner. We want to avoid an L1 data cache miss on that
-//     same line for monitorexit. Putting these <remaining_fields>:
-//     _recursions, _EntryList, _cxq, and _succ, all of which may be
-//     fetched in the inflated unlock path, on a different cache line
-//     would make them immune to CAS-based invalidation from the _owner
-//     field.
+// - Separating _owner from the <remaining_fields> by enough space to
+//   avoid false sharing might be profitable. Given that the CAS in
+//   monitorenter will invalidate the line underlying _owner. We want
+//   to avoid an L1 data cache miss on that same line for monitorexit.
+//   Putting these <remaining_fields>:
+//   _recursions, _EntryList, _cxq, and _succ, all of which may be
+//   fetched in the inflated unlock path, on a different cache line
+//   would make them immune to CAS-based invalidation from the _owner
+//   field.
 //
-//   - The _recursions field should be of type int, or int32_t but not
-//     intptr_t. There's no reason to use a 64-bit type for this field
-//     in a 64-bit JVM.
+// - The _recursions field should be of type int, or int32_t but not
+//   intptr_t. There's no reason to use a 64-bit type for this field
+//   in a 64-bit JVM.
 
 #define OM_CACHE_LINE_SIZE DEFAULT_CACHE_LINE_SIZE
 
 class ObjectMonitor : public CHeapObj<mtObjectMonitor> {
   friend class ObjectSynchronizer;
@@ -129,32 +130,38 @@
   friend class VMStructs;
   JVMCI_ONLY(friend class JVMCIVMStructs;)
 
   static OopStorage* _oop_storage;
 
-  // The sync code expects the header field to be at offset zero (0).
-  // Enforced by the assert() in header_addr().
-  volatile markWord _header;        // displaced object header word - mark
+  // The sync code expects the metadata field to be at offset zero (0).
+  // Enforced by the assert() in metadata_addr().
+  // * LM_LIGHTWEIGHT
+  // Contains the _object's hashCode.
+  // * LM_LEGACY, LM_MONITOR
+  // Contains the displaced object header word - mark
+  volatile uintptr_t _metadata;     // metadata
   WeakHandle _object;               // backward object pointer
-  // Separate _header and _owner on different cache lines since both can
-  // have busy multi-threaded access. _header and _object are set at initial
+  // Separate _metadata and _owner on different cache lines since both can
+  // have busy multi-threaded access. _metadata and _object are set at initial
   // inflation. The _object does not change, so it is a good choice to share
-  // its cache line with _header.
-  DEFINE_PAD_MINUS_SIZE(0, OM_CACHE_LINE_SIZE, sizeof(volatile markWord) +
+  // its cache line with _metadata.
+  DEFINE_PAD_MINUS_SIZE(0, OM_CACHE_LINE_SIZE, sizeof(_metadata) +
                         sizeof(WeakHandle));
   // Used by async deflation as a marker in the _owner field.
   // Note that the choice of the two markers is peculiar:
   // - They need to represent values that cannot be pointers. In particular,
   //   we achieve this by using the lowest two bits.
   // - ANONYMOUS_OWNER should be a small value, it is used in generated code
   //   and small values encode much better.
   // - We test for anonymous owner by testing for the lowest bit, therefore
   //   DEFLATER_MARKER must *not* have that bit set.
-  #define DEFLATER_MARKER reinterpret_cast<void*>(2)
+  static const uintptr_t DEFLATER_MARKER_VALUE = 2;
+  #define DEFLATER_MARKER reinterpret_cast<void*>(DEFLATER_MARKER_VALUE)
 public:
   // NOTE: Typed as uintptr_t so that we can pick it up in SA, via vmStructs.
   static const uintptr_t ANONYMOUS_OWNER = 1;
+  static const uintptr_t ANONYMOUS_OWNER_OR_DEFLATER_MARKER = ANONYMOUS_OWNER | DEFLATER_MARKER_VALUE;
 
 private:
   static void* anon_owner_ptr() { return reinterpret_cast<void*>(ANONYMOUS_OWNER); }
 
   void* volatile _owner;            // pointer to owning thread OR BasicLock
@@ -179,11 +186,11 @@
 
   int _contentions;                 // Number of active contentions in enter(). It is used by is_busy()
                                     // along with other fields to determine if an ObjectMonitor can be
                                     // deflated. It is also used by the async deflation protocol. See
                                     // ObjectMonitor::deflate_monitor().
- protected:
+
   ObjectWaiter* volatile _WaitSet;  // LL of threads wait()ing on the monitor
   volatile int  _waiters;           // number of waiting threads
  private:
   volatile int _WaitSetLock;        // protects Wait Queue - simple spinlock
 
@@ -211,10 +218,11 @@
   static PerfCounter * _sync_Deflations;
   static PerfLongVariable * _sync_MonExtant;
 
   static int Knob_SpinLimit;
 
+  static ByteSize metadata_offset()    { return byte_offset_of(ObjectMonitor, _metadata); }
   static ByteSize owner_offset()       { return byte_offset_of(ObjectMonitor, _owner); }
   static ByteSize recursions_offset()  { return byte_offset_of(ObjectMonitor, _recursions); }
   static ByteSize cxq_offset()         { return byte_offset_of(ObjectMonitor, _cxq); }
   static ByteSize succ_offset()        { return byte_offset_of(ObjectMonitor, _succ); }
   static ByteSize EntryList_offset()   { return byte_offset_of(ObjectMonitor, _EntryList); }
@@ -228,16 +236,25 @@
   // offset or we can adjust the offset that is added to the ObjectMonitor
   // reference. The latter avoids an AGI (Address Generation Interlock)
   // stall so the helper macro adjusts the offset value that is returned
   // to the ObjectMonitor reference manipulation code:
   //
+  // Lightweight locking fetches ObjectMonitor references from a cache
+  // instead of the markWord and doesn't work with tagged values.
+  //
   #define OM_OFFSET_NO_MONITOR_VALUE_TAG(f) \
-    ((in_bytes(ObjectMonitor::f ## _offset())) - checked_cast<int>(markWord::monitor_value))
+    ((in_bytes(ObjectMonitor::f ## _offset())) - (LockingMode == LM_LIGHTWEIGHT ? 0 : checked_cast<int>(markWord::monitor_value)))
+
+  uintptr_t           metadata() const;
+  void                set_metadata(uintptr_t value);
+  volatile uintptr_t* metadata_addr();
+
+  markWord            header() const;
+  void                set_header(markWord hdr);
 
-  markWord           header() const;
-  volatile markWord* header_addr();
-  void               set_header(markWord hdr);
+  intptr_t            hash() const;
+  void                set_hash(intptr_t hash);
 
   bool is_busy() const {
     // TODO-FIXME: assert _owner == null implies _recursions = 0
     intptr_t ret_code = intptr_t(_waiters) | intptr_t(_cxq) | intptr_t(_EntryList);
     int cnts = contentions(); // read once
@@ -247,10 +264,18 @@
     if (!owner_is_DEFLATER_MARKER()) {
       ret_code |= intptr_t(owner_raw());
     }
     return ret_code != 0;
   }
+  bool is_contended() const {
+    intptr_t ret_code = intptr_t(_waiters) | intptr_t(_cxq) | intptr_t(_EntryList);
+    int cnts = contentions();
+    if (cnts > 0) {
+      ret_code |= intptr_t(cnts);
+    }
+    return ret_code != 0;
+  }
   const char* is_busy_to_string(stringStream* ss);
 
   bool is_entered(JavaThread* current) const;
 
   // Returns true if this OM has an owner, false otherwise.
@@ -304,10 +329,13 @@
   ObjectMonitor(oop object);
   ~ObjectMonitor();
 
   oop       object() const;
   oop       object_peek() const;
+  bool      object_is_cleared() const;
+  bool      object_is_dead() const;
+  bool      object_refers_to(oop obj) const;
 
   // Returns true if the specified thread owns the ObjectMonitor. Otherwise
   // returns false and throws IllegalMonitorStateException (IMSE).
   bool      check_owner(TRAPS);
 
@@ -326,13 +354,19 @@
     ObjectMonitor* _om;
    public:
     ClearSuccOnSuspend(ObjectMonitor* om) : _om(om)  {}
     void operator()(JavaThread* current);
   };
+
+  bool      enter_is_async_deflating();
  public:
+  void      enter_for_with_contention_mark(JavaThread* locking_thread, ObjectMonitorContentionMark& contention_mark);
   bool      enter_for(JavaThread* locking_thread);
   bool      enter(JavaThread* current);
+  bool      try_enter(JavaThread* current);
+  bool      spin_enter(JavaThread* current);
+  void      enter_with_contention_mark(JavaThread* current, ObjectMonitorContentionMark& contention_mark);
   void      exit(JavaThread* current, bool not_suspended = true);
   void      wait(jlong millis, bool interruptible, TRAPS);
   void      notify(TRAPS);
   void      notifyAll(TRAPS);
 
@@ -356,10 +390,25 @@
   int       TryLock(JavaThread* current);
   int       TrySpin(JavaThread* current);
   void      ExitEpilog(JavaThread* current, ObjectWaiter* Wakee);
 
   // Deflation support
-  bool      deflate_monitor();
+  bool      deflate_monitor(Thread* current);
+private:
   void      install_displaced_markword_in_object(const oop obj);
 };
 
+// RAII object to ensure that ObjectMonitor::is_being_async_deflated() is
+// stable within the context of this mark.
+class ObjectMonitorContentionMark : StackObj {
+  DEBUG_ONLY(friend class ObjectMonitor;)
+
+  ObjectMonitor* _monitor;
+
+  NONCOPYABLE(ObjectMonitorContentionMark);
+
+public:
+  explicit ObjectMonitorContentionMark(ObjectMonitor* monitor);
+  ~ObjectMonitorContentionMark();
+};
+
 #endif // SHARE_RUNTIME_OBJECTMONITOR_HPP
diff a/src/hotspot/share/runtime/objectMonitor.inline.hpp b/src/hotspot/share/runtime/objectMonitor.inline.hpp
--- a/src/hotspot/share/runtime/objectMonitor.inline.hpp
+++ b/src/hotspot/share/runtime/objectMonitor.inline.hpp
@@ -27,13 +27,17 @@
 
 #include "runtime/objectMonitor.hpp"
 
 #include "logging/log.hpp"
 #include "oops/access.inline.hpp"
+#include "oops/markWord.hpp"
 #include "runtime/atomic.hpp"
+#include "runtime/globals.hpp"
 #include "runtime/lockStack.inline.hpp"
 #include "runtime/synchronizer.hpp"
+#include "utilities/checkedCast.hpp"
+#include "utilities/globalDefinitions.hpp"
 
 inline bool ObjectMonitor::is_entered(JavaThread* current) const {
   if (LockingMode == LM_LIGHTWEIGHT) {
     if (is_owner_anonymous()) {
       return current->lock_stack().contains(object());
@@ -47,20 +51,42 @@
     }
   }
   return false;
 }
 
-inline markWord ObjectMonitor::header() const {
-  return Atomic::load(&_header);
+inline uintptr_t ObjectMonitor::metadata() const {
+  return Atomic::load(&_metadata);
+}
+
+inline void ObjectMonitor::set_metadata(uintptr_t value) {
+  Atomic::store(&_metadata, value);
 }
 
-inline volatile markWord* ObjectMonitor::header_addr() {
-  return &_header;
+inline volatile uintptr_t* ObjectMonitor::metadata_addr() {
+  STATIC_ASSERT(std::is_standard_layout<ObjectMonitor>::value);
+  STATIC_ASSERT(offsetof(ObjectMonitor, _metadata) == 0);
+  return &_metadata;
+}
+
+inline markWord ObjectMonitor::header() const {
+  assert(LockingMode != LM_LIGHTWEIGHT, "Lightweight locking does not use header");
+  return markWord(metadata());
 }
 
 inline void ObjectMonitor::set_header(markWord hdr) {
-  Atomic::store(&_header, hdr);
+  assert(LockingMode != LM_LIGHTWEIGHT, "Lightweight locking does not use header");
+  set_metadata(hdr.value());
+}
+
+inline intptr_t ObjectMonitor::hash() const {
+  assert(LockingMode == LM_LIGHTWEIGHT, "Only used by lightweight locking");
+  return metadata();
+}
+
+inline void ObjectMonitor::set_hash(intptr_t hash) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "Only used by lightweight locking");
+  set_metadata(hash);
 }
 
 inline int ObjectMonitor::waiters() const {
   return _waiters;
 }
@@ -178,6 +204,37 @@
 // Simply set _next_om field to new_value.
 inline void ObjectMonitor::set_next_om(ObjectMonitor* new_value) {
   Atomic::store(&_next_om, new_value);
 }
 
+inline ObjectMonitorContentionMark::ObjectMonitorContentionMark(ObjectMonitor* monitor)
+  : _monitor(monitor) {
+  _monitor->add_to_contentions(1);
+}
+
+inline ObjectMonitorContentionMark::~ObjectMonitorContentionMark() {
+  _monitor->add_to_contentions(-1);
+}
+
+inline oop ObjectMonitor::object_peek() const {
+  if (_object.is_null()) {
+    return nullptr;
+  }
+  return _object.peek();
+}
+
+inline bool ObjectMonitor::object_is_dead() const {
+  return object_peek() == nullptr;
+}
+
+inline bool ObjectMonitor::object_is_cleared() const {
+  return _object.is_null();
+}
+
+inline bool ObjectMonitor::object_refers_to(oop obj) const {
+  if (_object.is_null()) {
+    return false;
+  }
+  return _object.peek() == obj;
+}
+
 #endif // SHARE_RUNTIME_OBJECTMONITOR_INLINE_HPP
diff a/src/hotspot/share/runtime/safepoint.cpp b/src/hotspot/share/runtime/safepoint.cpp
--- a/src/hotspot/share/runtime/safepoint.cpp
+++ b/src/hotspot/share/runtime/safepoint.cpp
@@ -729,10 +729,15 @@
     SafepointSynchronize::increment_jni_active_count();
   }
   DEBUG_ONLY(_thread->set_visited_for_critical_count(SafepointSynchronize::safepoint_counter());)
   assert(!_safepoint_safe, "Must be unsafe before safe");
   _safepoint_safe = true;
+
+  // The oops in the monitor cache is cleared to prevent stale cache entries
+  // from keeping dead objects alive. Because these oops are always cleared
+  // before safepoint operations they are not visited in JavaThread::oops_do.
+  _thread->om_clear_monitor_cache();
 }
 
 void ThreadSafepointState::restart() {
   assert(_safepoint_safe, "Must be safe before unsafe");
   _safepoint_safe = false;
diff a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp
--- a/src/hotspot/share/runtime/sharedRuntime.cpp
+++ b/src/hotspot/share/runtime/sharedRuntime.cpp
@@ -54,11 +54,13 @@
 #include "prims/jvmtiExport.hpp"
 #include "prims/jvmtiThreadState.hpp"
 #include "prims/methodHandles.hpp"
 #include "prims/nativeLookup.hpp"
 #include "runtime/atomic.hpp"
+#include "runtime/basicLock.inline.hpp"
 #include "runtime/frame.inline.hpp"
+#include "runtime/globals.hpp"
 #include "runtime/handles.inline.hpp"
 #include "runtime/init.hpp"
 #include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/java.hpp"
 #include "runtime/javaCalls.hpp"
@@ -71,10 +73,11 @@
 #include "runtime/vframeArray.hpp"
 #include "runtime/vm_version.hpp"
 #include "utilities/copy.hpp"
 #include "utilities/dtrace.hpp"
 #include "utilities/events.hpp"
+#include "utilities/globalDefinitions.hpp"
 #include "utilities/resourceHash.hpp"
 #include "utilities/macros.hpp"
 #include "utilities/xmlstream.hpp"
 #ifdef COMPILER1
 #include "c1/c1_Runtime1.hpp"
@@ -2932,10 +2935,12 @@
           ObjectSynchronizer::inflate_helper(kptr2->obj());
         }
         // Now the displaced header is free to move because the
         // object's header no longer refers to it.
         buf[i] = (intptr_t)lock->displaced_header().value();
+      } else if (LockingMode == LM_LIGHTWEIGHT) {
+        buf[i] = (intptr_t)lock->object_monitor_cache();
       }
 #ifdef ASSERT
       else {
         buf[i] = badDispHeaderOSR;
       }
diff a/src/hotspot/share/runtime/synchronizer.cpp b/src/hotspot/share/runtime/synchronizer.cpp
--- a/src/hotspot/share/runtime/synchronizer.cpp
+++ b/src/hotspot/share/runtime/synchronizer.cpp
@@ -33,16 +33,18 @@
 #include "memory/resourceArea.hpp"
 #include "memory/universe.hpp"
 #include "oops/markWord.hpp"
 #include "oops/oop.inline.hpp"
 #include "runtime/atomic.hpp"
+#include "runtime/basicLock.inline.hpp"
 #include "runtime/frame.inline.hpp"
 #include "runtime/globals.hpp"
 #include "runtime/handles.inline.hpp"
 #include "runtime/handshake.hpp"
 #include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/javaThread.hpp"
+#include "runtime/lightweightSynchronizer.hpp"
 #include "runtime/lockStack.inline.hpp"
 #include "runtime/mutexLocker.hpp"
 #include "runtime/objectMonitor.hpp"
 #include "runtime/objectMonitor.inline.hpp"
 #include "runtime/os.inline.hpp"
@@ -65,10 +67,22 @@
 #include "utilities/linkedlist.hpp"
 #include "utilities/preserveException.hpp"
 
 class ObjectMonitorDeflationLogging;
 
+ObjectMonitor* ObjectSynchronizer::read_monitor(markWord mark) {
+  assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking uses table");
+  return mark.monitor();
+}
+
+ObjectMonitor* ObjectSynchronizer::read_monitor(Thread* current, oop obj, markWord mark) {
+  if (LockingMode != LM_LIGHTWEIGHT) {
+    return read_monitor(mark);
+  }
+  return LightweightSynchronizer::read_monitor(current, obj);
+}
+
 void MonitorList::add(ObjectMonitor* m) {
   ObjectMonitor* head;
   do {
     head = Atomic::load(&_head);
     m->set_next_om(head);
@@ -274,10 +288,14 @@
   // Start the ceiling with the estimate for one thread.
   set_in_use_list_ceiling(AvgMonitorsPerThreadEstimate);
 
   // Start the timer for deflations, so it does not trigger immediately.
   _last_async_deflation_time_ns = os::javaTimeNanos();
+
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    LightweightSynchronizer::initialize();
+  }
 }
 
 MonitorList ObjectSynchronizer::_in_use_list;
 // monitors_used_above_threshold() policy is as follows:
 //
@@ -347,11 +365,15 @@
       return true;
     }
   }
 
   if (mark.has_monitor()) {
-    ObjectMonitor* const mon = mark.monitor();
+    ObjectMonitor* const mon = read_monitor(current, obj, mark);
+    if (LockingMode == LM_LIGHTWEIGHT && mon == nullptr) {
+      // Racing with inflation/deflation go slow path
+      return false;
+    }
     assert(mon->object() == oop(obj), "invariant");
     if (mon->owner() != current) return false;  // slow-path for IMS exception
 
     if (mon->first_waiter() != nullptr) {
       // We have one or more waiters. Since this is an inflated monitor
@@ -392,26 +414,17 @@
   if (obj->klass()->is_value_based()) {
     return false;
   }
 
   if (LockingMode == LM_LIGHTWEIGHT) {
-    LockStack& lock_stack = current->lock_stack();
-    if (lock_stack.is_full()) {
-      // Always go into runtime if the lock stack is full.
-      return false;
-    }
-    if (lock_stack.try_recursive_enter(obj)) {
-      // Recursive lock successful.
-      current->inc_held_monitor_count();
-      return true;
-    }
+    return LightweightSynchronizer::quick_enter(obj, current, lock);
   }
 
   const markWord mark = obj->mark();
 
   if (mark.has_monitor()) {
-    ObjectMonitor* const m = mark.monitor();
+    ObjectMonitor* const m = ObjectSynchronizer::read_monitor(mark);
     // An async deflation or GC can race us before we manage to make
     // the ObjectMonitor busy by setting the owner below. If we detect
     // that race we just bail out to the slow-path here.
     if (m->object_peek() == nullptr) {
       return false;
@@ -427,11 +440,11 @@
       m->_recursions++;
       current->inc_held_monitor_count();
       return true;
     }
 
-    if (LockingMode != LM_LIGHTWEIGHT) {
+    if (LockingMode == LM_LEGACY) {
       // This Java Monitor is inflated so obj's header will never be
       // displaced to this thread's BasicLock. Make the displaced header
       // non-null so this BasicLock is not seen as recursive nor as
       // being locked. We do this unconditionally so that this thread's
       // BasicLock cannot be mis-interpreted by any stack walkers. For
@@ -522,10 +535,15 @@
 void ObjectSynchronizer::enter_for(Handle obj, BasicLock* lock, JavaThread* locking_thread) {
   // When called with locking_thread != Thread::current() some mechanism must synchronize
   // the locking_thread with respect to the current thread. Currently only used when
   // deoptimizing and re-locking locks. See Deoptimization::relock_objects
   assert(locking_thread == Thread::current() || locking_thread->is_obj_deopt_suspend(), "must be");
+
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    return LightweightSynchronizer::enter_for(obj, lock, locking_thread);
+  }
+
   if (!enter_fast_impl(obj, lock, locking_thread)) {
     // Inflated ObjectMonitor::enter_for is required
 
     // An async deflation can race after the inflate_for() call and before
     // enter_for() can make the ObjectMonitor busy. enter_for() returns false
@@ -540,10 +558,15 @@
   }
 }
 
 void ObjectSynchronizer::enter(Handle obj, BasicLock* lock, JavaThread* current) {
   assert(current == Thread::current(), "must be");
+
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    return LightweightSynchronizer::enter(obj, lock, current);
+  }
+
   if (!enter_fast_impl(obj, lock, current)) {
     // Inflated ObjectMonitor::enter is required
 
     // An async deflation can race after the inflate() call and before
     // enter() can make the ObjectMonitor busy. enter() returns false if
@@ -559,73 +582,20 @@
 
 // The interpreter and compiler assembly code tries to lock using the fast path
 // of this algorithm. Make sure to update that code if the following function is
 // changed. The implementation is extremely sensitive to race condition. Be careful.
 bool ObjectSynchronizer::enter_fast_impl(Handle obj, BasicLock* lock, JavaThread* locking_thread) {
+  assert(LockingMode != LM_LIGHTWEIGHT, "Use LightweightSynchronizer");
 
   if (obj->klass()->is_value_based()) {
     handle_sync_on_value_based_class(obj, locking_thread);
   }
 
   locking_thread->inc_held_monitor_count();
 
   if (!useHeavyMonitors()) {
-    if (LockingMode == LM_LIGHTWEIGHT) {
-      // Fast-locking does not use the 'lock' argument.
-      LockStack& lock_stack = locking_thread->lock_stack();
-      if (lock_stack.is_full()) {
-        // We unconditionally make room on the lock stack by inflating
-        // the least recently locked object on the lock stack.
-
-        // About the choice to inflate least recently locked object.
-        // First we must chose to inflate a lock, either some lock on
-        // the lock-stack or the lock that is currently being entered
-        // (which may or may not be on the lock-stack).
-        // Second the best lock to inflate is a lock which is entered
-        // in a control flow where there are only a very few locks being
-        // used, as the costly part of inflated locking is inflation,
-        // not locking. But this property is entirely program dependent.
-        // Third inflating the lock currently being entered on when it
-        // is not present on the lock-stack will result in a still full
-        // lock-stack. This creates a scenario where every deeper nested
-        // monitorenter must call into the runtime.
-        // The rational here is as follows:
-        // Because we cannot (currently) figure out the second, and want
-        // to avoid the third, we inflate a lock on the lock-stack.
-        // The least recently locked lock is chosen as it is the lock
-        // with the longest critical section.
-
-        log_info(monitorinflation)("LockStack capacity exceeded, inflating.");
-        ObjectMonitor* monitor = inflate_for(locking_thread, lock_stack.bottom(), inflate_cause_vm_internal);
-        assert(monitor->owner() == Thread::current(), "must be owner=" PTR_FORMAT " current=" PTR_FORMAT " mark=" PTR_FORMAT,
-               p2i(monitor->owner()), p2i(Thread::current()), monitor->object()->mark_acquire().value());
-        assert(!lock_stack.is_full(), "must have made room here");
-      }
-
-      markWord mark = obj()->mark_acquire();
-      while (mark.is_unlocked()) {
-        // Retry until a lock state change has been observed. cas_set_mark() may collide with non lock bits modifications.
-        // Try to swing into 'fast-locked' state.
-        assert(!lock_stack.contains(obj()), "thread must not already hold the lock");
-        const markWord locked_mark = mark.set_fast_locked();
-        const markWord old_mark = obj()->cas_set_mark(locked_mark, mark);
-        if (old_mark == mark) {
-          // Successfully fast-locked, push object to lock-stack and return.
-          lock_stack.push(obj());
-          return true;
-        }
-        mark = old_mark;
-      }
-
-      if (mark.is_fast_locked() && lock_stack.try_recursive_enter(obj())) {
-        // Recursive lock successful.
-        return true;
-      }
-
-      // Failed to fast lock.
-      return false;
-    } else if (LockingMode == LM_LEGACY) {
+    if (LockingMode == LM_LEGACY) {
       markWord mark = obj->mark();
       if (mark.is_unlocked()) {
         // Anticipate successful CAS -- the ST of the displaced mark must
         // be visible <= the ST performed by the CAS.
         lock->set_displaced_header(mark);
@@ -657,38 +627,17 @@
 }
 
 void ObjectSynchronizer::exit(oop object, BasicLock* lock, JavaThread* current) {
   current->dec_held_monitor_count();
 
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    return LightweightSynchronizer::exit(object, current);
+  }
+
   if (!useHeavyMonitors()) {
     markWord mark = object->mark();
-    if (LockingMode == LM_LIGHTWEIGHT) {
-      // Fast-locking does not use the 'lock' argument.
-      LockStack& lock_stack = current->lock_stack();
-      if (mark.is_fast_locked() && lock_stack.try_recursive_exit(object)) {
-        // Recursively unlocked.
-        return;
-      }
-
-      if (mark.is_fast_locked() && lock_stack.is_recursive(object)) {
-        // This lock is recursive but is not at the top of the lock stack so we're
-        // doing an unbalanced exit. We have to fall thru to inflation below and
-        // let ObjectMonitor::exit() do the unlock.
-      } else {
-        while (mark.is_fast_locked()) {
-          // Retry until a lock state change has been observed. cas_set_mark() may collide with non lock bits modifications.
-          const markWord unlocked_mark = mark.set_unlocked();
-          const markWord old_mark = object->cas_set_mark(unlocked_mark, mark);
-          if (old_mark == mark) {
-            size_t recursions = lock_stack.remove(object) - 1;
-            assert(recursions == 0, "must not be recursive here");
-            return;
-          }
-          mark = old_mark;
-        }
-      }
-    } else if (LockingMode == LM_LEGACY) {
+    if (LockingMode == LM_LEGACY) {
       markWord dhw = lock->displaced_header();
       if (dhw.value() == 0) {
         // If the displaced header is null, then this exit matches up with
         // a recursive enter. No real work to do here except for diagnostics.
 #ifndef PRODUCT
@@ -706,11 +655,11 @@
             // after this thread entered the stack-lock recursively. When a
             // Java Monitor is inflated, we cannot safely walk the Java
             // Monitor owner's stack and update the BasicLocks because a
             // Java Monitor can be asynchronously inflated by a thread that
             // does not own the Java Monitor.
-            ObjectMonitor* m = mark.monitor();
+            ObjectMonitor* m = read_monitor(mark);
             assert(m->object()->mark() == mark, "invariant");
             assert(m->is_entered(current), "invariant");
           }
         }
 #endif
@@ -750,12 +699,20 @@
   current->set_current_pending_monitor_is_from_java(false);
   // An async deflation can race after the inflate() call and before
   // enter() can make the ObjectMonitor busy. enter() returns false if
   // we have lost the race to async deflation and we simply try again.
   while (true) {
-    ObjectMonitor* monitor = inflate(current, obj(), inflate_cause_jni_enter);
-    if (monitor->enter(current)) {
+    ObjectMonitor* monitor;
+    bool entered;
+    if (LockingMode == LM_LIGHTWEIGHT) {
+      entered = LightweightSynchronizer::inflate_and_enter(obj(), current, current, inflate_cause_jni_enter) != nullptr;
+    } else {
+      monitor = inflate(current, obj(), inflate_cause_jni_enter);
+      entered = monitor->enter(current);
+    }
+
+    if (entered) {
       current->inc_held_monitor_count(1, true);
       break;
     }
   }
   current->set_current_pending_monitor_is_from_java(true);
@@ -763,13 +720,18 @@
 
 // NOTE: must use heavy weight monitor to handle jni monitor exit
 void ObjectSynchronizer::jni_exit(oop obj, TRAPS) {
   JavaThread* current = THREAD;
 
-  // The ObjectMonitor* can't be async deflated until ownership is
-  // dropped inside exit() and the ObjectMonitor* must be !is_busy().
-  ObjectMonitor* monitor = inflate(current, obj, inflate_cause_jni_exit);
+  ObjectMonitor* monitor;
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    monitor = LightweightSynchronizer::inflate_locked_or_imse(obj, inflate_cause_jni_exit, CHECK);
+  } else {
+    // The ObjectMonitor* can't be async deflated until ownership is
+    // dropped inside exit() and the ObjectMonitor* must be !is_busy().
+    monitor = inflate(current, obj, inflate_cause_jni_exit);
+  }
   // If this thread has locked the object, exit the monitor. We
   // intentionally do not use CHECK on check_owner because we must exit the
   // monitor even if an exception was already pending.
   if (monitor->check_owner(THREAD)) {
     monitor->exit(current);
@@ -798,19 +760,26 @@
 
 
 // -----------------------------------------------------------------------------
 //  Wait/Notify/NotifyAll
 // NOTE: must use heavy weight monitor to handle wait()
+
 int ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
   JavaThread* current = THREAD;
   if (millis < 0) {
     THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
   }
-  // The ObjectMonitor* can't be async deflated because the _waiters
-  // field is incremented before ownership is dropped and decremented
-  // after ownership is regained.
-  ObjectMonitor* monitor = inflate(current, obj(), inflate_cause_wait);
+
+  ObjectMonitor* monitor;
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    monitor = LightweightSynchronizer::inflate_locked_or_imse(obj(), inflate_cause_wait, CHECK_0);
+  } else {
+    // The ObjectMonitor* can't be async deflated because the _waiters
+    // field is incremented before ownership is dropped and decremented
+    // after ownership is regained.
+    monitor = inflate(current, obj(), inflate_cause_wait);
+  }
 
   DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), current, millis);
   monitor->wait(millis, true, THREAD); // Not CHECK as we need following code
 
   // This dummy call is in place to get around dtrace bug 6254741.  Once
@@ -834,13 +803,19 @@
     if (mark.has_locker() && current->is_lock_owned((address)mark.locker())) {
       // Not inflated so there can't be any waiters to notify.
       return;
     }
   }
-  // The ObjectMonitor* can't be async deflated until ownership is
-  // dropped by the calling thread.
-  ObjectMonitor* monitor = inflate(current, obj(), inflate_cause_notify);
+
+  ObjectMonitor* monitor;
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    monitor = LightweightSynchronizer::inflate_locked_or_imse(obj(), inflate_cause_notify, CHECK);
+  } else {
+    // The ObjectMonitor* can't be async deflated until ownership is
+    // dropped by the calling thread.
+    monitor = inflate(current, obj(), inflate_cause_notify);
+  }
   monitor->notify(CHECK);
 }
 
 // NOTE: see comment of notify()
 void ObjectSynchronizer::notifyall(Handle obj, TRAPS) {
@@ -856,13 +831,19 @@
     if (mark.has_locker() && current->is_lock_owned((address)mark.locker())) {
       // Not inflated so there can't be any waiters to notify.
       return;
     }
   }
-  // The ObjectMonitor* can't be async deflated until ownership is
-  // dropped by the calling thread.
-  ObjectMonitor* monitor = inflate(current, obj(), inflate_cause_notify);
+
+  ObjectMonitor* monitor;
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    monitor = LightweightSynchronizer::inflate_locked_or_imse(obj(), inflate_cause_notify, CHECK);
+  } else {
+    // The ObjectMonitor* can't be async deflated until ownership is
+    // dropped by the calling thread.
+    monitor = inflate(current, obj(), inflate_cause_notify);
+  }
   monitor->notifyAll(CHECK);
 }
 
 // -----------------------------------------------------------------------------
 // Hash Code handling
@@ -990,17 +971,25 @@
     v = (v ^ (v >> 19)) ^ (t ^ (t >> 8));
     current->_hashStateW = v;
     value = v;
   }
 
-  value &= markWord::hash_mask;
+  value &= UseCompactObjectHeaders ? markWord::hash_mask_compact : markWord::hash_mask;
   if (value == 0) value = 0xBAD;
   assert(value != markWord::no_hash, "invariant");
   return value;
 }
 
+intptr_t ObjectSynchronizer::get_next_hash(Thread* current, oop obj) {
+  // CLEANUP[Axel]: hack for LightweightSynchronizer being in different translation unit
+  return ::get_next_hash(current, obj);
+}
+
 intptr_t ObjectSynchronizer::FastHashCode(Thread* current, oop obj) {
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    return LightweightSynchronizer::FastHashCode(current, obj);
+  }
 
   while (true) {
     ObjectMonitor* monitor = nullptr;
     markWord temp, test;
     intptr_t hash;
@@ -1086,11 +1075,11 @@
     hash = mark.hash();
     if (hash == 0) {                       // if it does not have a hash
       hash = get_next_hash(current, obj);  // get a new hash
       temp = mark.copy_set_hash(hash)   ;  // merge the hash into header
       assert(temp.is_neutral(), "invariant: header=" INTPTR_FORMAT, temp.value());
-      uintptr_t v = Atomic::cmpxchg((volatile uintptr_t*)monitor->header_addr(), mark.value(), temp.value());
+      uintptr_t v = Atomic::cmpxchg(monitor->metadata_addr(), mark.value(), temp.value());
       test = markWord(v);
       if (test != mark) {
         // The attempt to update the ObjectMonitor's header/dmw field
         // did not work. This can happen if another thread managed to
         // merge in the hash just before our cmpxchg().
@@ -1129,15 +1118,29 @@
   if (LockingMode == LM_LIGHTWEIGHT && mark.is_fast_locked()) {
     // fast-locking case, see if lock is in current's lock stack
     return current->lock_stack().contains(h_obj());
   }
 
-  if (mark.has_monitor()) {
+  while (LockingMode == LM_LIGHTWEIGHT && mark.has_monitor()) {
+    ObjectMonitor* monitor = LightweightSynchronizer::read_monitor(current, obj);
+    if (monitor != nullptr) {
+      return monitor->is_entered(current) != 0;
+    }
+    // Racing with inflation/deflation, retry
+    mark = obj->mark_acquire();
+
+    if (mark.is_fast_locked()) {
+      // Some other thread fast_locked, current could not have held the lock
+      return false;
+    }
+  }
+
+  if (LockingMode != LM_LIGHTWEIGHT && mark.has_monitor()) {
     // Inflated monitor so header points to ObjectMonitor (tagged pointer).
     // The first stage of async deflation does not affect any field
     // used by this comparison so the ObjectMonitor* is usable here.
-    ObjectMonitor* monitor = mark.monitor();
+    ObjectMonitor* monitor = read_monitor(mark);
     return monitor->is_entered(current) != 0;
   }
   // Unlocked case, header in place
   assert(mark.is_unlocked(), "sanity check");
   return false;
@@ -1157,15 +1160,29 @@
     // fast-locked so get owner from the object.
     // owning_thread_from_object() may also return null here:
     return Threads::owning_thread_from_object(t_list, h_obj());
   }
 
-  if (mark.has_monitor()) {
+  while (LockingMode == LM_LIGHTWEIGHT && mark.has_monitor()) {
+    ObjectMonitor* monitor = LightweightSynchronizer::read_monitor(Thread::current(), obj);
+    if (monitor != nullptr) {
+      return Threads::owning_thread_from_monitor(t_list, monitor);
+    }
+    // Racing with inflation/deflation, retry
+    mark = obj->mark_acquire();
+
+    if (mark.is_fast_locked()) {
+      // Some other thread fast_locked
+      return Threads::owning_thread_from_object(t_list, h_obj());
+    }
+  }
+
+  if (LockingMode != LM_LIGHTWEIGHT && mark.has_monitor()) {
     // Inflated monitor so header points to ObjectMonitor (tagged pointer).
     // The first stage of async deflation does not affect any field
     // used by this comparison so the ObjectMonitor* is usable here.
-    ObjectMonitor* monitor = mark.monitor();
+    ObjectMonitor* monitor = read_monitor(mark);
     assert(monitor != nullptr, "monitor should be non-null");
     // owning_thread_from_monitor() may also return null here:
     return Threads::owning_thread_from_monitor(t_list, monitor);
   }
 
@@ -1237,11 +1254,11 @@
     return false;
   }
   if (NoAsyncDeflationProgressMax != 0 &&
       _no_progress_cnt >= NoAsyncDeflationProgressMax) {
     double remainder = (100.0 - MonitorUsedDeflationThreshold) / 100.0;
-    size_t new_ceiling = ceiling + (size_t)((double)ceiling * remainder) + 1;
+    size_t new_ceiling = ceiling / remainder + 1;
     ObjectSynchronizer::set_in_use_list_ceiling(new_ceiling);
     log_info(monitorinflation)("Too many deflations without progress; "
                                "bumping in_use_list_ceiling from " SIZE_FORMAT
                                " to " SIZE_FORMAT, old_ceiling, new_ceiling);
     _no_progress_cnt = 0;
@@ -1373,139 +1390,66 @@
   event->commit();
 }
 
 // Fast path code shared by multiple functions
 void ObjectSynchronizer::inflate_helper(oop obj) {
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    return;
+  }
   markWord mark = obj->mark_acquire();
   if (mark.has_monitor()) {
-    ObjectMonitor* monitor = mark.monitor();
+    ObjectMonitor* monitor = read_monitor(mark);
     markWord dmw = monitor->header();
     assert(dmw.is_neutral(), "sanity check: header=" INTPTR_FORMAT, dmw.value());
     return;
   }
   (void)inflate(Thread::current(), obj, inflate_cause_vm_internal);
 }
 
 ObjectMonitor* ObjectSynchronizer::inflate(Thread* current, oop obj, const InflateCause cause) {
   assert(current == Thread::current(), "must be");
-  if (LockingMode == LM_LIGHTWEIGHT && current->is_Java_thread()) {
-    return inflate_impl(JavaThread::cast(current), obj, cause);
-  }
-  return inflate_impl(nullptr, obj, cause);
+  assert(LockingMode != LM_LIGHTWEIGHT, "LM_LIGHTWEIGHT cannot use inflate");
+  return inflate_impl(obj, cause);
 }
 
 ObjectMonitor* ObjectSynchronizer::inflate_for(JavaThread* thread, oop obj, const InflateCause cause) {
   assert(thread == Thread::current() || thread->is_obj_deopt_suspend(), "must be");
-  return inflate_impl(thread, obj, cause);
-}
-
-ObjectMonitor* ObjectSynchronizer::inflate_impl(JavaThread* inflating_thread, oop object, const InflateCause cause) {
-  // The JavaThread* inflating_thread parameter is only used by LM_LIGHTWEIGHT and requires
-  // that the inflating_thread == Thread::current() or is suspended throughout the call by
-  // some other mechanism.
-  // Even with LM_LIGHTWEIGHT the thread might be nullptr when called from a non
-  // JavaThread. (As may still be the case from FastHashCode). However it is only
-  // important for the correctness of the LM_LIGHTWEIGHT algorithm that the thread
-  // is set when called from ObjectSynchronizer::enter from the owning thread,
-  // ObjectSynchronizer::enter_for from any thread, or ObjectSynchronizer::exit.
+  assert(LockingMode != LM_LIGHTWEIGHT, "LM_LIGHTWEIGHT cannot use inflate_for");
+  return inflate_impl(obj, cause);
+}
+
+ObjectMonitor* ObjectSynchronizer::inflate_impl(oop object, const InflateCause cause) {
+  assert(LockingMode != LM_LIGHTWEIGHT, "LM_LIGHTWEIGHT cannot use inflate_impl");
   EventJavaMonitorInflate event;
 
   for (;;) {
     const markWord mark = object->mark_acquire();
 
     // The mark can be in one of the following states:
-    // *  inflated     - Just return if using stack-locking.
-    //                   If using fast-locking and the ObjectMonitor owner
-    //                   is anonymous and the inflating_thread owns the
-    //                   object lock, then we make the inflating_thread
-    //                   the ObjectMonitor owner and remove the lock from
-    //                   the inflating_thread's lock stack.
-    // *  fast-locked  - Coerce it to inflated from fast-locked.
+    // *  inflated     - Just return it.
     // *  stack-locked - Coerce it to inflated from stack-locked.
     // *  INFLATING    - Busy wait for conversion from stack-locked to
     //                   inflated.
     // *  unlocked     - Aggressively inflate the object.
 
     // CASE: inflated
     if (mark.has_monitor()) {
       ObjectMonitor* inf = mark.monitor();
       markWord dmw = inf->header();
       assert(dmw.is_neutral(), "invariant: header=" INTPTR_FORMAT, dmw.value());
-      if (LockingMode == LM_LIGHTWEIGHT && inf->is_owner_anonymous() &&
-          inflating_thread != nullptr && inflating_thread->lock_stack().contains(object)) {
-        inf->set_owner_from_anonymous(inflating_thread);
-        size_t removed = inflating_thread->lock_stack().remove(object);
-        inf->set_recursions(removed - 1);
-      }
       return inf;
     }
 
-    if (LockingMode != LM_LIGHTWEIGHT) {
-      // New lightweight locking does not use INFLATING.
-      // CASE: inflation in progress - inflating over a stack-lock.
-      // Some other thread is converting from stack-locked to inflated.
-      // Only that thread can complete inflation -- other threads must wait.
-      // The INFLATING value is transient.
-      // Currently, we spin/yield/park and poll the markword, waiting for inflation to finish.
-      // We could always eliminate polling by parking the thread on some auxiliary list.
-      if (mark == markWord::INFLATING()) {
-        read_stable_mark(object);
-        continue;
-      }
-    }
-
-    // CASE: fast-locked
-    // Could be fast-locked either by the inflating_thread or by some other thread.
-    //
-    // Note that we allocate the ObjectMonitor speculatively, _before_
-    // attempting to set the object's mark to the new ObjectMonitor. If
-    // the inflating_thread owns the monitor, then we set the ObjectMonitor's
-    // owner to the inflating_thread. Otherwise, we set the ObjectMonitor's owner
-    // to anonymous. If we lose the race to set the object's mark to the
-    // new ObjectMonitor, then we just delete it and loop around again.
-    //
-    LogStreamHandle(Trace, monitorinflation) lsh;
-    if (LockingMode == LM_LIGHTWEIGHT && mark.is_fast_locked()) {
-      ObjectMonitor* monitor = new ObjectMonitor(object);
-      monitor->set_header(mark.set_unlocked());
-      bool own = inflating_thread != nullptr && inflating_thread->lock_stack().contains(object);
-      if (own) {
-        // Owned by inflating_thread.
-        monitor->set_owner_from(nullptr, inflating_thread);
-      } else {
-        // Owned by somebody else.
-        monitor->set_owner_anonymous();
-      }
-      markWord monitor_mark = markWord::encode(monitor);
-      markWord old_mark = object->cas_set_mark(monitor_mark, mark);
-      if (old_mark == mark) {
-        // Success! Return inflated monitor.
-        if (own) {
-          size_t removed = inflating_thread->lock_stack().remove(object);
-          monitor->set_recursions(removed - 1);
-        }
-        // Once the ObjectMonitor is configured and object is associated
-        // with the ObjectMonitor, it is safe to allow async deflation:
-        _in_use_list.add(monitor);
-
-        // Hopefully the performance counters are allocated on distinct
-        // cache lines to avoid false sharing on MP systems ...
-        OM_PERFDATA_OP(Inflations, inc());
-        if (log_is_enabled(Trace, monitorinflation)) {
-          ResourceMark rm;
-          lsh.print_cr("inflate(has_locker): object=" INTPTR_FORMAT ", mark="
-                       INTPTR_FORMAT ", type='%s'", p2i(object),
-                       object->mark().value(), object->klass()->external_name());
-        }
-        if (event.should_commit()) {
-          post_monitor_inflate_event(&event, object, cause);
-        }
-        return monitor;
-      } else {
-        delete monitor;
-        continue;  // Interference -- just retry
-      }
+    // CASE: inflation in progress - inflating over a stack-lock.
+    // Some other thread is converting from stack-locked to inflated.
+    // Only that thread can complete inflation -- other threads must wait.
+    // The INFLATING value is transient.
+    // Currently, we spin/yield/park and poll the markword, waiting for inflation to finish.
+    // We could always eliminate polling by parking the thread on some auxiliary list.
+    if (mark == markWord::INFLATING()) {
+      read_stable_mark(object);
+      continue;
     }
 
     // CASE: stack-locked
     // Could be stack-locked either by current or by some other thread.
     //
@@ -1515,12 +1459,12 @@
     // ObjectMonitor into the mark.  This was correct, but artificially lengthened
     // the interval in which INFLATING appeared in the mark, thus increasing
     // the odds of inflation contention. If we lose the race to set INFLATING,
     // then we just delete the ObjectMonitor and loop around again.
     //
+    LogStreamHandle(Trace, monitorinflation) lsh;
     if (LockingMode == LM_LEGACY && mark.has_locker()) {
-      assert(LockingMode != LM_LIGHTWEIGHT, "cannot happen with new lightweight locking");
       ObjectMonitor* m = new ObjectMonitor(object);
       // Optimistically prepare the ObjectMonitor - anticipate successful CAS
       // We do this before the CAS in order to minimize the length of time
       // in which INFLATING appears in the mark.
 
@@ -1648,17 +1592,18 @@
 // ObjectMonitors. Returns the number of deflated ObjectMonitors.
 //
 size_t ObjectSynchronizer::deflate_monitor_list(ObjectMonitorDeflationSafepointer* safepointer) {
   MonitorList::Iterator iter = _in_use_list.iterator();
   size_t deflated_count = 0;
+  Thread* current = Thread::current();
 
   while (iter.has_next()) {
     if (deflated_count >= (size_t)MonitorDeflationMax) {
       break;
     }
     ObjectMonitor* mid = iter.next();
-    if (mid->deflate_monitor()) {
+    if (mid->deflate_monitor(current)) {
       deflated_count++;
     }
 
     // Must check for a safepoint/handshake and honor it.
     safepointer->block_for_safepoint("deflation", "deflated_count", deflated_count);
@@ -1672,10 +1617,15 @@
   HandshakeForDeflation() : HandshakeClosure("HandshakeForDeflation") {}
 
   void do_thread(Thread* thread) {
     log_trace(monitorinflation)("HandshakeForDeflation::do_thread: thread="
                                 INTPTR_FORMAT, p2i(thread));
+    if (thread->is_Java_thread()) {
+      // Clear OM cache
+      JavaThread* jt = JavaThread::cast(thread);
+      jt->om_clear_monitor_cache();
+    }
   }
 };
 
 class VM_RendezvousGCThreads : public VM_Operation {
 public:
@@ -1818,10 +1768,18 @@
   if (deflated_count > 0) {
     ResourceMark rm(current);
     GrowableArray<ObjectMonitor*> delete_list((int)deflated_count);
     unlinked_count = _in_use_list.unlink_deflated(deflated_count, &delete_list, &safepointer);
 
+#ifdef ASSERT
+    if (LockingMode == LM_LIGHTWEIGHT) {
+      for (ObjectMonitor* monitor : delete_list) {
+        assert(!LightweightSynchronizer::contains_monitor(current, monitor), "Should have been removed");
+      }
+    }
+#endif
+
     log.before_handshake(unlinked_count);
 
     // A JavaThread needs to handshake in order to safely free the
     // ObjectMonitors that were deflated in this cycle.
     HandshakeForDeflation hfd_hc;
@@ -2026,33 +1984,39 @@
   if (n->owner_is_DEFLATER_MARKER()) {
     // This could happen when monitor deflation blocks for a safepoint.
     return;
   }
 
-  if (n->header().value() == 0) {
+
+  if (n->metadata() == 0) {
     out->print_cr("ERROR: monitor=" INTPTR_FORMAT ": in-use monitor must "
-                  "have non-null _header field.", p2i(n));
+                  "have non-null _metadata (header/hash) field.", p2i(n));
     *error_cnt_p = *error_cnt_p + 1;
   }
+
   const oop obj = n->object_peek();
-  if (obj != nullptr) {
-    const markWord mark = obj->mark();
-    if (!mark.has_monitor()) {
-      out->print_cr("ERROR: monitor=" INTPTR_FORMAT ": in-use monitor's "
-                    "object does not think it has a monitor: obj="
-                    INTPTR_FORMAT ", mark=" INTPTR_FORMAT, p2i(n),
-                    p2i(obj), mark.value());
-      *error_cnt_p = *error_cnt_p + 1;
-    }
-    ObjectMonitor* const obj_mon = mark.monitor();
-    if (n != obj_mon) {
-      out->print_cr("ERROR: monitor=" INTPTR_FORMAT ": in-use monitor's "
-                    "object does not refer to the same monitor: obj="
-                    INTPTR_FORMAT ", mark=" INTPTR_FORMAT ", obj_mon="
-                    INTPTR_FORMAT, p2i(n), p2i(obj), mark.value(), p2i(obj_mon));
-      *error_cnt_p = *error_cnt_p + 1;
-    }
+  if (obj == nullptr) {
+    return;
+  }
+
+  const markWord mark = obj->mark();
+  if (!mark.has_monitor()) {
+    out->print_cr("ERROR: monitor=" INTPTR_FORMAT ": in-use monitor's "
+                  "object does not think it has a monitor: obj="
+                  INTPTR_FORMAT ", mark=" INTPTR_FORMAT, p2i(n),
+                  p2i(obj), mark.value());
+    *error_cnt_p = *error_cnt_p + 1;
+    return;
+  }
+
+  ObjectMonitor* const obj_mon = read_monitor(Thread::current(), obj, mark);
+  if (n != obj_mon) {
+    out->print_cr("ERROR: monitor=" INTPTR_FORMAT ": in-use monitor's "
+                  "object does not refer to the same monitor: obj="
+                  INTPTR_FORMAT ", mark=" INTPTR_FORMAT ", obj_mon="
+                  INTPTR_FORMAT, p2i(n), p2i(obj), mark.value(), p2i(obj_mon));
+    *error_cnt_p = *error_cnt_p + 1;
   }
 }
 
 // Log details about ObjectMonitors on the in_use_list. The 'BHL'
 // flags indicate why the entry is in-use, 'object' and 'object type'
@@ -2071,14 +2035,14 @@
     };
 
     monitors_iterate([&](ObjectMonitor* monitor) {
       if (is_interesting(monitor)) {
         const oop obj = monitor->object_peek();
-        const markWord mark = monitor->header();
+        const intptr_t hash = LockingMode == LM_LIGHTWEIGHT ? monitor->hash() : monitor->header().hash();
         ResourceMark rm;
         out->print(INTPTR_FORMAT "  %d%d%d  " INTPTR_FORMAT "  %s", p2i(monitor),
-                   monitor->is_busy(), mark.hash() != 0, monitor->owner() != nullptr,
+                   monitor->is_busy(), hash != 0, monitor->owner() != nullptr,
                    p2i(obj), obj == nullptr ? "" : obj->klass()->external_name());
         if (monitor->is_busy()) {
           out->print(" (%s)", monitor->is_busy_to_string(&ss));
           ss.reset();
         }
diff a/src/hotspot/share/runtime/synchronizer.hpp b/src/hotspot/share/runtime/synchronizer.hpp
--- a/src/hotspot/share/runtime/synchronizer.hpp
+++ b/src/hotspot/share/runtime/synchronizer.hpp
@@ -27,10 +27,11 @@
 
 #include "memory/padded.hpp"
 #include "oops/markWord.hpp"
 #include "runtime/basicLock.hpp"
 #include "runtime/handles.hpp"
+#include "runtime/javaThread.hpp"
 #include "utilities/resourceHash.hpp"
 
 template <typename T> class GrowableArray;
 class LogStream;
 class ObjectMonitor;
@@ -124,17 +125,20 @@
   // Used to inflate a monitor as if it was done from the thread JavaThread.
   static ObjectMonitor* inflate_for(JavaThread* thread, oop obj, const InflateCause cause);
 
 private:
   // Shared implementation between the different LockingMode.
-  static ObjectMonitor* inflate_impl(JavaThread* thread, oop obj, const InflateCause cause);
+  static ObjectMonitor* inflate_impl(oop obj, const InflateCause cause);
 
 public:
   // This version is only for internal use
   static void inflate_helper(oop obj);
   static const char* inflate_cause_name(const InflateCause cause);
 
+  static ObjectMonitor* read_monitor(markWord mark);
+  static ObjectMonitor* read_monitor(Thread* current, oop obj, markWord mark);
+
   // Returns the identity hash value for an oop
   // NOTE: It may cause monitor inflation
   static intptr_t FastHashCode(Thread* current, oop obj);
 
   // java.lang.Thread support
@@ -192,10 +196,13 @@
   static void do_final_audit_and_print_stats();
   static void log_in_use_monitor_details(outputStream* out, bool log_all);
 
  private:
   friend class SynchronizerTest;
+  friend class LightweightSynchronizer;
+
+  static intptr_t get_next_hash(Thread* current, oop obj);
 
   static MonitorList _in_use_list;
   static volatile bool _is_async_deflation_requested;
   static volatile bool _is_final_audit;
   static jlong         _last_async_deflation_time_ns;
diff a/src/hotspot/share/runtime/vframe.cpp b/src/hotspot/share/runtime/vframe.cpp
--- a/src/hotspot/share/runtime/vframe.cpp
+++ b/src/hotspot/share/runtime/vframe.cpp
@@ -244,17 +244,20 @@
           // an inflated monitor that is first on the monitor list in
           // the first frame can block us on a monitor enter.
           markWord mark = monitor->owner()->mark();
           // The first stage of async deflation does not affect any field
           // used by this comparison so the ObjectMonitor* is usable here.
-          if (mark.has_monitor() &&
-              ( // we have marked ourself as pending on this monitor
-                mark.monitor() == thread()->current_pending_monitor() ||
+          if (mark.has_monitor()) {
+            ObjectMonitor* mon = ObjectSynchronizer::read_monitor(current, monitor->owner(), mark);
+            if (// if the monitor is null we must be in the process of locking
+                mon == nullptr ||
+                // we have marked ourself as pending on this monitor
+                mon == thread()->current_pending_monitor() ||
                 // we are not the owner of this monitor
-                !mark.monitor()->is_entered(thread())
-              )) {
-            lock_state = "waiting to lock";
+                !mon->is_entered(thread())) {
+              lock_state = "waiting to lock";
+            }
           }
         }
         print_locked_object_class_name(st, Handle(current, monitor->owner()), lock_state);
 
         found_first_monitor = true;
diff a/src/hotspot/share/runtime/vmStructs.cpp b/src/hotspot/share/runtime/vmStructs.cpp
--- a/src/hotspot/share/runtime/vmStructs.cpp
+++ b/src/hotspot/share/runtime/vmStructs.cpp
@@ -776,15 +776,15 @@
                                                                                                                                      \
   /************/                                                                                                                     \
   /* Monitors */                                                                                                                     \
   /************/                                                                                                                     \
                                                                                                                                      \
-  volatile_nonstatic_field(ObjectMonitor,      _header,                                       markWord)                              \
+  volatile_nonstatic_field(ObjectMonitor,      _metadata,                                     uintptr_t)                             \
   unchecked_nonstatic_field(ObjectMonitor,     _object,                                       sizeof(void *)) /* NOTE: no type */    \
   unchecked_nonstatic_field(ObjectMonitor,     _owner,                                        sizeof(void *)) /* NOTE: no type */    \
   volatile_nonstatic_field(ObjectMonitor,      _next_om,                                      ObjectMonitor*)                        \
-  volatile_nonstatic_field(BasicLock,          _displaced_header,                             markWord)                              \
+  volatile_nonstatic_field(BasicLock,          _metadata,                                     uintptr_t)                             \
   nonstatic_field(ObjectMonitor,               _contentions,                                  int)                                   \
   volatile_nonstatic_field(ObjectMonitor,      _waiters,                                      int)                                   \
   volatile_nonstatic_field(ObjectMonitor,      _recursions,                                   intx)                                  \
   nonstatic_field(BasicObjectLock,             _lock,                                         BasicLock)                             \
   nonstatic_field(BasicObjectLock,             _obj,                                          oop)                                   \
@@ -1991,12 +1991,10 @@
   declare_constant(oopSize)                                               \
   declare_constant(LogBytesPerWord)                                       \
   declare_constant(BytesPerWord)                                          \
   declare_constant(BytesPerLong)                                          \
                                                                           \
-  declare_constant(LogKlassAlignmentInBytes)                              \
-                                                                          \
   declare_constant(HeapWordSize)                                          \
   declare_constant(LogHeapWordSize)                                       \
                                                                           \
                                                                           \
   /************************/                                              \
@@ -2497,21 +2495,26 @@
                                                                           \
   declare_constant(markWord::age_bits)                                    \
   declare_constant(markWord::lock_bits)                                   \
   declare_constant(markWord::max_hash_bits)                               \
   declare_constant(markWord::hash_bits)                                   \
+  declare_constant(markWord::hash_bits_compact)                           \
                                                                           \
   declare_constant(markWord::lock_shift)                                  \
   declare_constant(markWord::age_shift)                                   \
   declare_constant(markWord::hash_shift)                                  \
+  declare_constant(markWord::hash_shift_compact)                          \
+  LP64_ONLY(declare_constant(markWord::klass_shift))                      \
                                                                           \
   declare_constant(markWord::lock_mask)                                   \
   declare_constant(markWord::lock_mask_in_place)                          \
   declare_constant(markWord::age_mask)                                    \
   declare_constant(markWord::age_mask_in_place)                           \
   declare_constant(markWord::hash_mask)                                   \
   declare_constant(markWord::hash_mask_in_place)                          \
+  declare_constant(markWord::hash_mask_compact)                           \
+  declare_constant(markWord::hash_mask_compact_in_place)                  \
                                                                           \
   declare_constant(markWord::locked_value)                                \
   declare_constant(markWord::unlocked_value)                              \
   declare_constant(markWord::monitor_value)                               \
   declare_constant(markWord::marked_value)                                \
diff a/src/hotspot/share/utilities/fastHash.hpp b/src/hotspot/share/utilities/fastHash.hpp
--- /dev/null
+++ b/src/hotspot/share/utilities/fastHash.hpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2023, 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef SHARE_UTILITIES_FASTHASH_HPP
+#define SHARE_UTILITIES_FASTHASH_HPP
+
+#include "memory/allStatic.hpp"
+
+class FastHash : public AllStatic {
+private:
+  static void fullmul64(uint64_t& hi, uint64_t& lo, uint64_t op1, uint64_t op2) {
+#if defined(__SIZEOF_INT128__)
+    __uint128_t prod = static_cast<__uint128_t>(op1) * static_cast<__uint128_t>(op2);
+    hi = static_cast<uint64_t>(prod >> 64);
+    lo = static_cast<uint64_t>(prod >>  0);
+#else
+    /* First calculate all of the cross products. */
+    uint64_t lo_lo = (op1 & 0xFFFFFFFF) * (op2 & 0xFFFFFFFF);
+    uint64_t hi_lo = (op1 >> 32)        * (op2 & 0xFFFFFFFF);
+    uint64_t lo_hi = (op1 & 0xFFFFFFFF) * (op2 >> 32);
+    uint64_t hi_hi = (op1 >> 32)        * (op2 >> 32);
+
+    /* Now add the products together. These will never overflow. */
+    uint64_t cross = (lo_lo >> 32) + (hi_lo & 0xFFFFFFFF) + lo_hi;
+    uint64_t upper = (hi_lo >> 32) + (cross >> 32)        + hi_hi;
+    hi = upper;
+    lo = (cross << 32) | (lo_lo & 0xFFFFFFFF);
+#endif
+  }
+
+  static void fullmul32(uint32_t& hi, uint32_t& lo, uint32_t op1, uint32_t op2) {
+    uint64_t x64 = op1, y64 = op2, xy64 = x64 * y64;
+    hi = (uint32_t)(xy64 >> 32);
+    lo = (uint32_t)(xy64 >>  0);
+  }
+
+  static uint64_t ror(uint64_t x, uint64_t distance) {
+    distance = distance & 0x3F;
+    return (x >> distance) | (x << (64 - distance));
+  }
+
+public:
+  static uint64_t get_hash64(uint64_t x, uint64_t y) {
+    const uint64_t M  = 0x8ADAE89C337954D5;
+    const uint64_t A  = 0xAAAAAAAAAAAAAAAA; // REPAA
+    const uint64_t H0 = (x ^ y), L0 = (x ^ A);
+
+    uint64_t U0, V0; fullmul64(U0, V0, L0, M);
+    const uint64_t Q0 = (H0 * M);
+    const uint64_t L1 = (Q0 ^ U0);
+
+    uint64_t U1, V1; fullmul64(U1, V1, L1, M);
+    const uint64_t P1 = (V0 ^ M);
+    const uint64_t Q1 = ror(P1, L1);
+    const uint64_t L2 = (Q1 ^ U1);
+    return V1 ^ L2;
+  }
+
+  static uint32_t get_hash32(uint32_t x, uint32_t y) {
+    const uint32_t M  = 0x337954D5;
+    const uint32_t A  = 0xAAAAAAAA; // REPAA
+    const uint32_t H0 = (x ^ y), L0 = (x ^ A);
+
+    uint32_t U0, V0; fullmul32(U0, V0, L0, M);
+    const uint32_t Q0 = (H0 * M);
+    const uint32_t L1 = (Q0 ^ U0);
+
+    uint32_t U1, V1; fullmul32(U1, V1, L1, M);
+    const uint32_t P1 = (V0 ^ M);
+    const uint32_t Q1 = ror(P1, L1);
+    const uint32_t L2 = (Q1 ^ U1);
+    return V1 ^ L2;
+  }
+};
+
+#endif// SHARE_UTILITIES_FASTHASH_HPP
diff a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp
--- a/src/hotspot/share/utilities/globalDefinitions.hpp
+++ b/src/hotspot/share/utilities/globalDefinitions.hpp
@@ -1016,11 +1016,11 @@
   // Use only heavy monitors for locking
   LM_MONITOR     = 0,
   // Legacy stack-locking, with monitors as 2nd tier
   LM_LEGACY      = 1,
   // New lightweight locking, with monitors as 2nd tier
-  LM_LIGHTWEIGHT = 2
+  LM_LIGHTWEIGHT = 2,
 };
 
 //----------------------------------------------------------------------------------------------------
 // Special constants for debugging
 
diff a/src/hotspot/share/utilities/vmError.cpp b/src/hotspot/share/utilities/vmError.cpp
--- a/src/hotspot/share/utilities/vmError.cpp
+++ b/src/hotspot/share/utilities/vmError.cpp
@@ -573,11 +573,11 @@
    st->print_cr("# JRE version: %s%s%s (%s) (%sbuild %s)", runtime_name,
                 (*vendor_version != '\0') ? " " : "", vendor_version,
                 buf, jdk_debug_level, runtime_version);
 
    // This is the long version with some default settings added
-   st->print_cr("# Java VM: %s%s%s (%s%s, %s%s%s%s%s%s, %s, %s)",
+   st->print_cr("# Java VM: %s%s%s (%s%s, %s%s%s%s%s%s%s, %s, %s)",
                  VM_Version::vm_name(),
                 (*vendor_version != '\0') ? " " : "", vendor_version,
                  jdk_debug_level,
                  VM_Version::vm_release(),
                  VM_Version::vm_info_string(),
@@ -588,10 +588,13 @@
 #else
                  "", "",
 #endif
                  UseCompressedOops ? ", compressed oops" : "",
                  UseCompressedClassPointers ? ", compressed class ptrs" : "",
+                 LockingMode == LM_MONITOR ? ", lm_monitors" :
+                 LockingMode == LM_LEGACY ? ", lm_legacy" :
+                 LockingMode == LM_LIGHTWEIGHT ? ", lm_lightweight" : "",
                  GCConfig::hs_err_name(),
                  VM_Version::vm_platform_string()
                );
 }
 
@@ -1114,11 +1117,11 @@
       _verbose && _context != nullptr && _thread != nullptr && Universe::is_fully_initialized())
     ResourceMark rm(_thread);
     print_stack_location(st, _context, continuation);
     st->cr();
 
-  STEP_IF("printing lock stack", _verbose && _thread != nullptr && _thread->is_Java_thread() && LockingMode == LM_LIGHTWEIGHT);
+  STEP_IF("printing lock stack", _verbose && _thread != nullptr && _thread->is_Java_thread() && (LockingMode == LM_LIGHTWEIGHT));
     st->print_cr("Lock stack of current Java thread (top to bottom):");
     JavaThread::cast(_thread)->lock_stack().print_on(st);
     st->cr();
 
   STEP_IF("printing code blobs if possible", _verbose)
diff a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/DebuggerBase.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/DebuggerBase.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/DebuggerBase.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/DebuggerBase.java
@@ -22,10 +22,13 @@
  *
  */
 
 package sun.jvm.hotspot.debugger;
 
+import sun.jvm.hotspot.oops.Mark;
+import sun.jvm.hotspot.runtime.VM;
+
 /** <P> DebuggerBase is a recommended base class for debugger
     implementations. It can use a PageCache to cache data from the
     target process. Note that this class would not be suitable if the
     system were used to reflect upon itself; it would never be safe to
     store the value in an OopHandle in anything but an OopHandle.
@@ -392,11 +395,19 @@
     return value;
   }
 
   protected long readCompKlassAddressValue(long address)
     throws UnmappedAddressException, UnalignedAddressException {
-    long value = readCInteger(address, getKlassPtrSize(), true);
+    long value;
+    if (VM.getVM().isCompactObjectHeadersEnabled()) {
+      // With compact headers, the compressed Klass* is currently read from the mark
+      // word. We need to load the whole mark, and shift the upper parts.
+      value = readCInteger(address, machDesc.getAddressSize(), true);
+      value = value >>> Mark.getKlassShift();
+    } else {
+      value = readCInteger(address, getKlassPtrSize(), true);
+    }
     if (value != 0) {
       value = (long)(narrowKlassBase + (long)(value << narrowKlassShift));
     }
     return value;
   }
diff a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Array.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Array.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Array.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Array.java
@@ -79,11 +79,13 @@
 
   private static long lengthOffsetInBytes() {
     if (lengthOffsetInBytes != 0) {
       return lengthOffsetInBytes;
     }
-    if (VM.getVM().isCompressedKlassPointersEnabled()) {
+    if (VM.getVM().isCompactObjectHeadersEnabled()) {
+      lengthOffsetInBytes = Oop.getHeaderSize();
+    } else if (VM.getVM().isCompressedKlassPointersEnabled()) {
       lengthOffsetInBytes = typeSize - VM.getVM().getIntSize();
     } else {
       lengthOffsetInBytes = typeSize;
     }
     return lengthOffsetInBytes;
diff a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Instance.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Instance.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Instance.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Instance.java
@@ -53,11 +53,13 @@
     super(handle, heap);
   }
 
   // Returns header size in bytes.
   public static long getHeaderSize() {
-    if (VM.getVM().isCompressedKlassPointersEnabled()) {
+    if (VM.getVM().isCompactObjectHeadersEnabled()) {
+      return Oop.getHeaderSize();
+    } else if (VM.getVM().isCompressedKlassPointersEnabled()) {
       return typeSize - VM.getVM().getIntSize();
     } else {
       return typeSize;
     }
   }
diff a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Mark.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Mark.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Mark.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Mark.java
@@ -49,19 +49,26 @@
 
     ageBits             = db.lookupLongConstant("markWord::age_bits").longValue();
     lockBits            = db.lookupLongConstant("markWord::lock_bits").longValue();
     maxHashBits         = db.lookupLongConstant("markWord::max_hash_bits").longValue();
     hashBits            = db.lookupLongConstant("markWord::hash_bits").longValue();
+    hashBitsCompact     = db.lookupLongConstant("markWord::hash_bits_compact").longValue();
     lockShift           = db.lookupLongConstant("markWord::lock_shift").longValue();
     ageShift            = db.lookupLongConstant("markWord::age_shift").longValue();
     hashShift           = db.lookupLongConstant("markWord::hash_shift").longValue();
+    hashShiftCompact    = db.lookupLongConstant("markWord::hash_shift_compact").longValue();
+    if (VM.getVM().isLP64()) {
+      klassShift          = db.lookupLongConstant("markWord::klass_shift").longValue();
+    }
     lockMask            = db.lookupLongConstant("markWord::lock_mask").longValue();
     lockMaskInPlace     = db.lookupLongConstant("markWord::lock_mask_in_place").longValue();
     ageMask             = db.lookupLongConstant("markWord::age_mask").longValue();
     ageMaskInPlace      = db.lookupLongConstant("markWord::age_mask_in_place").longValue();
     hashMask            = db.lookupLongConstant("markWord::hash_mask").longValue();
     hashMaskInPlace     = db.lookupLongConstant("markWord::hash_mask_in_place").longValue();
+    hashMaskCompact     = db.lookupLongConstant("markWord::hash_mask_compact").longValue();
+    hashMaskCompactInPlace = db.lookupLongConstant("markWord::hash_mask_compact_in_place").longValue();
     lockedValue         = db.lookupLongConstant("markWord::locked_value").longValue();
     unlockedValue       = db.lookupLongConstant("markWord::unlocked_value").longValue();
     monitorValue        = db.lookupLongConstant("markWord::monitor_value").longValue();
     markedValue         = db.lookupLongConstant("markWord::marked_value").longValue();
     noHash              = db.lookupLongConstant("markWord::no_hash").longValue();
@@ -76,21 +83,26 @@
   // Constants -- read from VM
   private static long ageBits;
   private static long lockBits;
   private static long maxHashBits;
   private static long hashBits;
+  private static long hashBitsCompact;
 
   private static long lockShift;
   private static long ageShift;
   private static long hashShift;
+  private static long hashShiftCompact;
+  private static long klassShift;
 
   private static long lockMask;
   private static long lockMaskInPlace;
   private static long ageMask;
   private static long ageMaskInPlace;
   private static long hashMask;
   private static long hashMaskInPlace;
+  private static long hashMaskCompact;
+  private static long hashMaskCompactInPlace;
 
   private static long lockedValue;
   private static long unlockedValue;
   private static long monitorValue;
   private static long markedValue;
@@ -100,10 +112,14 @@
   private static long noHashInPlace;
   private static long noLockInPlace;
 
   private static long maxAge;
 
+  public static long getKlassShift() {
+    return klassShift;
+  }
+
   public Mark(Address addr) {
     super(addr);
   }
 
   public long value() {
@@ -154,10 +170,20 @@
   }
   public ObjectMonitor monitor() {
     if (Assert.ASSERTS_ENABLED) {
       Assert.that(hasMonitor(), "check");
     }
+    if (VM.getVM().getCommandLineFlag("LockingMode").getInt() == LockingMode.getLightweight()) {
+      Iterator it = ObjectSynchronizer.objectMonitorIterator();
+      while (it != null && it.hasNext()) {
+        ObjectMonitor mon = (ObjectMonitor)it.next();
+        if (getAddress().equals(mon.object())) {
+          return mon;
+        }
+      }
+      return null;
+    }
     // Use xor instead of &~ to provide one extra tag-bit check.
     Address monAddr = valueAsAddress().xorWithMask(monitorValue);
     return new ObjectMonitor(monAddr);
   }
   public boolean hasDisplacedMarkHelper() {
@@ -172,17 +198,26 @@
   }
   public int age() { return (int) Bits.maskBitsLong(value() >> ageShift, ageMask); }
 
   // hash operations
   public long hash() {
-    return Bits.maskBitsLong(value() >> hashShift, hashMask);
+    if (VM.getVM().isCompactObjectHeadersEnabled()) {
+      return Bits.maskBitsLong(value() >> hashShiftCompact, hashMaskCompact);
+    } else {
+      return Bits.maskBitsLong(value() >> hashShift, hashMask);
+    }
   }
 
   public boolean hasNoHash() {
     return hash() == noHash;
   }
 
+  public Klass getKlass() {
+    assert(VM.getVM().isCompactObjectHeadersEnabled());
+    return (Klass)Metadata.instantiateWrapperFor(addr.getCompKlassAddressAt(0));
+  }
+
   // Debugging
   public void printOn(PrintStream tty) {
     if (isLocked()) {
       tty.print("locked(0x" +
                 Long.toHexString(value()) + ")->");
diff a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Oop.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Oop.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Oop.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Oop.java
@@ -44,13 +44,18 @@
   }
 
   private static synchronized void initialize(TypeDataBase db) throws WrongTypeException {
     Type type  = db.lookupType("oopDesc");
     mark       = new CIntField(type.getCIntegerField("_mark"), 0);
-    klass      = new MetadataField(type.getAddressField("_metadata._klass"), 0);
-    compressedKlass  = new NarrowKlassField(type.getAddressField("_metadata._compressed_klass"), 0);
-    headerSize = type.getSize();
+    if (VM.getVM().isCompactObjectHeadersEnabled()) {
+      Type markType = db.lookupType("markWord");
+      headerSize = markType.getSize();
+    } else {
+      headerSize = type.getSize();
+      klass      = new MetadataField(type.getAddressField("_metadata._klass"), 0);
+      compressedKlass  = new NarrowKlassField(type.getAddressField("_metadata._compressed_klass"), 0);
+    }
   }
 
   private OopHandle  handle;
   private ObjectHeap heap;
 
@@ -73,12 +78,21 @@
   private static MetadataField  klass;
   private static NarrowKlassField compressedKlass;
 
   // Accessors for declared fields
   public Mark  getMark()   { return new Mark(getHandle()); }
+
+  private static Klass getKlass(Mark mark) {
+    assert(VM.getVM().isCompactObjectHeadersEnabled());
+    return mark.getKlass();
+  }
+
   public Klass getKlass() {
-    if (VM.getVM().isCompressedKlassPointersEnabled()) {
+    if (VM.getVM().isCompactObjectHeadersEnabled()) {
+      assert(VM.getVM().isCompressedKlassPointersEnabled());
+      return getKlass(getMark());
+    } else if (VM.getVM().isCompressedKlassPointersEnabled()) {
       return (Klass)compressedKlass.getValue(getHandle());
     } else {
       return (Klass)klass.getValue(getHandle());
     }
   }
@@ -145,14 +159,16 @@
   }
 
   void iterateFields(OopVisitor visitor, boolean doVMFields) {
     if (doVMFields) {
       visitor.doCInt(mark, true);
-      if (VM.getVM().isCompressedKlassPointersEnabled()) {
-        visitor.doMetadata(compressedKlass, true);
-      } else {
-        visitor.doMetadata(klass, true);
+      if (!VM.getVM().isCompactObjectHeadersEnabled()) {
+        if (VM.getVM().isCompressedKlassPointersEnabled()) {
+          visitor.doMetadata(compressedKlass, true);
+        } else {
+          visitor.doMetadata(klass, true);
+        }
       }
     }
   }
 
   public void print()      { printOn(System.out); }
@@ -204,11 +220,14 @@
 
   public static Klass getKlassForOopHandle(OopHandle handle) {
     if (handle == null) {
       return null;
     }
-    if (VM.getVM().isCompressedKlassPointersEnabled()) {
+    if (VM.getVM().isCompactObjectHeadersEnabled()) {
+      Mark mark = new Mark(handle);
+      return getKlass(mark);
+    } else if (VM.getVM().isCompressedKlassPointersEnabled()) {
       return (Klass)Metadata.instantiateWrapperFor(handle.getCompKlassAddressAt(compressedKlass.getOffset()));
     } else {
       return (Klass)Metadata.instantiateWrapperFor(handle.getAddressAt(klass.getOffset()));
     }
   }
diff a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/BasicLock.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/BasicLock.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/BasicLock.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/BasicLock.java
@@ -41,11 +41,11 @@
       });
   }
 
   private static synchronized void initialize(TypeDataBase db) throws WrongTypeException {
     Type type  = db.lookupType("BasicLock");
-    displacedHeaderField = type.getCIntegerField("_displaced_header");
+    displacedHeaderField = type.getCIntegerField("_metadata");
   }
 
   private static CIntegerField displacedHeaderField;
 
   public BasicLock(Address addr) {
diff a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ObjectMonitor.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ObjectMonitor.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ObjectMonitor.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ObjectMonitor.java
@@ -43,12 +43,12 @@
 
   private static synchronized void initialize(TypeDataBase db) throws WrongTypeException {
     heap = VM.getVM().getObjectHeap();
     Type type  = db.lookupType("ObjectMonitor");
 
-    sun.jvm.hotspot.types.Field f = type.getField("_header");
-    headerFieldOffset = f.getOffset();
+    sun.jvm.hotspot.types.Field f = type.getField("_metadata");
+    metadataFieldOffset = f.getOffset();
     f = type.getField("_object");
     objectFieldOffset = f.getOffset();
     f = type.getField("_owner");
     ownerFieldOffset = f.getOffset();
     f = type.getField("_next_om");
@@ -63,11 +63,11 @@
   public ObjectMonitor(Address addr) {
     super(addr);
   }
 
   public Mark header() {
-    return new Mark(addr.addOffsetTo(headerFieldOffset));
+    return new Mark(addr.addOffsetTo(metadataFieldOffset));
   }
 
   // FIXME
   //  void      set_header(markWord hdr);
 
@@ -112,11 +112,11 @@
 
   // The following four either aren't expressed as typed fields in
   // vmStructs.cpp because they aren't strongly typed in the VM, or
   // would confuse the SA's type system.
   private static ObjectHeap    heap;
-  private static long          headerFieldOffset;
+  private static long          metadataFieldOffset;
   private static long          objectFieldOffset;
   private static long          ownerFieldOffset;
   private static long          nextOMFieldOffset;
   private static CIntField     contentionsField;
   private static CIntField     waitersField;
diff a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ObjectSynchronizer.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ObjectSynchronizer.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ObjectSynchronizer.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ObjectSynchronizer.java
@@ -53,10 +53,13 @@
     Mark mark = obj.getMark();
     if (mark.isUnlocked()) {
       // FIXME: can not generate marks in debugging system
       return mark.hash();
     } else if (mark.hasMonitor()) {
+      if (VM.getVM().getCommandLineFlag("LockingMode").getInt() == LockingMode.getLightweight()) {
+        return mark.hash();
+      }
       ObjectMonitor monitor = mark.monitor();
       Mark temp = monitor.header();
       return temp.hash();
     } else {
       if (Assert.ASSERTS_ENABLED) {
diff a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/VM.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/VM.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/VM.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/VM.java
@@ -146,10 +146,11 @@
   private static Type uint64tType;
   private static CIntegerType boolType;
   private Boolean sharingEnabled;
   private Boolean compressedOopsEnabled;
   private Boolean compressedKlassPointersEnabled;
+  private Boolean compactObjectHeadersEnabled;
 
   // command line flags supplied to VM - see struct JVMFlag in jvmFlag.hpp
   public static final class Flag {
      private String type;
      private String name;
@@ -958,10 +959,19 @@
              (flag.getBool()? Boolean.TRUE: Boolean.FALSE);
     }
     return compressedKlassPointersEnabled.booleanValue();
   }
 
+  public boolean isCompactObjectHeadersEnabled() {
+    if (compactObjectHeadersEnabled == null) {
+        Flag flag = getCommandLineFlag("UseCompactObjectHeaders");
+        compactObjectHeadersEnabled = (flag == null) ? Boolean.FALSE:
+             (flag.getBool()? Boolean.TRUE: Boolean.FALSE);
+    }
+    return compactObjectHeadersEnabled.booleanValue();
+  }
+
   public int getObjectAlignmentInBytes() {
     if (objectAlignmentInBytes == 0) {
         Flag flag = getCommandLineFlag("ObjectAlignmentInBytes");
         objectAlignmentInBytes = (flag == null) ? 8 : (int)flag.getInt();
     }
diff a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/RobustOopDeterminator.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/RobustOopDeterminator.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/RobustOopDeterminator.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/RobustOopDeterminator.java
@@ -24,10 +24,11 @@
 
 package sun.jvm.hotspot.utilities;
 
 import sun.jvm.hotspot.debugger.*;
 import sun.jvm.hotspot.oops.Metadata;
+import sun.jvm.hotspot.oops.Oop;
 import sun.jvm.hotspot.runtime.*;
 import sun.jvm.hotspot.types.*;
 
 /** This class determines to the best of its ability, and in a
     reasonably robust fashion, whether a given pointer is an intact
@@ -35,44 +36,20 @@
     metaclass hierarchy. This is only intended for use in the
     debugging system. It may provide more resilience to unexpected VM
     states than the ObjectHeap code. */
 
 public class RobustOopDeterminator {
-  private static AddressField klassField;
-
-  static {
-    VM.registerVMInitializedObserver(new Observer() {
-        public void update(Observable o, Object data) {
-          initialize(VM.getVM().getTypeDataBase());
-        }
-      });
-  }
-
-  private static void initialize(TypeDataBase db) {
-    Type type = db.lookupType("oopDesc");
-
-    if (VM.getVM().isCompressedKlassPointersEnabled()) {
-      klassField = type.getAddressField("_metadata._compressed_klass");
-    } else {
-      klassField = type.getAddressField("_metadata._klass");
-    }
-  }
-
   public static boolean oopLooksValid(OopHandle oop) {
     if (oop == null) {
       return false;
     }
     if (!VM.getVM().getUniverse().isIn(oop)) {
       return false;
     }
     try {
       // Try to instantiate the Klass
-      if (VM.getVM().isCompressedKlassPointersEnabled()) {
-        Metadata.instantiateWrapperFor(oop.getCompKlassAddressAt(klassField.getOffset()));
-      } else {
-        Metadata.instantiateWrapperFor(klassField.getValue(oop));
-      }
+      Oop.getKlassForOopHandle(oop);
       return true;
     } catch (AddressException | WrongTypeException e) {
       return false;
     }
   }
diff a/test/hotspot/gtest/gc/shared/test_preservedMarks.cpp b/test/hotspot/gtest/gc/shared/test_preservedMarks.cpp
--- a/test/hotspot/gtest/gc/shared/test_preservedMarks.cpp
+++ b/test/hotspot/gtest/gc/shared/test_preservedMarks.cpp
@@ -21,68 +21,60 @@
  * questions.
  */
 
 #include "precompiled.hpp"
 #include "gc/shared/preservedMarks.inline.hpp"
+#include "gc/shared/slidingForwarding.inline.hpp"
 #include "oops/oop.inline.hpp"
 #include "unittest.hpp"
 
-// Class to create a "fake" oop with a mark that will
-// return true for calls to must_be_preserved().
-class FakeOop {
-  oopDesc _oop;
-
-public:
-  FakeOop() : _oop() { _oop.set_mark(originalMark()); }
-
-  oop get_oop() { return &_oop; }
-  markWord mark() { return _oop.mark(); }
-  void set_mark(markWord m) { _oop.set_mark(m); }
-  void forward_to(oop obj) {
-    markWord m = markWord::encode_pointer_as_mark(obj);
-    _oop.set_mark(m);
-  }
-
-  static markWord originalMark() { return markWord(markWord::lock_mask_in_place); }
-  static markWord changedMark()  { return markWord(0x4711); }
-};
+static markWord originalMark() { return markWord(markWord::lock_mask_in_place); }
+static markWord changedMark()  { return markWord(0x4711); }
 
 #define ASSERT_MARK_WORD_EQ(a, b) ASSERT_EQ((a).value(), (b).value())
 
 TEST_VM(PreservedMarks, iterate_and_restore) {
   PreservedMarks pm;
-  FakeOop o1;
-  FakeOop o2;
-  FakeOop o3;
-  FakeOop o4;
+
+  HeapWord fakeheap[32] = { nullptr };
+  HeapWord* heap = align_up(fakeheap, 8 * sizeof(HeapWord));
+  SlidingForwarding::initialize(MemRegion(&heap[0], &heap[16]), 8);
+
+  oop o1 = cast_to_oop(&heap[0]); o1->set_mark(originalMark());
+  oop o2 = cast_to_oop(&heap[2]); o2->set_mark(originalMark());
+  oop o3 = cast_to_oop(&heap[4]); o3->set_mark(originalMark());
+  oop o4 = cast_to_oop(&heap[6]); o4->set_mark(originalMark());
 
   // Make sure initial marks are correct.
-  ASSERT_MARK_WORD_EQ(o1.mark(), FakeOop::originalMark());
-  ASSERT_MARK_WORD_EQ(o2.mark(), FakeOop::originalMark());
-  ASSERT_MARK_WORD_EQ(o3.mark(), FakeOop::originalMark());
-  ASSERT_MARK_WORD_EQ(o4.mark(), FakeOop::originalMark());
+  ASSERT_MARK_WORD_EQ(o1->mark(), originalMark());
+  ASSERT_MARK_WORD_EQ(o2->mark(), originalMark());
+  ASSERT_MARK_WORD_EQ(o3->mark(), originalMark());
+  ASSERT_MARK_WORD_EQ(o4->mark(), originalMark());
 
   // Change the marks and verify change.
-  o1.set_mark(FakeOop::changedMark());
-  o2.set_mark(FakeOop::changedMark());
-  ASSERT_MARK_WORD_EQ(o1.mark(), FakeOop::changedMark());
-  ASSERT_MARK_WORD_EQ(o2.mark(), FakeOop::changedMark());
+  o1->set_mark(changedMark());
+  o2->set_mark(changedMark());
+  ASSERT_MARK_WORD_EQ(o1->mark(), changedMark());
+  ASSERT_MARK_WORD_EQ(o2->mark(), changedMark());
+
+  SlidingForwarding::begin();
 
   // Push o1 and o2 to have their marks preserved.
-  pm.push_if_necessary(o1.get_oop(), o1.mark());
-  pm.push_if_necessary(o2.get_oop(), o2.mark());
+  pm.push_if_necessary(o1, o1->mark());
+  pm.push_if_necessary(o2, o2->mark());
 
   // Fake a move from o1->o3 and o2->o4.
-  o1.forward_to(o3.get_oop());
-  o2.forward_to(o4.get_oop());
-  ASSERT_EQ(o1.get_oop()->forwardee(), o3.get_oop());
-  ASSERT_EQ(o2.get_oop()->forwardee(), o4.get_oop());
+  SlidingForwarding::forward_to(o1, o3);
+  SlidingForwarding::forward_to(o2, o4);
+  ASSERT_EQ(SlidingForwarding::forwardee(o1), o3);
+  ASSERT_EQ(SlidingForwarding::forwardee(o2), o4);
   // Adjust will update the PreservedMarks stack to
   // make sure the mark is updated at the new location.
   pm.adjust_during_full_gc();
 
   // Restore all preserved and verify that the changed
   // mark is now present at o3 and o4.
   pm.restore();
-  ASSERT_MARK_WORD_EQ(o3.mark(), FakeOop::changedMark());
-  ASSERT_MARK_WORD_EQ(o4.mark(), FakeOop::changedMark());
+  ASSERT_MARK_WORD_EQ(o3->mark(), changedMark());
+  ASSERT_MARK_WORD_EQ(o4->mark(), changedMark());
+  SlidingForwarding::end();
 }
diff a/test/hotspot/gtest/gc/shared/test_slidingForwarding.cpp b/test/hotspot/gtest/gc/shared/test_slidingForwarding.cpp
--- /dev/null
+++ b/test/hotspot/gtest/gc/shared/test_slidingForwarding.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright Amazon.com Inc. 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "precompiled.hpp"
+#include "gc/shared/slidingForwarding.inline.hpp"
+#include "oops/markWord.hpp"
+#include "oops/oop.inline.hpp"
+#include "utilities/align.hpp"
+#include "unittest.hpp"
+
+#ifdef _LP64
+#ifndef PRODUCT
+
+static uintptr_t make_mark(uintptr_t target_region, uintptr_t offset) {
+  return (target_region) << 3 | (offset << 4) | 3 /* forwarded */;
+}
+
+static uintptr_t make_fallback() {
+  return ((uintptr_t(1) << 2) /* fallback */ | 3 /* forwarded */);
+}
+
+// Test simple forwarding within the same region.
+TEST_VM(SlidingForwarding, simple) {
+  HeapWord fakeheap[32] = { nullptr };
+  HeapWord* heap = align_up(fakeheap, 8 * sizeof(HeapWord));
+  oop obj1 = cast_to_oop(&heap[2]);
+  oop obj2 = cast_to_oop(&heap[0]);
+  SlidingForwarding::initialize(MemRegion(&heap[0], &heap[16]), 8);
+  obj1->set_mark(markWord::prototype());
+  SlidingForwarding::begin();
+
+  SlidingForwarding::forward_to(obj1, obj2);
+  ASSERT_EQ(obj1->mark().value(), make_mark(0 /* target_region */, 0 /* offset */));
+  ASSERT_EQ(SlidingForwarding::forwardee(obj1), obj2);
+
+  SlidingForwarding::end();
+}
+
+// Test forwardings crossing 2 regions.
+TEST_VM(SlidingForwarding, tworegions) {
+  HeapWord fakeheap[32] = { nullptr };
+  HeapWord* heap = align_up(fakeheap, 8 * sizeof(HeapWord));
+  oop obj1 = cast_to_oop(&heap[14]);
+  oop obj2 = cast_to_oop(&heap[2]);
+  oop obj3 = cast_to_oop(&heap[10]);
+  SlidingForwarding::initialize(MemRegion(&heap[0], &heap[16]), 8);
+  obj1->set_mark(markWord::prototype());
+  SlidingForwarding::begin();
+
+  SlidingForwarding::forward_to(obj1, obj2);
+  ASSERT_EQ(obj1->mark().value(), make_mark(0 /* target_region */, 2 /* offset */));
+  ASSERT_EQ(SlidingForwarding::forwardee(obj1), obj2);
+
+  SlidingForwarding::forward_to(obj1, obj3);
+  ASSERT_EQ(obj1->mark().value(), make_mark(1 /* target_region */, 2 /* offset */));
+  ASSERT_EQ(SlidingForwarding::forwardee(obj1), obj3);
+
+  SlidingForwarding::end();
+}
+
+// Test fallback forwardings crossing 4 regions.
+TEST_VM(SlidingForwarding, fallback) {
+  HeapWord fakeheap[32] = { nullptr };
+  HeapWord* heap = align_up(fakeheap, 8 * sizeof(HeapWord));
+  oop s_obj1 = cast_to_oop(&heap[12]);
+  oop s_obj2 = cast_to_oop(&heap[13]);
+  oop s_obj3 = cast_to_oop(&heap[14]);
+  oop s_obj4 = cast_to_oop(&heap[15]);
+  oop t_obj1 = cast_to_oop(&heap[2]);
+  oop t_obj2 = cast_to_oop(&heap[4]);
+  oop t_obj3 = cast_to_oop(&heap[10]);
+  oop t_obj4 = cast_to_oop(&heap[12]);
+  SlidingForwarding::initialize(MemRegion(&heap[0], &heap[16]), 4);
+  s_obj1->set_mark(markWord::prototype());
+  s_obj2->set_mark(markWord::prototype());
+  s_obj3->set_mark(markWord::prototype());
+  s_obj4->set_mark(markWord::prototype());
+  SlidingForwarding::begin();
+
+  SlidingForwarding::forward_to(s_obj1, t_obj1);
+  ASSERT_EQ(s_obj1->mark().value(), make_mark(0 /* target_region */, 2 /* offset */));
+  ASSERT_EQ(SlidingForwarding::forwardee(s_obj1), t_obj1);
+
+  SlidingForwarding::forward_to(s_obj2, t_obj2);
+  ASSERT_EQ(s_obj2->mark().value(), make_mark(1 /* target_region */, 0 /* offset */));
+  ASSERT_EQ(SlidingForwarding::forwardee(s_obj2), t_obj2);
+
+  SlidingForwarding::forward_to(s_obj3, t_obj3);
+  ASSERT_EQ(s_obj3->mark().value(), make_fallback());
+  ASSERT_EQ(SlidingForwarding::forwardee(s_obj3), t_obj3);
+
+  SlidingForwarding::forward_to(s_obj4, t_obj4);
+  ASSERT_EQ(s_obj4->mark().value(), make_fallback());
+  ASSERT_EQ(SlidingForwarding::forwardee(s_obj4), t_obj4);
+
+  SlidingForwarding::end();
+}
+
+#endif // PRODUCT
+#endif // _LP64
diff a/test/hotspot/gtest/metaspace/metaspaceGtestContexts.hpp b/test/hotspot/gtest/metaspace/metaspaceGtestContexts.hpp
--- a/test/hotspot/gtest/metaspace/metaspaceGtestContexts.hpp
+++ b/test/hotspot/gtest/metaspace/metaspaceGtestContexts.hpp
@@ -35,14 +35,18 @@
 using metaspace::Metachunk;
 using metaspace::chunklevel_t;
 using namespace metaspace::chunklevel;
 
 class MetaspaceGtestContext : public metaspace::MetaspaceTestContext {
+  int _num_arenas_created;
 public:
   MetaspaceGtestContext(size_t commit_limit = 0, size_t reserve_limit = 0) :
-    metaspace::MetaspaceTestContext("gtest-metaspace-context", commit_limit, reserve_limit)
-  {}
+    metaspace::MetaspaceTestContext("gtest-metaspace-context", commit_limit, reserve_limit),
+    _num_arenas_created(0) {}
+
+  int num_arenas_created() const { return _num_arenas_created; }
+  void inc_num_arenas_created() { _num_arenas_created ++; }
 };
 
 class ChunkGtestContext : public MetaspaceGtestContext {
 
   int _num_chunks_allocated;
diff a/test/hotspot/gtest/metaspace/test_binlist.cpp b/test/hotspot/gtest/metaspace/test_binlist.cpp
--- a/test/hotspot/gtest/metaspace/test_binlist.cpp
+++ b/test/hotspot/gtest/metaspace/test_binlist.cpp
@@ -24,16 +24,18 @@
  */
 
 #include "precompiled.hpp"
 #include "memory/metaspace/binList.hpp"
 #include "memory/metaspace/counters.hpp"
+#include "memory/metaspace/metablock.hpp"
 //#define LOG_PLEASE
 #include "metaspaceGtestCommon.hpp"
 
 using metaspace::BinList32;
 using metaspace::BinListImpl;
 using metaspace::MemRangeCounter;
+using metaspace::MetaBlock;
 
 #define CHECK_BL_CONTENT(bl, expected_num, expected_size) { \
   EXPECT_EQ(bl.count(), (unsigned)expected_num); \
   EXPECT_EQ(bl.total_size(), (size_t)expected_size); \
   if (expected_num == 0) { \
@@ -41,10 +43,23 @@
   } else { \
     EXPECT_FALSE(bl.is_empty()); \
   } \
 }
 
+template <int num_lists>
+struct TestedBinList : public BinListImpl<num_lists> {
+  typedef BinListImpl<num_lists> ListType;
+  void add_block(MetaWord* p, size_t word_size) {
+    ListType::add_block(MetaBlock(p, word_size));
+  }
+  MetaWord* remove_block(size_t requested_size, size_t* real_size) {
+    MetaBlock result = ListType::remove_block(requested_size);
+    (*real_size) = result.word_size();
+    return result.base();
+  }
+};
+
 template <class BINLISTTYPE>
 struct BinListBasicTest {
 
   static const size_t maxws;
 
@@ -204,16 +219,16 @@
   }
 };
 
 template <typename BINLISTTYPE> const size_t BinListBasicTest<BINLISTTYPE>::maxws = BINLISTTYPE::MaxWordSize;
 
-TEST_VM(metaspace, BinList_basic_1)     { BinListBasicTest< BinListImpl<1> >::basic_test(); }
-TEST_VM(metaspace, BinList_basic_8)     { BinListBasicTest< BinListImpl<8> >::basic_test(); }
-TEST_VM(metaspace, BinList_basic_32)    { BinListBasicTest<BinList32>::basic_test(); }
+TEST_VM(metaspace, BinList_basic_1)     { BinListBasicTest< TestedBinList<1> >::basic_test(); }
+TEST_VM(metaspace, BinList_basic_8)     { BinListBasicTest< TestedBinList<8> >::basic_test(); }
+TEST_VM(metaspace, BinList_basic_32)    { BinListBasicTest< TestedBinList<32> >::basic_test(); }
 
-TEST_VM(metaspace, BinList_basic_2_1)     { BinListBasicTest< BinListImpl<1> >::basic_test_2(); }
-TEST_VM(metaspace, BinList_basic_2_8)     { BinListBasicTest< BinListImpl<8> >::basic_test_2(); }
-TEST_VM(metaspace, BinList_basic_2_32)    { BinListBasicTest<BinList32>::basic_test_2(); }
+TEST_VM(metaspace, BinList_basic_2_1)     { BinListBasicTest< TestedBinList<1> >::basic_test_2(); }
+TEST_VM(metaspace, BinList_basic_2_8)     { BinListBasicTest< TestedBinList<8> >::basic_test_2(); }
+TEST_VM(metaspace, BinList_basic_2_32)    { BinListBasicTest< TestedBinList<32> >::basic_test_2(); }
 
-TEST_VM(metaspace, BinList_basic_rand_1)     { BinListBasicTest< BinListImpl<1> >::random_test(); }
-TEST_VM(metaspace, BinList_basic_rand_8)     { BinListBasicTest< BinListImpl<8> >::random_test(); }
-TEST_VM(metaspace, BinList_basic_rand_32)    { BinListBasicTest<BinList32>::random_test(); }
+TEST_VM(metaspace, BinList_basic_rand_1)     { BinListBasicTest< TestedBinList<1> >::random_test(); }
+TEST_VM(metaspace, BinList_basic_rand_8)     { BinListBasicTest< TestedBinList<8> >::random_test(); }
+TEST_VM(metaspace, BinList_basic_rand_32)    { BinListBasicTest< TestedBinList<32> >::random_test(); }
diff a/test/hotspot/gtest/metaspace/test_blocktree.cpp b/test/hotspot/gtest/metaspace/test_blocktree.cpp
--- a/test/hotspot/gtest/metaspace/test_blocktree.cpp
+++ b/test/hotspot/gtest/metaspace/test_blocktree.cpp
@@ -24,20 +24,33 @@
  */
 
 #include "precompiled.hpp"
 #include "memory/metaspace/blockTree.hpp"
 #include "memory/metaspace/counters.hpp"
+#include "memory/metaspace/metablock.hpp"
 #include "memory/resourceArea.hpp"
 // #define LOG_PLEASE
 #include "metaspaceGtestCommon.hpp"
 
 using metaspace::BlockTree;
 using metaspace::MemRangeCounter;
+using metaspace::MetaBlock;
+
+struct TestedBlockTree : public BlockTree {
+  void add_block(MetaWord* p, size_t word_size) {
+    BlockTree::add_block(MetaBlock(p, word_size));
+  }
+  MetaWord* remove_block(size_t requested_size, size_t* real_size) {
+    MetaBlock result = BlockTree::remove_block(requested_size);
+    (*real_size) = result.word_size();
+    return result.base();
+  }
+};
 
 // Small helper. Given a 0-terminated array of sizes, a feeder buffer and a tree,
 //  add blocks of these sizes to the tree in the order they appear in the array.
-static void create_nodes(const size_t sizes[], FeederBuffer& fb, BlockTree& bt) {
+static void create_nodes(const size_t sizes[], FeederBuffer& fb, TestedBlockTree& bt) {
   for (int i = 0; sizes[i] > 0; i ++) {
     size_t s = sizes[i];
     MetaWord* p = fb.get(s);
     bt.add_block(p, s);
   }
@@ -53,11 +66,11 @@
   } \
 }
 
 TEST_VM(metaspace, BlockTree_basic) {
 
-  BlockTree bt;
+  TestedBlockTree bt;
   CHECK_BT_CONTENT(bt, 0, 0);
 
   size_t real_size = 0;
   MetaWord* p = nullptr;
   MetaWord arr[10000];
@@ -110,11 +123,11 @@
 
 // Given a sequence of (0-terminated) sizes, add blocks of those sizes to the tree in the order given. Then, ask
 // for a request size and check that it is the expected result.
 static void test_find_nearest_fit_with_tree(const size_t sizes[], size_t request_size) {
 
-  BlockTree bt;
+  TestedBlockTree bt;
   FeederBuffer fb(4 * K);
 
   create_nodes(sizes, fb, bt);
 
   DEBUG_ONLY(bt.verify();)
@@ -153,11 +166,11 @@
     30, 17, 10, 28,
     50, 32, 51, 35,
     0 // stop
   };
 
-  BlockTree bt;
+  TestedBlockTree bt;
   FeederBuffer fb(4 * K);
 
   create_nodes(sizes, fb, bt);
 
   for (int i = BlockTree::MinWordSize; i <= 60; i ++) {
@@ -168,11 +181,11 @@
 
 // Test repeated adding and removing of blocks of the same size, which
 // should exercise the list-part of the tree.
 TEST_VM(metaspace, BlockTree_basic_siblings)
 {
-  BlockTree bt;
+  TestedBlockTree bt;
   FeederBuffer fb(4 * K);
 
   CHECK_BT_CONTENT(bt, 0, 0);
 
   const size_t test_size = BlockTree::MinWordSize;
@@ -202,11 +215,11 @@
     30, 17, 10, 28,
     50, 32, 51, 35,
     0 // stop
   };
 
-  BlockTree bt;
+  TestedBlockTree bt;
   FeederBuffer fb(4 * K);
 
   create_nodes(sizes, fb, bt);
 
   ResourceMark rm;
@@ -220,11 +233,11 @@
 // Test that an overwritten node would result in an assert and a printed tree
 TEST_VM_ASSERT_MSG(metaspace, BlockTree_overwriter_test, ".*failed: Invalid node") {
   static const size_t sizes1[] = { 30, 17, 0 };
   static const size_t sizes2[] = { 12, 12, 0 };
 
-  BlockTree bt;
+  TestedBlockTree bt;
   FeederBuffer fb(4 * K);
 
   // some nodes...
   create_nodes(sizes1, fb, bt);
 
@@ -247,11 +260,11 @@
 
 class BlockTreeTest {
 
   FeederBuffer _fb;
 
-  BlockTree _bt[2];
+  TestedBlockTree _bt[2];
   MemRangeCounter _cnt[2];
 
   RandSizeGenerator _rgen;
 
 #define CHECK_COUNTERS \
@@ -354,11 +367,11 @@
 
   // Drain the trees. While draining, observe the order of the drained items.
   void drain_all() {
 
     for (int which = 0; which < 2; which++) {
-      BlockTree* bt = _bt + which;
+      TestedBlockTree* bt = _bt + which;
       size_t last_size = 0;
       while (!bt->is_empty()) {
 
         // We only query for the minimal size. Actually returned size should be
         // monotonously growing since remove_block should always return the closest fit.
diff a/test/hotspot/gtest/metaspace/test_clms.cpp b/test/hotspot/gtest/metaspace/test_clms.cpp
--- /dev/null
+++ b/test/hotspot/gtest/metaspace/test_clms.cpp
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023 Red Hat, Inc. 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+#include "memory/classLoaderMetaspace.hpp"
+#include "memory/metaspace/freeBlocks.hpp"
+#include "memory/metaspace/metablock.inline.hpp"
+#include "memory/metaspace/metaspaceArena.hpp"
+#include "memory/metaspace/metaspaceSettings.hpp"
+#include "memory/metaspace/metaspaceStatistics.hpp"
+#include "memory/metaspace.hpp"
+#include "oops/klass.hpp"
+#include "runtime/mutex.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/align.hpp"
+#include "utilities/globalDefinitions.hpp"
+
+#ifdef _LP64
+
+#define LOG_PLEASE
+#include "metaspaceGtestCommon.hpp"
+#include "metaspaceGtestContexts.hpp"
+#include "metaspaceGtestRangeHelpers.hpp"
+#include "metaspaceGtestSparseArray.hpp"
+
+#define HANDLE_FAILURE \
+  if (testing::Test::HasFailure()) { \
+    return; \
+  }
+
+namespace metaspace {
+
+class ClmsTester {
+
+  Mutex _lock;
+  MetaspaceContext* _class_context;
+  MetaspaceContext* _nonclass_context;
+  ClassLoaderMetaspace* _clms;
+  const size_t _klass_arena_alignment_words;
+  unsigned _num_allocations;
+
+  struct Deltas {
+    int num_chunks_delta;
+    ssize_t used_words_delta;
+    int num_freeblocks_delta;
+    ssize_t freeblocks_words_delta;
+  };
+
+  Deltas calc_deltas(const ArenaStats& before, const ArenaStats& after) {
+    Deltas d;
+    d.num_chunks_delta = after.totals()._num - before.totals()._num;
+    d.used_words_delta = after.totals()._used_words - before.totals()._used_words;
+    d.num_freeblocks_delta = (int)after._free_blocks_num - (int)before._free_blocks_num;
+    d.freeblocks_words_delta = after._free_blocks_word_size - before._free_blocks_word_size;
+    return d;
+  }
+
+public:
+
+  ClmsTester(size_t klass_alignment_words, Metaspace::MetaspaceType space_type,
+             MetaspaceContext* class_context, MetaspaceContext* nonclass_context)
+  : _lock(Monitor::nosafepoint, "CLMSTest_lock"),
+    _class_context(class_context), _nonclass_context(nonclass_context),
+    _clms(nullptr), _klass_arena_alignment_words(klass_alignment_words), _num_allocations(0) {
+    _clms = new ClassLoaderMetaspace(&_lock, space_type, nonclass_context, class_context, klass_alignment_words);
+  }
+
+  ~ClmsTester() {
+    delete _clms;
+    EXPECT_EQ(_class_context->used_words(), (size_t)0);
+    EXPECT_EQ(_nonclass_context->used_words(), (size_t)0);
+  }
+
+  MetaBlock allocate_and_check(size_t word_size, bool is_class) {
+
+    // take stats before allocation
+    ClmsStats stats_before;
+    _clms->add_to_statistics(&stats_before);
+
+    // allocate
+    MetaWord* p = _clms->allocate(word_size, is_class ? Metaspace::ClassType : Metaspace::NonClassType);
+    _num_allocations ++;
+
+    // take stats after allocation
+    ClmsStats stats_after;
+    _clms->add_to_statistics(&stats_after);
+
+    // for less verbose testing:
+    const ArenaStats& ca_before = stats_before._arena_stats_class;
+    const ArenaStats& ca_after = stats_after._arena_stats_class;
+    const ArenaStats& nca_before = stats_before._arena_stats_nonclass;
+    const ArenaStats& nca_after = stats_after._arena_stats_nonclass;
+
+    // deltas
+    const Deltas d_ca = calc_deltas(ca_before, ca_after);
+    const Deltas d_nca = calc_deltas(nca_before, nca_after);
+
+#define EXPECT_FREEBLOCKS_UNCHANGED(arena_prefix) \
+    EXPECT_EQ(d_##arena_prefix.num_freeblocks_delta, 0);  \
+    EXPECT_EQ(d_##arena_prefix.freeblocks_words_delta, (ssize_t)0);
+
+#define EXPECT_ARENA_UNCHANGED(arena_prefix) \
+    EXPECT_EQ(d_##arena_prefix.num_chunks_delta, 0);  \
+    EXPECT_EQ(d_##arena_prefix.used_words_delta, (ssize_t)0);
+
+    if (p != nullptr) {
+
+      MetaBlock bl(p, word_size);
+
+      if (is_class) {
+
+        EXPECT_TRUE(bl.is_aligned_base(_klass_arena_alignment_words));
+
+        if (_num_allocations == 1) {
+          // first allocation: nonclass arena unchanged, class arena grows by 1 chunk and wordsize,
+          // class arena freeblocks unchanged
+          EXPECT_ARENA_UNCHANGED(nca);
+          EXPECT_FREEBLOCKS_UNCHANGED(nca);
+          EXPECT_EQ(d_ca.num_chunks_delta, 1);
+          EXPECT_EQ((size_t)d_ca.used_words_delta, word_size);
+          EXPECT_FREEBLOCKS_UNCHANGED(ca);
+          return bl;
+        }
+
+        // Had this been taken from class arena freeblocks?
+        if (d_ca.num_freeblocks_delta == -1) {
+          // the class arena freeblocks should have gone down, and the non-class arena freeblocks may have gone
+          // up in case the block was larger than required
+          const size_t wordsize_block_taken = (size_t)(-d_ca.freeblocks_words_delta);
+          EXPECT_GE(wordsize_block_taken, word_size); // the block we took must be at least allocation size
+          const size_t expected_freeblock_remainder = wordsize_block_taken - word_size;
+          if (expected_freeblock_remainder > 0) {
+            // the remainder, if it existed, should have been added to nonclass freeblocks
+            EXPECT_EQ(d_nca.num_freeblocks_delta, 1);
+            EXPECT_EQ((size_t)d_nca.freeblocks_words_delta, expected_freeblock_remainder);
+          }
+          // finally, nothing should have happened in the arenas proper.
+          EXPECT_ARENA_UNCHANGED(ca);
+          EXPECT_ARENA_UNCHANGED(nca);
+          return bl;
+        }
+
+        // block was taken from class arena proper
+
+        // We expect allocation waste due to alignment, should have been added to the freeblocks
+        // of nonclass arena. Allocation waste can be 0. If no chunk turnover happened, it must be
+        // smaller than klass alignment, otherwise it can get as large as a commit granule.
+        const size_t max_expected_allocation_waste =
+            d_ca.num_chunks_delta == 0 ? (_klass_arena_alignment_words - 1) : Settings::commit_granule_words();
+        EXPECT_GE(d_ca.num_chunks_delta, 0);
+        EXPECT_LE(d_ca.num_chunks_delta, 1);
+        EXPECT_GE((size_t)d_ca.used_words_delta, word_size);
+        EXPECT_LE((size_t)d_ca.used_words_delta, word_size + max_expected_allocation_waste);
+        EXPECT_FREEBLOCKS_UNCHANGED(ca);
+        EXPECT_ARENA_UNCHANGED(nca);
+        if (max_expected_allocation_waste > 0) {
+          EXPECT_GE(d_nca.num_freeblocks_delta, 0);
+          EXPECT_LE(d_nca.num_freeblocks_delta, 1);
+          EXPECT_GE(d_nca.freeblocks_words_delta, 0);
+          EXPECT_LE((size_t)d_nca.freeblocks_words_delta, max_expected_allocation_waste);
+        } else {
+          EXPECT_FREEBLOCKS_UNCHANGED(nca);
+        }
+        return bl;
+
+      } // end: is_class
+
+      else
+
+      {
+        // Nonclass arena allocation.
+        // Allocation waste can happen:
+        // - if we allocate from nonclass freeblocks, the block remainder
+        // - if we allocate from arena proper, by chunk turnover
+
+        if (d_nca.freeblocks_words_delta < 0) {
+          // We allocated a block from the nonclass arena freeblocks.
+          const size_t wordsize_block_taken = (size_t)(-d_nca.freeblocks_words_delta);
+          EXPECT_EQ(wordsize_block_taken, word_size);
+          // The number of blocks may or may not have decreased (depending on whether there
+          // was a wastage block)
+          EXPECT_GE(d_nca.num_chunks_delta, -1);
+          EXPECT_LE(d_nca.num_chunks_delta, 0);
+          EXPECT_ARENA_UNCHANGED(nca);
+          EXPECT_ARENA_UNCHANGED(ca);
+          EXPECT_FREEBLOCKS_UNCHANGED(ca);
+          return bl;
+        }
+
+        // We don't expect alignment waste. Only wastage happens at chunk turnover.
+        const size_t max_expected_allocation_waste =
+            d_nca.num_chunks_delta == 0 ? 0 : Settings::commit_granule_words();
+        EXPECT_ARENA_UNCHANGED(ca);
+        EXPECT_FREEBLOCKS_UNCHANGED(ca);
+        EXPECT_GE(d_nca.num_chunks_delta, 0);
+        EXPECT_LE(d_nca.num_chunks_delta, 1);
+        EXPECT_GE((size_t)d_nca.used_words_delta, word_size);
+        EXPECT_LE((size_t)d_nca.used_words_delta, word_size + max_expected_allocation_waste);
+        if (max_expected_allocation_waste == 0) {
+          EXPECT_FREEBLOCKS_UNCHANGED(nca);
+        }
+      }
+      return bl;
+
+    } // end: allocation successful
+
+    // allocation failed.
+    EXPECT_ARENA_UNCHANGED(ca);
+    EXPECT_FREEBLOCKS_UNCHANGED(ca);
+    EXPECT_ARENA_UNCHANGED(nca);
+    EXPECT_FREEBLOCKS_UNCHANGED(nca);
+
+    return MetaBlock();
+  }
+
+  MetaBlock allocate_expect_success(size_t word_size, bool is_class) {
+    MetaBlock bl = allocate_and_check(word_size, is_class);
+    EXPECT_TRUE(bl.is_nonempty());
+    return bl;
+  }
+
+  MetaBlock allocate_expect_failure(size_t word_size, bool is_class) {
+    MetaBlock bl = allocate_and_check(word_size, is_class);
+    EXPECT_TRUE(bl.is_empty());
+    return bl;
+  }
+
+  void deallocate_and_check(MetaBlock bl, bool is_class) {
+
+    // take stats before deallocation
+    ClmsStats stats_before;
+    _clms->add_to_statistics(&stats_before);
+
+    // allocate
+    _clms->deallocate(bl.base(), bl.word_size(), is_class);
+
+    // take stats after deallocation
+    ClmsStats stats_after;
+    _clms->add_to_statistics(&stats_after);
+
+    // for less verbose testing:
+    const ArenaStats& ca_before = stats_before._arena_stats_class;
+    const ArenaStats& ca_after = stats_after._arena_stats_class;
+    const ArenaStats& nca_before = stats_before._arena_stats_nonclass;
+    const ArenaStats& nca_after = stats_after._arena_stats_nonclass;
+
+    // deltas
+    // deltas
+    const Deltas d_ca = calc_deltas(ca_before, ca_after);
+    const Deltas d_nca = calc_deltas(nca_before, nca_after);
+
+    EXPECT_ARENA_UNCHANGED(ca);
+    EXPECT_ARENA_UNCHANGED(nca);
+    if (is_class) {
+      EXPECT_EQ(d_ca.num_freeblocks_delta, 1);
+      EXPECT_EQ((size_t)d_ca.freeblocks_words_delta, bl.word_size());
+      EXPECT_FREEBLOCKS_UNCHANGED(nca);
+    } else {
+      EXPECT_EQ(d_nca.num_freeblocks_delta, 1);
+      EXPECT_EQ((size_t)d_nca.freeblocks_words_delta, bl.word_size());
+      EXPECT_FREEBLOCKS_UNCHANGED(ca);
+    }
+  }
+};
+
+static constexpr size_t klass_size = sizeof(Klass) / BytesPerWord;
+
+static void basic_test(size_t klass_arena_alignment) {
+  if (Settings::use_allocation_guard()) {
+    return;
+  }
+  MetaspaceGtestContext class_context, nonclass_context;
+  {
+    ClmsTester tester(klass_arena_alignment, Metaspace::StandardMetaspaceType, class_context.context(), nonclass_context.context());
+
+    MetaBlock bl1 = tester.allocate_expect_success(klass_size, true);
+    HANDLE_FAILURE;
+
+    MetaBlock bl2 = tester.allocate_expect_success(klass_size, true);
+    HANDLE_FAILURE;
+
+    tester.deallocate_and_check(bl1, true);
+    HANDLE_FAILURE;
+
+    MetaBlock bl3 = tester.allocate_expect_success(klass_size, true);
+    HANDLE_FAILURE;
+    EXPECT_EQ(bl3, bl1); // should have gotten the same block back from freelist
+
+    MetaBlock bl4 = tester.allocate_expect_success(Metaspace::min_allocation_word_size, false);
+    HANDLE_FAILURE;
+
+    MetaBlock bl5 = tester.allocate_expect_success(K, false);
+    HANDLE_FAILURE;
+
+    tester.deallocate_and_check(bl5, false);
+    HANDLE_FAILURE;
+
+    MetaBlock bl6 = tester.allocate_expect_success(K, false);
+    HANDLE_FAILURE;
+
+    EXPECT_EQ(bl5, bl6); // should have gotten the same block back from freelist
+  }
+  EXPECT_EQ(class_context.used_words(), (size_t)0);
+  EXPECT_EQ(nonclass_context.used_words(), (size_t)0);
+  // we should have used exactly one commit granule (64K), not more, for each context
+  EXPECT_EQ(class_context.committed_words(), Settings::commit_granule_words());
+  EXPECT_EQ(nonclass_context.committed_words(), Settings::commit_granule_words());
+}
+
+#define TEST_BASIC_N(n)               \
+TEST_VM(metaspace, CLMS_basics_##n) { \
+  basic_test(n);            \
+}
+
+TEST_BASIC_N(1)
+TEST_BASIC_N(4)
+TEST_BASIC_N(16)
+TEST_BASIC_N(32)
+TEST_BASIC_N(128)
+
+static void test_random(size_t klass_arena_alignment) {
+  if (Settings::use_allocation_guard()) {
+    return;
+  }
+
+  MetaspaceGtestContext class_context, nonclass_context;
+  constexpr int max_allocations = 1024;
+  const SizeRange nonclass_alloc_range(Metaspace::min_allocation_alignment_words, 1024);
+  const SizeRange class_alloc_range(klass_size, 1024);
+  const IntRange one_out_of_ten(0, 10);
+  for (int runs = 9; runs >= 0; runs--) {
+    {
+      ClmsTester tester(64, Metaspace::StandardMetaspaceType, class_context.context(), nonclass_context.context());
+      struct LifeBlock {
+        MetaBlock bl;
+        bool is_class;
+      };
+      LifeBlock life_allocations[max_allocations];
+      for (int i = 0; i < max_allocations; i++) {
+        life_allocations[i].bl.reset();
+      }
+
+      unsigned num_class_allocs = 0, num_nonclass_allocs = 0, num_class_deallocs = 0, num_nonclass_deallocs = 0;
+      for (int i = 0; i < 5000; i ++) {
+        const int slot = IntRange(0, max_allocations).random_value();
+        if (life_allocations[slot].bl.is_empty()) {
+          const bool is_class = one_out_of_ten.random_value() == 0;
+          const size_t word_size =
+              is_class ? class_alloc_range.random_value() : nonclass_alloc_range.random_value();
+          MetaBlock bl = tester.allocate_expect_success(word_size, is_class);
+          HANDLE_FAILURE;
+          life_allocations[slot].bl = bl;
+          life_allocations[slot].is_class = is_class;
+          if (is_class) {
+            num_class_allocs ++;
+          } else {
+            num_nonclass_allocs ++;
+          }
+        } else {
+          tester.deallocate_and_check(life_allocations[slot].bl, life_allocations[slot].is_class);
+          HANDLE_FAILURE;
+          life_allocations[slot].bl.reset();
+          if (life_allocations[slot].is_class) {
+            num_class_deallocs ++;
+          } else {
+            num_nonclass_deallocs ++;
+          }
+        }
+      }
+      LOG("num class allocs: %u, num nonclass allocs: %u, num class deallocs: %u, num nonclass deallocs: %u",
+          num_class_allocs, num_nonclass_allocs, num_class_deallocs, num_nonclass_deallocs);
+    }
+    EXPECT_EQ(class_context.used_words(), (size_t)0);
+    EXPECT_EQ(nonclass_context.used_words(), (size_t)0);
+    constexpr float fragmentation_factor = 3.0f;
+    const size_t max_expected_nonclass_committed = max_allocations * nonclass_alloc_range.highest() * fragmentation_factor;
+    const size_t max_expected_class_committed = max_allocations * class_alloc_range.highest() * fragmentation_factor;
+    // we should have used exactly one commit granule (64K), not more, for each context
+    EXPECT_LT(class_context.committed_words(), max_expected_class_committed);
+    EXPECT_LT(nonclass_context.committed_words(), max_expected_nonclass_committed);
+  }
+}
+
+#define TEST_RANDOM_N(n)               \
+TEST_VM(metaspace, CLMS_random_##n) {  \
+  test_random(n);                      \
+}
+
+TEST_RANDOM_N(1)
+TEST_RANDOM_N(4)
+TEST_RANDOM_N(16)
+TEST_RANDOM_N(32)
+TEST_RANDOM_N(128)
+
+} // namespace metaspace
+
+#endif // _LP64
diff a/test/hotspot/gtest/metaspace/test_freeblocks.cpp b/test/hotspot/gtest/metaspace/test_freeblocks.cpp
--- a/test/hotspot/gtest/metaspace/test_freeblocks.cpp
+++ b/test/hotspot/gtest/metaspace/test_freeblocks.cpp
@@ -24,14 +24,17 @@
  */
 
 #include "precompiled.hpp"
 #include "memory/metaspace/counters.hpp"
 #include "memory/metaspace/freeBlocks.hpp"
+#include "memory/metaspace/metablock.hpp"
+
 //#define LOG_PLEASE
 #include "metaspaceGtestCommon.hpp"
 
 using metaspace::FreeBlocks;
+using metaspace::MetaBlock;
 using metaspace::SizeCounter;
 
 #define CHECK_CONTENT(fb, num_blocks_expected, word_size_expected) \
 { \
   if (word_size_expected > 0) { \
@@ -41,201 +44,23 @@
   } \
   EXPECT_EQ(fb.total_size(), (size_t)word_size_expected); \
   EXPECT_EQ(fb.count(), (int)num_blocks_expected); \
 }
 
-class FreeBlocksTest {
-
-  FeederBuffer _fb;
-  FreeBlocks _freeblocks;
-
-  // random generator for block feeding
-  RandSizeGenerator _rgen_feeding;
-
-  // random generator for allocations (and, hence, deallocations)
-  RandSizeGenerator _rgen_allocations;
-
-  SizeCounter _allocated_words;
-
-  struct allocation_t {
-    allocation_t* next;
-    size_t word_size;
-    MetaWord* p;
-  };
-
-  // Array of the same size as the pool max capacity; holds the allocated elements.
-  allocation_t* _allocations;
-
-  int _num_allocs;
-  int _num_deallocs;
-  int _num_feeds;
-
-  bool feed_some() {
-    size_t word_size = _rgen_feeding.get();
-    MetaWord* p = _fb.get(word_size);
-    if (p != nullptr) {
-      _freeblocks.add_block(p, word_size);
-      return true;
-    }
-    return false;
-  }
-
-  bool deallocate_top() {
-
-    allocation_t* a = _allocations;
-    if (a != nullptr) {
-      _allocations = a->next;
-      check_marked_range(a->p, a->word_size);
-      _freeblocks.add_block(a->p, a->word_size);
-      delete a;
-      DEBUG_ONLY(_freeblocks.verify();)
-      return true;
-    }
-    return false;
-  }
-
-  void deallocate_all() {
-    while (deallocate_top());
-  }
-
-  bool allocate() {
-
-    size_t word_size = MAX2(_rgen_allocations.get(), _freeblocks.MinWordSize);
-    MetaWord* p = _freeblocks.remove_block(word_size);
-    if (p != nullptr) {
-      _allocated_words.increment_by(word_size);
-      allocation_t* a = new allocation_t;
-      a->p = p; a->word_size = word_size;
-      a->next = _allocations;
-      _allocations = a;
-      DEBUG_ONLY(_freeblocks.verify();)
-      mark_range(p, word_size);
-      return true;
-    }
-    return false;
-  }
-
-  void test_all_marked_ranges() {
-    for (allocation_t* a = _allocations; a != nullptr; a = a->next) {
-      check_marked_range(a->p, a->word_size);
-    }
-  }
-
-  void test_loop() {
-    // We loop and in each iteration execute one of three operations:
-    // - allocation from fbl
-    // - deallocation to fbl of a previously allocated block
-    // - feeding a new larger block into the fbl (mimicks chunk retiring)
-    // When we have fed all large blocks into the fbl (feedbuffer empty), we
-    //  switch to draining the fbl completely (only allocs)
-    bool forcefeed = false;
-    bool draining = false;
-    bool stop = false;
-    int iter = 25000; // safety stop
-    while (!stop && iter > 0) {
-      iter --;
-      int surprise = (int)os::random() % 10;
-      if (!draining && (surprise >= 7 || forcefeed)) {
-        forcefeed = false;
-        if (feed_some()) {
-          _num_feeds++;
-        } else {
-          // We fed all input memory into the fbl. Now lets proceed until the fbl is drained.
-          draining = true;
-        }
-      } else if (!draining && surprise < 1) {
-        deallocate_top();
-        _num_deallocs++;
-      } else {
-        if (allocate()) {
-          _num_allocs++;
-        } else {
-          if (draining) {
-            stop = _freeblocks.total_size() < 512;
-          } else {
-            forcefeed = true;
-          }
-        }
-      }
-      if ((iter % 1000) == 0) {
-        DEBUG_ONLY(_freeblocks.verify();)
-        test_all_marked_ranges();
-        LOG("a %d (" SIZE_FORMAT "), d %d, f %d", _num_allocs, _allocated_words.get(), _num_deallocs, _num_feeds);
-#ifdef LOG_PLEASE
-        _freeblocks.print(tty, true);
-        tty->cr();
-#endif
-      }
-    }
-
-    // Drain
-
-  }
-
-public:
-
-  FreeBlocksTest(size_t avg_alloc_size) :
-    _fb(512 * K), _freeblocks(),
-    _rgen_feeding(128, 4096),
-    _rgen_allocations(avg_alloc_size / 4, avg_alloc_size * 2, 0.01f, avg_alloc_size / 3, avg_alloc_size * 30),
-    _allocations(nullptr),
-    _num_allocs(0),
-    _num_deallocs(0),
-    _num_feeds(0)
-  {
-    CHECK_CONTENT(_freeblocks, 0, 0);
-    // some initial feeding
-    _freeblocks.add_block(_fb.get(1024), 1024);
-    CHECK_CONTENT(_freeblocks, 1, 1024);
-  }
-
-  ~FreeBlocksTest() {
-    deallocate_all();
-  }
-
-  static void test_small_allocations() {
-    FreeBlocksTest test(10);
-    test.test_loop();
-  }
-
-  static void test_medium_allocations() {
-    FreeBlocksTest test(30);
-    test.test_loop();
-  }
-
-  static void test_large_allocations() {
-    FreeBlocksTest test(150);
-    test.test_loop();
-  }
-
-};
-
 TEST_VM(metaspace, freeblocks_basics) {
 
   FreeBlocks fbl;
   MetaWord tmp[1024];
   CHECK_CONTENT(fbl, 0, 0);
 
-  fbl.add_block(tmp, 1024);
+  MetaBlock bl(tmp, 1024);
+  fbl.add_block(bl);
   DEBUG_ONLY(fbl.verify();)
   ASSERT_FALSE(fbl.is_empty());
   CHECK_CONTENT(fbl, 1, 1024);
 
-  MetaWord* p = fbl.remove_block(1024);
-  EXPECT_EQ(p, tmp);
+  MetaBlock bl2 = fbl.remove_block(1024);
+  ASSERT_EQ(bl, bl2);
   DEBUG_ONLY(fbl.verify();)
   CHECK_CONTENT(fbl, 0, 0);
 
 }
-
-TEST_VM(metaspace, freeblocks_small) {
-  FreeBlocksTest::test_small_allocations();
-}
-
-TEST_VM(metaspace, freeblocks_medium) {
-  FreeBlocksTest::test_medium_allocations();
-}
-
-TEST_VM(metaspace, freeblocks_large) {
-  FreeBlocksTest::test_large_allocations();
-}
-
diff a/test/hotspot/gtest/metaspace/test_metablock.cpp b/test/hotspot/gtest/metaspace/test_metablock.cpp
--- /dev/null
+++ b/test/hotspot/gtest/metaspace/test_metablock.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020 SAP SE. 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+#include "memory/metaspace/metablock.inline.hpp"
+//#define LOG_PLEASE
+#include "metaspaceGtestCommon.hpp"
+
+using metaspace::MetaBlock;
+
+
+#define CHECK_BLOCK_EMPTY(block) { \
+  EXPECT_TRUE(block.is_empty()); \
+  DEBUG_ONLY(block.verify()); \
+}
+
+#define CHECK_BLOCK(block, expected_base, expected_size) { \
+    EXPECT_EQ(block.base(), (MetaWord*)expected_base); \
+    EXPECT_EQ((size_t)expected_size, block.word_size()); \
+    EXPECT_EQ(block.end(), expected_base + expected_size); \
+    DEBUG_ONLY(block.verify()); \
+}
+
+static constexpr uintptr_t large_pointer = NOT_LP64(0x99999990) LP64_ONLY(0x9999999999999990ULL);
+
+TEST(metaspace, MetaBlock_1) {
+  MetaBlock bl;
+  CHECK_BLOCK_EMPTY(bl);
+}
+
+TEST(metaspace, MetaBlock_2) {
+  MetaWord* const p = (MetaWord*)large_pointer;
+  constexpr size_t s = G;
+  MetaBlock bl(p, s);
+  CHECK_BLOCK(bl, p, s);
+}
+
+TEST(metaspace, MetaBlock_3) {
+  MetaWord* const p = (MetaWord*)large_pointer;
+  MetaBlock bl(p, 0);
+  CHECK_BLOCK_EMPTY(bl);
+}
+
+TEST_VM(metaspace, MetaBlock_4) {
+  MetaWord* const p = (MetaWord*)large_pointer;
+  MetaBlock bl(p, G);
+  CHECK_BLOCK(bl, p, G);
+
+  MetaBlock bl_copy = bl, bl2;
+
+  bl2 = bl.split_off_tail(M);
+  CHECK_BLOCK(bl, p, G - M);
+  CHECK_BLOCK(bl2, p + G - M, M);
+
+  bl = bl_copy;
+
+bl.print_on(tty);
+bl2.print_on(tty);
+  bl2 = bl.split_off_tail(G);
+  bl.print_on(tty);
+  bl2.print_on(tty);
+
+  ASSERT_EQ(bl2, bl_copy);
+  ASSERT_TRUE(bl.is_empty());
+
+  bl = bl_copy;
+
+  bl2 = bl.split_off_tail(0);
+  ASSERT_EQ(bl, bl_copy);
+  ASSERT_TRUE(bl2.is_empty());
+
+  MetaBlock empty;
+  bl = empty.split_off_tail(0);
+  ASSERT_TRUE(bl.is_empty());
+}
diff a/test/hotspot/gtest/metaspace/test_metaspaceUtils.cpp b/test/hotspot/gtest/metaspace/test_metaspaceUtils.cpp
--- a/test/hotspot/gtest/metaspace/test_metaspaceUtils.cpp
+++ b/test/hotspot/gtest/metaspace/test_metaspaceUtils.cpp
@@ -26,20 +26,20 @@
 #include "precompiled.hpp"
 #include "memory/metaspace.hpp"
 #include "memory/metaspaceUtils.hpp"
 #include "unittest.hpp"
 
-TEST_VM(MetaspaceUtils, reserved) {
+TEST_VM(metaspace, MetaspaceUtils_reserved) {
   size_t reserved = MetaspaceUtils::reserved_bytes();
   EXPECT_GT(reserved, 0UL);
 
   size_t reserved_metadata = MetaspaceUtils::reserved_bytes(Metaspace::NonClassType);
   EXPECT_GT(reserved_metadata, 0UL);
   EXPECT_LE(reserved_metadata, reserved);
 }
 
-TEST_VM(MetaspaceUtils, reserved_compressed_class_pointers) {
+TEST_VM(metaspace, MetaspaceUtils_reserved_compressed_class_pointers) {
   if (!UseCompressedClassPointers) {
     return;
   }
   size_t reserved = MetaspaceUtils::reserved_bytes();
   EXPECT_GT(reserved, 0UL);
@@ -47,11 +47,11 @@
   size_t reserved_class = MetaspaceUtils::reserved_bytes(Metaspace::ClassType);
   EXPECT_GT(reserved_class, 0UL);
   EXPECT_LE(reserved_class, reserved);
 }
 
-TEST_VM(MetaspaceUtils, committed) {
+TEST_VM(metaspace, MetaspaceUtils_committed) {
   size_t committed = MetaspaceUtils::committed_bytes();
   EXPECT_GT(committed, 0UL);
 
   size_t reserved  = MetaspaceUtils::reserved_bytes();
   EXPECT_LE(committed, reserved);
@@ -59,11 +59,11 @@
   size_t committed_metadata = MetaspaceUtils::committed_bytes(Metaspace::NonClassType);
   EXPECT_GT(committed_metadata, 0UL);
   EXPECT_LE(committed_metadata, committed);
 }
 
-TEST_VM(MetaspaceUtils, committed_compressed_class_pointers) {
+TEST_VM(metaspace, MetaspaceUtils_committed_compressed_class_pointers) {
   if (!UseCompressedClassPointers) {
     return;
   }
   size_t committed = MetaspaceUtils::committed_bytes();
   EXPECT_GT(committed, 0UL);
@@ -71,11 +71,11 @@
   size_t committed_class = MetaspaceUtils::committed_bytes(Metaspace::ClassType);
   EXPECT_GT(committed_class, 0UL);
   EXPECT_LE(committed_class, committed);
 }
 
-TEST_VM(MetaspaceUtils, non_compressed_class_pointers) {
+TEST_VM(metaspace, MetaspaceUtils_non_compressed_class_pointers) {
   if (UseCompressedClassPointers) {
     return;
   }
 
   size_t committed_class = MetaspaceUtils::committed_bytes(Metaspace::ClassType);
@@ -97,11 +97,11 @@
   EXPECT_GT(stats.reserved(), 0UL);
   EXPECT_GT(stats.committed(), 0UL);
   EXPECT_GT(stats.used(), 0UL);
 }
 
-TEST_VM(MetaspaceUtils, get_statistics) {
+TEST_VM(MetaspaceUtils, MetaspaceUtils_get_statistics) {
   MetaspaceCombinedStats combined_stats = MetaspaceUtils::get_combined_statistics();
   check_metaspace_stats_are_not_null(combined_stats);
   check_metaspace_stats_are_consistent(combined_stats);
   check_metaspace_stats_are_not_null(combined_stats.non_class_space_stats());
   check_metaspace_stats_are_consistent(combined_stats.non_class_space_stats());
diff a/test/hotspot/gtest/metaspace/test_metaspacearena.cpp b/test/hotspot/gtest/metaspace/test_metaspacearena.cpp
--- a/test/hotspot/gtest/metaspace/test_metaspacearena.cpp
+++ b/test/hotspot/gtest/metaspace/test_metaspacearena.cpp
@@ -1,8 +1,9 @@
 /*
  * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2023 SAP SE. All rights reserved.
+ * Copyright (c) 2023 Red Hat, Inc. 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.
@@ -26,102 +27,129 @@
 #include "precompiled.hpp"
 #include "memory/metaspace/chunkManager.hpp"
 #include "memory/metaspace/commitLimiter.hpp"
 #include "memory/metaspace/counters.hpp"
 #include "memory/metaspace/internalStats.hpp"
+#include "memory/metaspace/freeBlocks.hpp"
+#include "memory/metaspace/metablock.inline.hpp"
 #include "memory/metaspace/metaspaceArena.hpp"
 #include "memory/metaspace/metaspaceArenaGrowthPolicy.hpp"
+#include "memory/metaspace/metachunkList.hpp"
 #include "memory/metaspace/metaspaceCommon.hpp"
 #include "memory/metaspace/metaspaceSettings.hpp"
 #include "memory/metaspace/metaspaceStatistics.hpp"
+#include "memory/metaspace.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/globalDefinitions.hpp"
 
-//#define LOG_PLEASE
+#define LOG_PLEASE
 #include "metaspaceGtestCommon.hpp"
 #include "metaspaceGtestContexts.hpp"
 #include "metaspaceGtestRangeHelpers.hpp"
 
-using metaspace::AllocationAlignmentByteSize;
-using metaspace::ArenaGrowthPolicy;
-using metaspace::CommitLimiter;
-using metaspace::InternalStats;
-using metaspace::MemRangeCounter;
-using metaspace::MetaspaceArena;
-using metaspace::SizeAtomicCounter;
-using metaspace::Settings;
-using metaspace::ArenaStats;
+#define HANDLE_FAILURE \
+  if (testing::Test::HasFailure()) { \
+    return; \
+  }
+
+namespace metaspace {
+
+class MetaspaceArenaTestFriend {
+  const MetaspaceArena* const _arena;
+public:
+  MetaspaceArenaTestFriend(const MetaspaceArena* arena) : _arena(arena) {}
+  const MetachunkList& chunks() const { return _arena->_chunks; }
+  const FreeBlocks* fbl() const { return _arena->_fbl; }
+};
 
 class MetaspaceArenaTestHelper {
 
   MetaspaceGtestContext& _context;
+  const ArenaGrowthPolicy* const _growth_policy;
 
-  const ArenaGrowthPolicy* _growth_policy;
-  SizeAtomicCounter _used_words_counter;
   MetaspaceArena* _arena;
 
-  void initialize(const ArenaGrowthPolicy* growth_policy, const char* name = "gtest-MetaspaceArena") {
-    _growth_policy = growth_policy;
-    _arena = new MetaspaceArena(&_context.cm(), _growth_policy, &_used_words_counter, name);
-    DEBUG_ONLY(_arena->verify());
-  }
-
 public:
 
-  // Create a helper; growth policy for arena is determined by the given spacetype|class tupel
-  MetaspaceArenaTestHelper(MetaspaceGtestContext& helper,
-                            Metaspace::MetaspaceType space_type, bool is_class,
-                            const char* name = "gtest-MetaspaceArena") :
-    _context(helper)
-  {
-    initialize(ArenaGrowthPolicy::policy_for_space_type(space_type, is_class), name);
-  }
-
   // Create a helper; growth policy is directly specified
   MetaspaceArenaTestHelper(MetaspaceGtestContext& helper, const ArenaGrowthPolicy* growth_policy,
-                           const char* name = "gtest-MetaspaceArena") :
-    _context(helper)
+                           size_t allocation_alignment_words = Metaspace::min_allocation_alignment_words) :
+    _context(helper), _growth_policy(growth_policy), _arena(nullptr)
   {
-    initialize(growth_policy, name);
+    _arena = new MetaspaceArena(_context.context(), _growth_policy, allocation_alignment_words, "gtest-MetaspaceArena");
+    DEBUG_ONLY(_arena->verify());
+    _context.inc_num_arenas_created();
   }
 
+
+  // Create a helper; growth policy for arena is determined by the given spacetype|class tupel
+  MetaspaceArenaTestHelper(MetaspaceGtestContext& helper,
+                           Metaspace::MetaspaceType space_type, bool is_class,
+                           size_t allocation_alignment_words = Metaspace::min_allocation_alignment_words) :
+    MetaspaceArenaTestHelper(helper, ArenaGrowthPolicy::policy_for_space_type(space_type, is_class), allocation_alignment_words)
+  {}
+
   ~MetaspaceArenaTestHelper() {
     delete_arena_with_tests();
   }
 
-  const CommitLimiter& limiter() const { return _context.commit_limiter(); }
   MetaspaceArena* arena() const { return _arena; }
-  SizeAtomicCounter& used_words_counter() { return _used_words_counter; }
 
   // Note: all test functions return void due to gtests limitation that we cannot use ASSERT
   // in non-void returning tests.
 
   void delete_arena_with_tests() {
     if (_arena != nullptr) {
-      size_t used_words_before = _used_words_counter.get();
-      size_t committed_words_before = limiter().committed_words();
+      size_t used_words_before = _context.used_words();
+      size_t committed_words_before = _context.committed_words();
       DEBUG_ONLY(_arena->verify());
       delete _arena;
       _arena = nullptr;
-      size_t used_words_after = _used_words_counter.get();
-      size_t committed_words_after = limiter().committed_words();
-      ASSERT_0(used_words_after);
+      size_t used_words_after = _context.used_words();
+      size_t committed_words_after = _context.committed_words();
+      assert(_context.num_arenas_created() >= 1, "Sanity");
+      if (_context.num_arenas_created() == 1) {
+        ASSERT_0(used_words_after);
+      } else {
+        ASSERT_LE(used_words_after, used_words_before);
+      }
       ASSERT_LE(committed_words_after, committed_words_before);
     }
   }
 
   void usage_numbers_with_test(size_t* p_used, size_t* p_committed, size_t* p_capacity) const {
-    _arena->usage_numbers(p_used, p_committed, p_capacity);
-    if (p_used != nullptr) {
-      if (p_committed != nullptr) {
-        ASSERT_GE(*p_committed, *p_used);
-      }
-      // Since we own the used words counter, it should reflect our usage number 1:1
-      ASSERT_EQ(_used_words_counter.get(), *p_used);
+    size_t arena_used = 0, arena_committed = 0, arena_reserved = 0;
+    _arena->usage_numbers(&arena_used, &arena_committed, &arena_reserved);
+    EXPECT_GE(arena_committed, arena_used);
+    EXPECT_GE(arena_reserved, arena_committed);
+
+    size_t context_used = _context.used_words();
+    size_t context_committed = _context.committed_words();
+    size_t context_reserved = _context.reserved_words();
+    EXPECT_GE(context_committed, context_used);
+    EXPECT_GE(context_reserved, context_committed);
+
+    // If only one arena uses the context, usage numbers must match.
+    if (_context.num_arenas_created() == 1) {
+      EXPECT_EQ(context_used, arena_used);
+    } else {
+      assert(_context.num_arenas_created() > 1, "Sanity");
+      EXPECT_GE(context_used, arena_used);
+    }
+
+    // commit, reserve numbers don't have to match since free chunks may exist
+    EXPECT_GE(context_committed, arena_committed);
+    EXPECT_GE(context_reserved, arena_reserved);
+
+    if (p_used) {
+      *p_used = arena_used;
     }
-    if (p_committed != nullptr && p_capacity != nullptr) {
-      ASSERT_GE(*p_capacity, *p_committed);
+    if (p_committed) {
+      *p_committed = arena_committed;
+    }
+    if (p_capacity) {
+      *p_capacity = arena_reserved;
     }
   }
 
   // Allocate; caller expects success; return pointer in *p_return_value
   void allocate_from_arena_with_tests_expect_success(MetaWord** p_return_value, size_t word_size) {
@@ -140,45 +168,54 @@
     MetaWord* dummy = nullptr;
     allocate_from_arena_with_tests(&dummy, word_size);
     ASSERT_NULL(dummy);
   }
 
-  // Allocate; it may or may not work; return value in *p_return_value
   void allocate_from_arena_with_tests(MetaWord** p_return_value, size_t word_size) {
+    MetaBlock result, wastage;
+    allocate_from_arena_with_tests(word_size, result, wastage);
+    if (wastage.is_nonempty()) {
+      _arena->deallocate(wastage);
+      wastage.reset();
+    }
+    (*p_return_value) = result.base();
+  }
+
+  // Allocate; it may or may not work; return value in *p_return_value
+  void allocate_from_arena_with_tests(size_t word_size, MetaBlock& result, MetaBlock& wastage) {
 
     // Note: usage_numbers walks all chunks in use and counts.
     size_t used = 0, committed = 0, capacity = 0;
     usage_numbers_with_test(&used, &committed, &capacity);
 
-    size_t possible_expansion = limiter().possible_expansion_words();
+    size_t possible_expansion = _context.commit_limiter().possible_expansion_words();
 
-    MetaWord* p = _arena->allocate(word_size);
+    result = _arena->allocate(word_size, wastage);
 
     SOMETIMES(DEBUG_ONLY(_arena->verify();))
 
     size_t used2 = 0, committed2 = 0, capacity2 = 0;
     usage_numbers_with_test(&used2, &committed2, &capacity2);
 
-    if (p == nullptr) {
+    if (result.is_empty()) {
       // Allocation failed.
       ASSERT_LT(possible_expansion, word_size);
       ASSERT_EQ(used, used2);
       ASSERT_EQ(committed, committed2);
       ASSERT_EQ(capacity, capacity2);
     } else {
       // Allocation succeeded. Should be correctly aligned.
-      ASSERT_TRUE(is_aligned(p, AllocationAlignmentByteSize));
+      ASSERT_TRUE(result.is_aligned_base(_arena->allocation_alignment_words()));
+
       // used: may go up or may not (since our request may have been satisfied from the freeblocklist
       //   whose content already counts as used).
       // committed: may go up, may not
       // capacity: ditto
       ASSERT_GE(used2, used);
       ASSERT_GE(committed2, committed);
       ASSERT_GE(capacity2, capacity);
     }
-
-    *p_return_value = p;
   }
 
   // Allocate; it may or may not work; but caller does not care for the result value
   void allocate_from_arena_with_tests(size_t word_size) {
     MetaWord* dummy = nullptr;
@@ -187,11 +224,11 @@
 
   void deallocate_with_tests(MetaWord* p, size_t word_size) {
     size_t used = 0, committed = 0, capacity = 0;
     usage_numbers_with_test(&used, &committed, &capacity);
 
-    _arena->deallocate(p, word_size);
+    _arena->deallocate(MetaBlock(p, word_size));
 
     SOMETIMES(DEBUG_ONLY(_arena->verify();))
 
     size_t used2 = 0, committed2 = 0, capacity2 = 0;
     usage_numbers_with_test(&used2, &committed2, &capacity2);
@@ -207,13 +244,17 @@
     ArenaStats stats;
     _arena->add_to_statistics(&stats);
     return stats;
   }
 
+  MetaspaceArenaTestFriend internal_access() const {
+    return MetaspaceArenaTestFriend (_arena);
+  }
+
   // Convenience method to return number of chunks in arena (including current chunk)
   int get_number_of_chunks() const {
-    return get_arena_statistics().totals()._num;
+    return internal_access().chunks().count();
   }
 
 };
 
 static void test_basics(size_t commit_limit, bool is_micro) {
@@ -406,27 +447,32 @@
     MetaspaceGtestContext context;
     MetaspaceArenaTestHelper helper(context, Metaspace::StandardMetaspaceType, false);
 
     MetaWord* p1 = nullptr;
     helper.allocate_from_arena_with_tests_expect_success(&p1, s);
+    ASSERT_FALSE(HasFailure());
 
     size_t used1 = 0, capacity1 = 0;
     helper.usage_numbers_with_test(&used1, nullptr, &capacity1);
+    ASSERT_FALSE(HasFailure());
     ASSERT_EQ(used1, s);
 
     helper.deallocate_with_tests(p1, s);
 
     size_t used2 = 0, capacity2 = 0;
     helper.usage_numbers_with_test(&used2, nullptr, &capacity2);
+    ASSERT_FALSE(HasFailure());
     ASSERT_EQ(used1, used2);
     ASSERT_EQ(capacity2, capacity2);
 
     MetaWord* p2 = nullptr;
     helper.allocate_from_arena_with_tests_expect_success(&p2, s);
+    ASSERT_FALSE(HasFailure());
 
     size_t used3 = 0, capacity3 = 0;
     helper.usage_numbers_with_test(&used3, nullptr, &capacity3);
+    ASSERT_FALSE(HasFailure());
     ASSERT_EQ(used3, used2);
     ASSERT_EQ(capacity3, capacity2);
 
     // Actually, we should get the very same allocation back
     ASSERT_EQ(p1, p2);
@@ -465,10 +511,11 @@
   while (context.commit_limiter().possible_expansion_words() >= Settings::commit_granule_words() * 2 &&
       allocated_from_1_and_2 < commit_limit) {
     helper1.allocate_from_arena_with_tests_expect_success(1);
     helper2.allocate_from_arena_with_tests_expect_success(1);
     allocated_from_1_and_2 += 2;
+    HANDLE_FAILURE
   }
 
   // Now, allocating from helper3, creep up on the limit
   size_t allocated_from_3 = 0;
   MetaWord* p = nullptr;
@@ -513,13 +560,13 @@
   // watch it grow. Used/committed/capacity should not grow in
   // large jumps. Also, different types of MetaspaceArena should
   // have different initial capacities.
 
   MetaspaceGtestContext context;
-  MetaspaceArenaTestHelper smhelper(context, type, is_class, "Grower");
+  MetaspaceArenaTestHelper smhelper(context, type, is_class);
 
-  MetaspaceArenaTestHelper smhelper_harrasser(context, Metaspace::ReflectionMetaspaceType, true, "Harasser");
+  MetaspaceArenaTestHelper smhelper_harrasser(context, Metaspace::ReflectionMetaspaceType, true);
 
   size_t used = 0, committed = 0, capacity = 0;
   const size_t alloc_words = 16;
 
   smhelper.arena()->usage_numbers(&used, &committed, &capacity);
@@ -578,16 +625,18 @@
     if (!test_in_place_enlargement) {
       smhelper_harrasser.allocate_from_arena_with_tests_expect_success(alloc_words * 2);
     }
 
     smhelper.allocate_from_arena_with_tests_expect_success(alloc_words);
+    HANDLE_FAILURE
     words_allocated += metaspace::get_raw_word_size_for_requested_word_size(alloc_words);
     num_allocated++;
 
     size_t used2 = 0, committed2 = 0, capacity2 = 0;
 
     smhelper.arena()->usage_numbers(&used2, &committed2, &capacity2);
+    HANDLE_FAILURE
 
     // used should not grow larger than what we allocated, plus possible overhead.
     ASSERT_GE(used2, used);
     ASSERT_LE(used2, used + alloc_words * 2);
     ASSERT_LE(used2, words_allocated + 100);
@@ -625,10 +674,14 @@
 
     capacity = capacity2;
 
   }
 
+  // No FBL should exist, we did not deallocate
+  ASSERT_EQ(smhelper.internal_access().fbl(), (FreeBlocks*)nullptr);
+  ASSERT_EQ(smhelper_harrasser.internal_access().fbl(), (FreeBlocks*)nullptr);
+
   // After all this work, we should see an increase in number of chunk-in-place-enlargements
   //  (this especially is vulnerable to regression: the decisions of when to do in-place-enlargements are somewhat
   //   complicated, see MetaspaceArena::attempt_enlarge_current_chunk())
 #ifdef ASSERT
   if (test_in_place_enlargement) {
@@ -728,11 +781,12 @@
 // Test that repeated allocation-deallocation cycles with the same block size
 //  do not increase metaspace usage after the initial allocation (the deallocated
 //  block should be reused by the next allocation).
 static void test_repeatedly_allocate_and_deallocate(bool is_topmost) {
   // Test various sizes, including (important) the max. possible block size = 1 root chunk
-  for (size_t blocksize = Metaspace::max_allocation_word_size(); blocksize >= 1; blocksize /= 2) {
+  for (size_t blocksize = Metaspace::max_allocation_word_size();
+       blocksize >= Metaspace::min_allocation_word_size; blocksize /= 2) {
     size_t used1 = 0, used2 = 0, committed1 = 0, committed2 = 0;
     MetaWord* p = nullptr, *p2 = nullptr;
 
     MetaspaceGtestContext context;
     MetaspaceArenaTestHelper helper(context, Metaspace::StandardMetaspaceType, false);
@@ -740,19 +794,21 @@
     // First allocation
     helper.allocate_from_arena_with_tests_expect_success(&p, blocksize);
     if (!is_topmost) {
       // another one on top, size does not matter.
       helper.allocate_from_arena_with_tests_expect_success(0x10);
+      HANDLE_FAILURE
     }
 
     // Measure
     helper.usage_numbers_with_test(&used1, &committed1, nullptr);
 
     // Dealloc, alloc several times with the same size.
     for (int i = 0; i < 5; i ++) {
       helper.deallocate_with_tests(p, blocksize);
       helper.allocate_from_arena_with_tests_expect_success(&p2, blocksize);
+      HANDLE_FAILURE
       // We should get the same pointer back.
       EXPECT_EQ(p2, p);
     }
 
     // Measure again
@@ -767,5 +823,126 @@
 }
 
 TEST_VM(metaspace, MetaspaceArena_test_repeatedly_allocate_and_deallocate_nontop_allocation) {
   test_repeatedly_allocate_and_deallocate(false);
 }
+
+static void test_random_aligned_allocation(size_t arena_alignment_words, SizeRange range) {
+  if (Settings::use_allocation_guard()) {
+    return;
+  }
+
+  // We let the arena use 4K chunks, unless the alloc size is larger.
+  chunklevel_t level = CHUNK_LEVEL_4K;
+  const ArenaGrowthPolicy policy (&level, 1);
+  const size_t chunk_word_size = word_size_for_level(level);
+
+  size_t expected_used = 0;
+
+  MetaspaceGtestContext context;
+  MetaspaceArenaTestHelper helper(context, &policy, arena_alignment_words);
+
+  size_t last_alloc_size = 0;
+  unsigned num_allocations = 0;
+
+  const size_t max_used = MIN2(MAX2(chunk_word_size * 10, (range.highest() * 100)),
+                               LP64_ONLY(64) NOT_LP64(16) * M); // word size!
+  while (expected_used < max_used) {
+
+    const int chunks_before = helper.get_number_of_chunks();
+
+    MetaBlock result, wastage;
+    size_t alloc_words = range.random_value();
+    NOT_LP64(alloc_words = align_up(alloc_words, Metaspace::min_allocation_alignment_words));
+    helper.allocate_from_arena_with_tests(alloc_words, result, wastage);
+
+    ASSERT_TRUE(result.is_nonempty());
+    ASSERT_TRUE(result.is_aligned_base(arena_alignment_words));
+    ASSERT_EQ(result.word_size(), alloc_words);
+
+    expected_used += alloc_words + wastage.word_size();
+    const int chunks_now = helper.get_number_of_chunks();
+    ASSERT_GE(chunks_now, chunks_before);
+    ASSERT_LE(chunks_now, chunks_before + 1);
+
+    // Estimate wastage:
+    // Guessing at wastage is somewhat simple since we don't expect to ever use the fbl (we
+    // don't deallocate). Therefore, wastage can only be caused by alignment gap or by
+    // salvaging an old chunk before a new chunk is added.
+    const bool expect_alignment_gap = !is_aligned(last_alloc_size, arena_alignment_words);
+    const bool new_chunk_added = chunks_now > chunks_before;
+
+    if (num_allocations == 0) {
+      // expect no wastage if its the first allocation in the arena
+      ASSERT_TRUE(wastage.is_empty());
+    } else {
+      if (expect_alignment_gap) {
+        // expect wastage if the alignment requires it
+        ASSERT_TRUE(wastage.is_nonempty());
+      }
+    }
+
+    if (wastage.is_nonempty()) {
+      // If we have wastage, we expect it to be either too small or unaligned. That would not be true
+      // for wastage from the fbl, which could have any size; however, in this test we don't deallocate,
+      // so we don't expect wastage from the fbl.
+      if (wastage.is_aligned_base(arena_alignment_words)) {
+        ASSERT_LT(wastage.word_size(), alloc_words);
+      }
+      if (new_chunk_added) {
+        // chunk turnover: no more wastage than size of a commit granule, since we salvage the
+        // committed remainder of the old chunk.
+        ASSERT_LT(wastage.word_size(), Settings::commit_granule_words());
+      } else {
+        // No chunk turnover: no more wastage than what alignment requires.
+        ASSERT_LT(wastage.word_size(), arena_alignment_words);
+      }
+    }
+
+    // Check stats too
+    size_t used, committed, reserved;
+    helper.usage_numbers_with_test(&used, &committed, &reserved);
+    ASSERT_EQ(used, expected_used);
+
+    // No FBL should exist, we did not deallocate
+    ASSERT_EQ(helper.internal_access().fbl(), (FreeBlocks*)nullptr);
+
+    HANDLE_FAILURE
+
+    last_alloc_size = alloc_words;
+    num_allocations ++;
+  }
+  LOG("allocs: %u", num_allocations);
+}
+
+#define TEST_ARENA_WITH_ALIGNMENT_SMALL_RANGE(al)                              \
+TEST_VM(metaspace, MetaspaceArena_test_random_small_aligned_allocation_##al) { \
+  static const SizeRange range(Metaspace::min_allocation_word_size, 128);      \
+  test_random_aligned_allocation(al, range);                                   \
+}
+
+#ifdef _LP64
+TEST_ARENA_WITH_ALIGNMENT_SMALL_RANGE(1);
+#endif
+TEST_ARENA_WITH_ALIGNMENT_SMALL_RANGE(2);
+TEST_ARENA_WITH_ALIGNMENT_SMALL_RANGE(8);
+TEST_ARENA_WITH_ALIGNMENT_SMALL_RANGE(32);
+TEST_ARENA_WITH_ALIGNMENT_SMALL_RANGE(128);
+TEST_ARENA_WITH_ALIGNMENT_SMALL_RANGE(MIN_CHUNK_WORD_SIZE);
+
+#define TEST_ARENA_WITH_ALIGNMENT_LARGE_RANGE(al)                              \
+TEST_VM(metaspace, MetaspaceArena_test_random_large_aligned_allocation_##al) { \
+  static const SizeRange range(Metaspace::max_allocation_word_size() / 2,      \
+                                   Metaspace::max_allocation_word_size());     \
+  test_random_aligned_allocation(al, range);                                   \
+}
+
+#ifdef _LP64
+TEST_ARENA_WITH_ALIGNMENT_LARGE_RANGE(1);
+#endif
+TEST_ARENA_WITH_ALIGNMENT_LARGE_RANGE(2);
+TEST_ARENA_WITH_ALIGNMENT_LARGE_RANGE(8);
+TEST_ARENA_WITH_ALIGNMENT_LARGE_RANGE(32);
+TEST_ARENA_WITH_ALIGNMENT_LARGE_RANGE(128);
+TEST_ARENA_WITH_ALIGNMENT_LARGE_RANGE(MIN_CHUNK_WORD_SIZE);
+
+} // namespace metaspace
diff a/test/hotspot/gtest/metaspace/test_metaspacearena_stress.cpp b/test/hotspot/gtest/metaspace/test_metaspacearena_stress.cpp
--- a/test/hotspot/gtest/metaspace/test_metaspacearena_stress.cpp
+++ b/test/hotspot/gtest/metaspace/test_metaspacearena_stress.cpp
@@ -24,12 +24,14 @@
  */
 
 #include "precompiled.hpp"
 #include "memory/metaspace/chunkManager.hpp"
 #include "memory/metaspace/counters.hpp"
+#include "memory/metaspace/metablock.hpp"
 #include "memory/metaspace/metaspaceArena.hpp"
 #include "memory/metaspace/metaspaceArenaGrowthPolicy.hpp"
+#include "memory/metaspace/metaspaceContext.hpp"
 #include "memory/metaspace/metaspaceSettings.hpp"
 #include "memory/metaspace/metaspaceStatistics.hpp"
 #include "runtime/mutexLocker.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/globalDefinitions.hpp"
@@ -41,11 +43,13 @@
 using metaspace::AllocationAlignmentByteSize;
 using metaspace::ArenaGrowthPolicy;
 using metaspace::ChunkManager;
 using metaspace::IntCounter;
 using metaspace::MemRangeCounter;
+using metaspace::MetaBlock;
 using metaspace::MetaspaceArena;
+using metaspace::MetaspaceContext;
 using metaspace::SizeAtomicCounter;
 using metaspace::ArenaStats;
 using metaspace::InUseChunkStats;
 
 // Little randomness helper
@@ -124,20 +128,18 @@
 
 public:
 
   MetaspaceArena* arena() { return _arena; }
 
-  MetaspaceArenaTestBed(ChunkManager* cm, const ArenaGrowthPolicy* alloc_sequence,
-                        SizeAtomicCounter* used_words_counter, SizeRange allocation_range) :
-    _arena(nullptr),
-    _allocation_range(allocation_range),
-    _size_of_last_failed_allocation(0),
-    _allocations(nullptr),
-    _alloc_count(),
-    _dealloc_count()
+  MetaspaceArenaTestBed(MetaspaceContext* context, const ArenaGrowthPolicy* growth_policy,
+                        size_t allocation_alignment_words, SizeRange allocation_range)
+    : _arena(nullptr)
+    , _allocation_range(allocation_range)
+    , _size_of_last_failed_allocation(0)
+    , _allocations(nullptr)
   {
-    _arena = new MetaspaceArena(cm, alloc_sequence, used_words_counter, "gtest-MetaspaceArenaTestBed-sm");
+    _arena = new MetaspaceArena(context, growth_policy, Metaspace::min_allocation_alignment_words, "gtest-MetaspaceArenaTestBed-sm");
   }
 
   ~MetaspaceArenaTestBed() {
 
     verify_arena_statistics();
@@ -163,17 +165,24 @@
   size_t size_of_last_failed_allocation() const { return _size_of_last_failed_allocation; }
 
   // Allocate a random amount. Return false if the allocation failed.
   bool checked_random_allocate() {
     size_t word_size = 1 + _allocation_range.random_value();
-    MetaWord* p = _arena->allocate(word_size);
-    if (p != nullptr) {
-      EXPECT_TRUE(is_aligned(p, AllocationAlignmentByteSize));
+    MetaBlock wastage;
+    MetaBlock bl = _arena->allocate(word_size, wastage);
+    // We only expect wastage if either alignment was not met or the chunk remainder
+    // was not large enough.
+    if (wastage.is_nonempty()) {
+      _arena->deallocate(wastage);
+      wastage.reset();
+    }
+    if (bl.is_nonempty()) {
+      EXPECT_TRUE(is_aligned(bl.base(), AllocationAlignmentByteSize));
 
       allocation_t* a = NEW_C_HEAP_OBJ(allocation_t, mtInternal);
       a->word_size = word_size;
-      a->p = p;
+      a->p = bl.base();
       a->mark();
       a->next = _allocations;
       _allocations = a;
       _alloc_count.add(word_size);
       if ((_alloc_count.count() % 20) == 0) {
@@ -193,11 +202,11 @@
     while (a && a->p != nullptr && os::random() % 10 != 0) {
       a = a->next;
     }
     if (a != nullptr && a->p != nullptr) {
       a->verify();
-      _arena->deallocate(a->p, a->word_size);
+      _arena->deallocate(MetaBlock(a->p, a->word_size));
       _dealloc_count.add(a->word_size);
       a->p = nullptr; a->word_size = 0;
       if ((_dealloc_count.count() % 20) == 0) {
         verify_arena_statistics();
         DEBUG_ONLY(_arena->verify();)
@@ -218,12 +227,12 @@
 
   //////// Bed creation, destruction ///////
 
   void create_new_test_bed_at(int slotindex, const ArenaGrowthPolicy* growth_policy, SizeRange allocation_range) {
     DEBUG_ONLY(_testbeds.check_slot_is_null(slotindex));
-    MetaspaceArenaTestBed* bed = new MetaspaceArenaTestBed(&_context.cm(), growth_policy,
-                                                       &_used_words_counter, allocation_range);
+    MetaspaceArenaTestBed* bed = new MetaspaceArenaTestBed(_context.context(), growth_policy,
+        Metaspace::min_allocation_alignment_words, allocation_range);
     _testbeds.set_at(slotindex, bed);
     _num_beds.increment();
   }
 
   void create_random_test_bed_at(int slotindex) {
diff a/test/hotspot/gtest/oops/test_arrayOop.cpp b/test/hotspot/gtest/oops/test_arrayOop.cpp
--- a/test/hotspot/gtest/oops/test_arrayOop.cpp
+++ b/test/hotspot/gtest/oops/test_arrayOop.cpp
@@ -80,11 +80,27 @@
 }
 // T_VOID and T_ADDRESS are not supported by max_array_length()
 
 TEST_VM(arrayOopDesc, base_offset) {
 #ifdef _LP64
-  if (UseCompressedClassPointers) {
+  if (UseCompactObjectHeaders) {
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_BOOLEAN), 12);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_BYTE),    12);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_SHORT),   12);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_CHAR),    12);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_INT),     12);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_FLOAT),   12);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_LONG),    16);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_DOUBLE),  16);
+    if (UseCompressedOops) {
+      EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_OBJECT), 12);
+      EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_ARRAY),  12);
+    } else {
+      EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_OBJECT), 16);
+      EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_ARRAY),  16);
+    }
+  } else if (UseCompressedClassPointers) {
     EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_BOOLEAN), 16);
     EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_BYTE),    16);
     EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_SHORT),   16);
     EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_CHAR),    16);
     EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_INT),     16);
diff a/test/hotspot/gtest/oops/test_objArrayOop.cpp b/test/hotspot/gtest/oops/test_objArrayOop.cpp
--- a/test/hotspot/gtest/oops/test_objArrayOop.cpp
+++ b/test/hotspot/gtest/oops/test_objArrayOop.cpp
@@ -26,32 +26,39 @@
 #include "unittest.hpp"
 #include "utilities/globalDefinitions.hpp"
 
 TEST_VM(objArrayOop, osize) {
   static const struct {
-    int objal; bool ccp; bool coops; int result;
+    int objal; bool ccp; bool coops; bool coh; int result;
   } x[] = {
-//    ObjAligInB, UseCCP, UseCoops, object size in heap words
+//    ObjAligInB, UseCCP, UseCoops, UseCOH, object size in heap words
 #ifdef _LP64
-    { 8,          false,  false,    4 },  // 20 byte header, 8 byte oops
-    { 8,          false,  true,     3 },  // 20 byte header, 4 byte oops
-    { 8,          true,   false,    3 },  // 16 byte header, 8 byte oops
-    { 8,          true,   true,     3 },  // 16 byte header, 4 byte oops
-    { 16,         false,  false,    4 },  // 20 byte header, 8 byte oops, 16-byte align
-    { 16,         false,  true,     4 },  // 20 byte header, 4 byte oops, 16-byte align
-    { 16,         true,   false,    4 },  // 16 byte header, 8 byte oops, 16-byte align
-    { 16,         true,   true,     4 },  // 16 byte header, 4 byte oops, 16-byte align
-    { 256,        false,  false,    32 }, // 20 byte header, 8 byte oops, 256-byte align
-    { 256,        false,  true,     32 }, // 20 byte header, 4 byte oops, 256-byte align
-    { 256,        true,   false,    32 }, // 16 byte header, 8 byte oops, 256-byte align
-    { 256,        true,   true,     32 }, // 16 byte header, 4 byte oops, 256-byte align
+    { 8,          false,  false, false,   4 },  // 20 byte header, 8 byte oops
+    { 8,          false,  true,  false,   3 },  // 20 byte header, 4 byte oops
+    { 8,          true,   false, false,   3 },  // 16 byte header, 8 byte oops
+    { 8,          true,   true,  false,   3 },  // 16 byte header, 4 byte oops
+    { 8,          true,   false, true,    3 },  // 12 byte header, 8 byte oops
+    { 8,          true,   true,  true,    2 },  // 12 byte header, 4 byte oops
+    { 16,         false,  false, false,   4 },  // 20 byte header, 8 byte oops, 16-byte align
+    { 16,         false,  true,  false,   4 },  // 20 byte header, 4 byte oops, 16-byte align
+    { 16,         true,   false, false,   4 },  // 16 byte header, 8 byte oops, 16-byte align
+    { 16,         true,   true,  false,   4 },  // 16 byte header, 4 byte oops, 16-byte align
+    { 16,         true,   false, true,    4 },  // 12 byte header, 8 byte oops, 16-byte align
+    { 16,         true,   true,  true,    2 },  // 12 byte header, 4 byte oops, 16-byte align
+    { 256,        false,  false, false,  32 }, // 20 byte header, 8 byte oops, 256-byte align
+    { 256,        false,  true,  false,  32 }, // 20 byte header, 4 byte oops, 256-byte align
+    { 256,        true,   false, false,  32 }, // 16 byte header, 8 byte oops, 256-byte align
+    { 256,        true,   true,  false,  32 }, // 16 byte header, 4 byte oops, 256-byte align
+    { 256,        true,   false, true,   32 }, // 12 byte header, 8 byte oops, 256-byte align
+    { 256,        true,   true,  true,   32 }, // 12 byte header, 4 byte oops, 256-byte align
 #else
-    { 8,          false,  false,    4 }, // 12 byte header, 4 byte oops, wordsize 4
+    { 8,          false,  false, false,   4 }, // 12 byte header, 4 byte oops, wordsize 4
 #endif
-    { -1,         false,  false,   -1 }
+    { -1,         false,  false, false,  -1 }
   };
   for (int i = 0; x[i].result != -1; i++) {
-    if (x[i].objal == (int)ObjectAlignmentInBytes && x[i].ccp == UseCompressedClassPointers && x[i].coops == UseCompressedOops) {
+    if (x[i].objal == (int)ObjectAlignmentInBytes && x[i].ccp == UseCompressedClassPointers && x[i].coops == UseCompressedOops &&
+        x[i].coh == UseCompactObjectHeaders) {
       EXPECT_EQ(objArrayOopDesc::object_size(1), (size_t)x[i].result);
     }
   }
 }
diff a/test/hotspot/gtest/oops/test_typeArrayOop.cpp b/test/hotspot/gtest/oops/test_typeArrayOop.cpp
--- a/test/hotspot/gtest/oops/test_typeArrayOop.cpp
+++ b/test/hotspot/gtest/oops/test_typeArrayOop.cpp
@@ -34,11 +34,15 @@
   memset(mem, 0, ARRAY_SIZE(mem));
 
   char* addr = align_up(mem, 16);
 
   typeArrayOop o = (typeArrayOop) cast_to_oop(addr);
-  o->set_klass(Universe::boolArrayKlass());
+  if (UseCompactObjectHeaders) {
+    o->set_mark(Universe::boolArrayKlass()->prototype_header());
+  } else {
+    o->set_klass(Universe::boolArrayKlass());
+  }
   o->set_length(10);
 
 
   ASSERT_EQ((jboolean)0, o->bool_at(0));
   ASSERT_EQ((jboolean)0, o->bool_at(1));
diff a/test/hotspot/gtest/runtime/test_objectMonitor.cpp b/test/hotspot/gtest/runtime/test_objectMonitor.cpp
--- a/test/hotspot/gtest/runtime/test_objectMonitor.cpp
+++ b/test/hotspot/gtest/runtime/test_objectMonitor.cpp
@@ -25,22 +25,22 @@
 #include "runtime/objectMonitor.hpp"
 #include "runtime/vm_version.hpp"
 #include "unittest.hpp"
 
 TEST_VM(ObjectMonitor, sanity) {
- uint cache_line_size = VM_Version::L1_data_cache_line_size();
+  uint cache_line_size = VM_Version::L1_data_cache_line_size();
 
- if (cache_line_size != 0) {
-   // We were able to determine the L1 data cache line size so
-   // do some cache line specific sanity checks
-   EXPECT_EQ((size_t) 0, sizeof (PaddedEnd<ObjectMonitor>) % cache_line_size)
-        << "PaddedEnd<ObjectMonitor> size is not a "
-        << "multiple of a cache line which permits false sharing. "
-        << "sizeof(PaddedEnd<ObjectMonitor>) = "
-        << sizeof (PaddedEnd<ObjectMonitor>)
-        << "; cache_line_size = " << cache_line_size;
+  if (cache_line_size != 0) {
 
-   EXPECT_GE((size_t) in_bytes(ObjectMonitor::owner_offset()), cache_line_size)
-        << "the _header and _owner fields are closer "
-        << "than a cache line which permits false sharing.";
+    EXPECT_EQ(in_bytes(ObjectMonitor::metadata_offset()), 0)
+         << "_metadata at a non 0 offset. metadata_offset = "
+         << in_bytes(ObjectMonitor::metadata_offset());
+
+    EXPECT_GE((size_t) in_bytes(ObjectMonitor::owner_offset()), cache_line_size)
+         << "the _metadata and _owner fields are closer "
+         << "than a cache line which permits false sharing.";
+
+    EXPECT_GE((size_t) in_bytes(ObjectMonitor::recursions_offset() - ObjectMonitor::owner_offset()), cache_line_size)
+         << "the _owner and _recursions fields are closer "
+         << "than a cache line which permits false sharing.";
   }
 }
diff a/test/hotspot/gtest/runtime/test_os.cpp b/test/hotspot/gtest/runtime/test_os.cpp
--- a/test/hotspot/gtest/runtime/test_os.cpp
+++ b/test/hotspot/gtest/runtime/test_os.cpp
@@ -973,6 +973,5 @@
   // with high vm.mmap_min_addr settings.
 #if defined(_LP64)
   ASSERT_LE(s, NOT_LINUX(G * 4) LINUX_ONLY(G * 1024));
 #endif
 }
-
diff a/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMismatchedAccess.java b/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMismatchedAccess.java
--- a/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMismatchedAccess.java
+++ b/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMismatchedAccess.java
@@ -145,11 +145,12 @@
             }
         }
     }
 
     @Test
-    @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" })
+    @IR(applyIf = {"UseCompactObjectHeaders", "false"},
+        counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" })
     public static void testByteLong1(byte[] dest, long[] src) {
         for (int i = 0; i < src.length; i++) {
             UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * i, src[i]);
         }
     }
@@ -158,11 +159,12 @@
     public static void testByteLong1_runner() {
         runAndVerify(() -> testByteLong1(byteArray, longArray), 0);
     }
 
     @Test
-    @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" })
+    @IR(applyIf = {"UseCompactObjectHeaders", "false"},
+        counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" })
     public static void testByteLong2(byte[] dest, long[] src) {
         for (int i = 1; i < src.length; i++) {
             UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * (i - 1), src[i]);
         }
     }
@@ -171,11 +173,12 @@
     public static void testByteLong2_runner() {
         runAndVerify(() -> testByteLong2(byteArray, longArray), -8);
     }
 
     @Test
-    @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" })
+    @IR(applyIf = {"UseCompactObjectHeaders", "false"},
+        counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" })
     public static void testByteLong3(byte[] dest, long[] src) {
         for (int i = 0; i < src.length - 1; i++) {
             UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * (i + 1), src[i]);
         }
     }
@@ -200,11 +203,12 @@
         baseOffset = UNSAFE.ARRAY_BYTE_BASE_OFFSET;
         runAndVerify(() -> testByteLong4(byteArray, longArray, 0, size), 0);
     }
 
     @Test
-    @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" })
+    @IR(applyIf = {"UseCompactObjectHeaders", "false"},
+        counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" })
     public static void testByteLong5(byte[] dest, long[] src, int start, int stop) {
         for (int i = start; i < stop; i++) {
             UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * (i + baseOffset), src[i]);
         }
     }
@@ -214,11 +218,12 @@
         baseOffset = 1;
         runAndVerify(() -> testByteLong5(byteArray, longArray, 0, size-1), 8);
     }
 
     @Test
-    @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" })
+    @IR(applyIf = {"UseCompactObjectHeaders", "false"},
+        counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" })
     public static void testByteByte1(byte[] dest, byte[] src) {
         for (int i = 0; i < src.length / 8; i++) {
             UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * i, UNSAFE.getLongUnaligned(src, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * i));
         }
     }
@@ -227,11 +232,12 @@
     public static void testByteByte1_runner() {
         runAndVerify2(() -> testByteByte1(byteArray, byteArray), 0);
     }
 
     @Test
-    @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" })
+    @IR(applyIf = {"UseCompactObjectHeaders", "false"},
+        counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" })
     public static void testByteByte2(byte[] dest, byte[] src) {
         for (int i = 1; i < src.length / 8; i++) {
             UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * (i - 1), UNSAFE.getLongUnaligned(src, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * i));
         }
     }
diff a/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationNotRun.java b/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationNotRun.java
--- a/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationNotRun.java
+++ b/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationNotRun.java
@@ -49,11 +49,12 @@
     static int sizeBytes = 8 * size;
     static byte[] byteArray = new byte[sizeBytes];
     static long[] longArray = new long[size];
 
     @Test
-    @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" })
+    @IR(applyIf = {"UseCompactObjectHeaders", "false"},
+        counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" })
     public static void test(byte[] dest, long[] src) {
         for (int i = 0; i < src.length; i++) {
             if ((i < 0) || (8 > sizeBytes - i)) {
                 throw new IndexOutOfBoundsException();
             }
diff a/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java b/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java
--- a/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java
+++ b/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java
@@ -140,11 +140,12 @@
                     "UseSSE",
                     "UseSVE",
                     "UseZbb",
                     "UseRVV",
                     "Xlog",
-                    "LogCompilation"
+                    "LogCompilation",
+                    "UseCompactObjectHeaders"
             )
     );
 
     public static final boolean VERBOSE = Boolean.getBoolean("Verbose");
     public static final boolean TESTLIST = !System.getProperty("Test", "").isEmpty();
diff a/test/hotspot/jtreg/compiler/loopopts/superword/TestAlignVector.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestAlignVector.java
--- a/test/hotspot/jtreg/compiler/loopopts/superword/TestAlignVector.java
+++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestAlignVector.java
@@ -399,10 +399,11 @@
 
     @Test
     @IR(counts = {IRNode.LOAD_VECTOR_B, "> 0",
                   IRNode.AND_VB, "> 0",
                   IRNode.STORE_VECTOR, "> 0"},
+        applyIf = {"UseCompactObjectHeaders", "false"},
         applyIfPlatform = {"64-bit", "true"},
         applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true"})
     static Object[] test1(byte[] a, byte[] b, byte mask) {
         for (int i = 0; i < RANGE; i+=8) {
             // Safe to vectorize with AlignVector
@@ -707,11 +708,11 @@
 
     @Test
     @IR(counts = {IRNode.LOAD_VECTOR_S, IRNode.VECTOR_SIZE_4, "> 0",
                   IRNode.AND_VS,        IRNode.VECTOR_SIZE_4, "> 0",
                   IRNode.STORE_VECTOR, "> 0"},
-        applyIf = {"MaxVectorSize", ">=16"},
+        applyIfAnd = {"MaxVectorSize", ">=16", "UseCompactObjectHeaders", "false"},
         applyIfPlatform = {"64-bit", "true"},
         applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true"})
     static Object[] test10d(short[] a, short[] b, short mask) {
         for (int i = 13; i < RANGE-16; i+=8) {
             // init + offset -> aligned
@@ -1002,10 +1003,11 @@
     @IR(counts = {IRNode.LOAD_VECTOR_B, "> 0",
                   IRNode.LOAD_VECTOR_I, "> 0",
                   IRNode.ADD_VB, "> 0",
                   IRNode.ADD_VI, "> 0",
                   IRNode.STORE_VECTOR, "> 0"},
+        applyIf = {"UseCompactObjectHeaders", "false"},
         applyIfPlatform = {"64-bit", "true"},
         applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true"})
     static Object[] test13aIB(int[] a, byte[] b) {
         for (int i = 0; i < RANGE; i++) {
             a[i]++;
@@ -1018,10 +1020,11 @@
     @IR(counts = {IRNode.LOAD_VECTOR_I, "> 0",
                   IRNode.LOAD_VECTOR_S, "> 0",
                   IRNode.ADD_VI, "> 0",
                   IRNode.ADD_VS, "> 0",
                   IRNode.STORE_VECTOR, "> 0"},
+        applyIf = {"UseCompactObjectHeaders", "false"},
         applyIfPlatform = {"64-bit", "true"},
         applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true"})
     static Object[] test13aIS(int[] a, short[] b) {
         for (int i = 0; i < RANGE; i++) {
             a[i]++;
@@ -1038,10 +1041,11 @@
                   IRNode.ADD_VB, "> 0",
                   IRNode.ADD_VS, "> 0",
                   IRNode.ADD_VI, "> 0",
                   IRNode.ADD_VL, "> 0",
                   IRNode.STORE_VECTOR, "> 0"},
+        applyIf = {"UseCompactObjectHeaders", "false"},
         applyIfPlatform = {"64-bit", "true"},
         applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true"})
     static Object[] test13aBSIL(byte[] a, short[] b, int[] c, long[] d) {
         for (int i = 0; i < RANGE; i++) {
             a[i]++;
@@ -1073,10 +1077,11 @@
     @IR(counts = {IRNode.LOAD_VECTOR_B, "> 0",
                   IRNode.LOAD_VECTOR_I, "> 0",
                   IRNode.ADD_VB, "> 0",
                   IRNode.ADD_VI, "> 0",
                   IRNode.STORE_VECTOR, "> 0"},
+        applyIf = {"UseCompactObjectHeaders", "false"},
         applyIfPlatform = {"64-bit", "true"},
         applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true"})
     static Object[] test13bIB(int[] a, byte[] b) {
         for (int i = 1; i < RANGE; i++) {
             a[i]++;
@@ -1089,10 +1094,11 @@
     @IR(counts = {IRNode.LOAD_VECTOR_I, "> 0",
                   IRNode.LOAD_VECTOR_S, "> 0",
                   IRNode.ADD_VI, "> 0",
                   IRNode.ADD_VS, "> 0",
                   IRNode.STORE_VECTOR, "> 0"},
+        applyIf = {"UseCompactObjectHeaders", "false"},
         applyIfPlatform = {"64-bit", "true"},
         applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true"})
     static Object[] test13bIS(int[] a, short[] b) {
         for (int i = 1; i < RANGE; i++) {
             a[i]++;
@@ -1109,10 +1115,11 @@
                   IRNode.ADD_VB, "> 0",
                   IRNode.ADD_VS, "> 0",
                   IRNode.ADD_VI, "> 0",
                   IRNode.ADD_VL, "> 0",
                   IRNode.STORE_VECTOR, "> 0"},
+        applyIf = {"UseCompactObjectHeaders", "false"},
         applyIfPlatform = {"64-bit", "true"},
         applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true"})
     static Object[] test13bBSIL(byte[] a, short[] b, int[] c, long[] d) {
         for (int i = 1; i < RANGE; i++) {
             a[i]++;
diff a/test/hotspot/jtreg/compiler/loopopts/superword/TestIndependentPacksWithCyclicDependency.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestIndependentPacksWithCyclicDependency.java
--- a/test/hotspot/jtreg/compiler/loopopts/superword/TestIndependentPacksWithCyclicDependency.java
+++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestIndependentPacksWithCyclicDependency.java
@@ -264,11 +264,12 @@
         verify("test6", dataF, goldF6);
         verify("test6", dataL, goldL6);
     }
 
     @Test
-    @IR(counts = {IRNode.ADD_VI, "> 0", IRNode.MUL_VI, "> 0", IRNode.ADD_VF, "> 0"},
+    @IR(applyIf = {"UseCompactObjectHeaders", "false"},
+        counts = {IRNode.ADD_VI, "> 0", IRNode.MUL_VI, "> 0", IRNode.ADD_VF, "> 0"},
         applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"})
     static void test6(int[] dataIa, int[] dataIb, float[] dataFa, float[] dataFb,
                       long[] dataLa, long[] dataLb) {
         for (int i = 0; i < RANGE; i+=2) {
             // Chain of parallelizable op and conversion
diff a/test/hotspot/jtreg/gc/g1/TestGCLogMessages.java b/test/hotspot/jtreg/gc/g1/TestGCLogMessages.java
--- a/test/hotspot/jtreg/gc/g1/TestGCLogMessages.java
+++ b/test/hotspot/jtreg/gc/g1/TestGCLogMessages.java
@@ -257,11 +257,10 @@
         checkMessagesAtLevel(output, concRefineMessages, Level.DEBUG);
     }
 
     LogMessageWithLevel exhFailureMessages[] = new LogMessageWithLevel[] {
         new LogMessageWithLevel("Recalculate Used Memory \\(ms\\):", Level.DEBUG),
-        new LogMessageWithLevel("Restore Preserved Marks \\(ms\\):", Level.DEBUG),
         new LogMessageWithLevel("Restore Evacuation Failed Regions \\(ms\\):", Level.DEBUG),
         new LogMessageWithLevel("Process Evacuation Failed Regions \\(ms\\):", Level.DEBUG),
         new LogMessageWithLevel("Evacuation Failed Regions:", Level.DEBUG),
         new LogMessageWithLevel("Pinned Regions:", Level.DEBUG),
         new LogMessageWithLevel("Allocation Failed Regions:", Level.DEBUG),
diff a/test/hotspot/jtreg/gc/g1/plab/TestPLABPromotion.java b/test/hotspot/jtreg/gc/g1/plab/TestPLABPromotion.java
--- a/test/hotspot/jtreg/gc/g1/plab/TestPLABPromotion.java
+++ b/test/hotspot/jtreg/gc/g1/plab/TestPLABPromotion.java
@@ -30,11 +30,11 @@
  * @library /test/lib /
  * @modules java.base/jdk.internal.misc
  * @modules java.management
  * @build jdk.test.whitebox.WhiteBox
  * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
- * @run main/timeout=240 gc.g1.plab.TestPLABPromotion
+ * @run main/othervm/timeout=240 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI gc.g1.plab.TestPLABPromotion
  */
 package gc.g1.plab;
 
 import java.util.List;
 import java.util.Arrays;
@@ -46,16 +46,19 @@
 import gc.g1.plab.lib.PlabInfo;
 
 import jdk.test.lib.Platform;
 import jdk.test.lib.process.OutputAnalyzer;
 import jdk.test.lib.process.ProcessTools;
+import jdk.test.whitebox.WhiteBox;
 
 /**
  * Test checks PLAB promotion of different size objects.
  */
 public class TestPLABPromotion {
 
+    private static final boolean COMPACT_HEADERS = Platform.is64bit() && WhiteBox.getWhiteBox().getBooleanVMFlag("UseCompactObjectHeaders");
+
     // GC ID with survivor PLAB statistics
     private final static long GC_ID_SURVIVOR_STATS = 1l;
     // GC ID with old PLAB statistics
     private final static long GC_ID_OLD_STATS = 2l;
 
@@ -72,11 +75,11 @@
     private static final int PLAB_SIZE_SMALL = 1024;
     private static final int PLAB_SIZE_MEDIUM = 4096;
     private static final int PLAB_SIZE_HIGH = 65536;
     private static final int OBJECT_SIZE_SMALL  = 10 * HEAP_WORD_SIZE;
     private static final int OBJECT_SIZE_MEDIUM = 128 * HEAP_WORD_SIZE;
-    private static final int OBJECT_SIZE_HIGH   = 3072 * HEAP_WORD_SIZE;
+    private static final int OBJECT_SIZE_HIGH   = (COMPACT_HEADERS ? 3266 : 3250) * HEAP_WORD_SIZE;
     private static final int GC_NUM_SMALL = 1;
     private static final int GC_NUM_MEDIUM = 3;
     private static final int GC_NUM_HIGH = 7;
     private static final int WASTE_PCT_SMALL = 10;
     private static final int WASTE_PCT_MEDIUM = 20;
diff a/test/hotspot/jtreg/gtest/MetaspaceGtests.java b/test/hotspot/jtreg/gtest/MetaspaceGtests.java
--- a/test/hotspot/jtreg/gtest/MetaspaceGtests.java
+++ b/test/hotspot/jtreg/gtest/MetaspaceGtests.java
@@ -38,24 +38,35 @@
  * @requires vm.debug
  * @requires vm.flagless
  * @run main/native GTestWrapper --gtest_filter=metaspace* -XX:+UnlockDiagnosticVMOptions -XX:VerifyMetaspaceInterval=1
  */
 
-/* @test id=balanced-with-guards
+/* @test id=MetaspaceGuardAllocations-on
  * @summary Run metaspace-related gtests with allocation guards enabled
  * @library /test/lib
  * @modules java.base/jdk.internal.misc
  *          java.xml
  * @requires vm.debug
  * @requires vm.flagless
  * @run main/native GTestWrapper --gtest_filter=metaspace* -XX:VerifyMetaspaceInterval=1 -XX:+MetaspaceGuardAllocations
  */
 
-/* @test id=balanced-no-ccs
+/* @test id=UseCompressedClassPointers-off
  * @summary Run metaspace-related gtests with compressed class pointers off
  * @library /test/lib
  * @modules java.base/jdk.internal.misc
  *          java.xml
  * @requires vm.bits == 64
  * @requires vm.flagless
  * @run main/native GTestWrapper --gtest_filter=metaspace* -XX:+UnlockDiagnosticVMOptions -XX:-UseCompressedClassPointers
  */
+
+/* @test id=UseCompactObjectHeaders
+ * @summary Run metaspace-related gtests with tiny classpointers
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.xml
+ * @requires vm.bits == 64
+ * @requires vm.flagless
+ * @requires vm.debug
+ * @run main/native GTestWrapper --gtest_filter=metaspace* -XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders
+ */
diff a/test/hotspot/jtreg/gtest/MetaspaceUtilsGtests.java b/test/hotspot/jtreg/gtest/MetaspaceUtilsGtests.java
--- a/test/hotspot/jtreg/gtest/MetaspaceUtilsGtests.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2021 THL A29 Limited, a Tencent company. 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.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- *
- */
-
-/*
- * Note: This runs the metaspace utils related parts of gtest in configurations which
- *  are not tested explicitly in the standard gtests.
- *
- */
-
-/* @test
- * @bug 8264008
- * @summary Run metaspace utils related gtests with compressed class pointers off
- * @requires vm.bits == 64
- * @library /test/lib
- * @modules java.base/jdk.internal.misc
- *          java.xml
- * @requires vm.flagless
- * @run main/native GTestWrapper --gtest_filter=MetaspaceUtils* -XX:-UseCompressedClassPointers
- */
diff a/test/hotspot/jtreg/runtime/CompressedOops/CompressedCPUSpecificClassSpaceReservation.java b/test/hotspot/jtreg/runtime/CompressedOops/CompressedCPUSpecificClassSpaceReservation.java
--- a/test/hotspot/jtreg/runtime/CompressedOops/CompressedCPUSpecificClassSpaceReservation.java
+++ b/test/hotspot/jtreg/runtime/CompressedOops/CompressedCPUSpecificClassSpaceReservation.java
@@ -53,10 +53,11 @@
         // [0.017s][debug][os,map] reserve_between (range [0x0000000000000000-0x0000000100000000), size 0x41000000, alignment 0x1000000, randomize: 1)
         ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(
                 "-Xshare:" + (CDS ? "on" : "off"),
                 "-Xmx128m",
                 "-XX:CompressedClassSpaceSize=128m",
+                "-XX:+UnlockExperimentalVMOptions", "-XX:-UseCompactObjectHeaders",
                 "-Xlog:metaspace*", "-Xlog:metaspace+map=trace", "-Xlog:os+map=trace",
                 "-XX:+SimulateFullAddressSpace", // So that no resevation attempt will succeed
                 "-version");
         OutputAnalyzer output = new OutputAnalyzer(pb.start());
 
diff a/test/hotspot/jtreg/runtime/CompressedOops/CompressedClassPointers.java b/test/hotspot/jtreg/runtime/CompressedOops/CompressedClassPointers.java
--- a/test/hotspot/jtreg/runtime/CompressedOops/CompressedClassPointers.java
+++ b/test/hotspot/jtreg/runtime/CompressedOops/CompressedClassPointers.java
@@ -44,10 +44,11 @@
 
 public class CompressedClassPointers {
 
     static final String logging_option = "-Xlog:gc+metaspace=trace,metaspace=info,cds=trace";
     static final String reserveCCSAnywhere = "Reserving compressed class space anywhere";
+    static final String usesCompactObjectHeadersPat = "UseCompactObjectHeaders 1";
 
     // Returns true if we are to test the narrow klass base; we only do this on
     // platforms where we can be reasonably shure that we get reproducable placement).
     static boolean testNarrowKlassBase() {
         if (Platform.isWindows()) {
@@ -55,10 +56,15 @@
         }
         return true;
 
     }
 
+    // Returns true if the output indicates that the VM uses compact object headers
+    static boolean usesCompactObjectHeaders(OutputAnalyzer output) {
+        return output.getOutput().contains(usesCompactObjectHeadersPat);
+    }
+
     // Returns true if the output indicates that the ccs is reserved anywhere.
     static boolean isCCSReservedAnywhere(OutputAnalyzer output) {
         if (output.getOutput().contains(reserveCCSAnywhere)) {
             return true;
         } else {
@@ -219,11 +225,11 @@
             "-Xlog:gc+metaspace=trace",
             "-Xshare:off",
             "-Xlog:cds=trace",
             "-XX:+VerifyBeforeGC", "-version");
         OutputAnalyzer output = new OutputAnalyzer(pb.start());
-        if (!isCCSReservedAnywhere(output)) {
+        if (!isCCSReservedAnywhere(output) && !usesCompactObjectHeaders(output)) {
             output.shouldContain("Narrow klass base: 0x0000000000000000");
         }
         output.shouldHaveExitValue(0);
     }
 
@@ -237,14 +243,14 @@
             "-Xlog:gc+metaspace=trace",
             "-Xshare:off",
             "-Xlog:cds=trace",
             "-XX:+VerifyBeforeGC", "-version");
         OutputAnalyzer output = new OutputAnalyzer(pb.start());
-        if (!isCCSReservedAnywhere(output)) {
+        if (!isCCSReservedAnywhere(output) && !usesCompactObjectHeaders(output)) {
             output.shouldContain("Narrow klass base: 0x0000000000000000");
         }
-        if (!Platform.isAArch64() && !Platform.isPPC()) {
+        if (!Platform.isAArch64()  && !usesCompactObjectHeaders(output) && !Platform.isPPC()) {
             // Currently relax this test for Aarch64 and ppc.
             output.shouldContain("Narrow klass shift: 0");
         }
         output.shouldHaveExitValue(0);
     }
@@ -259,14 +265,14 @@
             "-Xlog:gc+metaspace=trace",
             "-Xshare:off",
             "-Xlog:cds=trace",
             "-XX:+VerifyBeforeGC", "-version");
         OutputAnalyzer output = new OutputAnalyzer(pb.start());
-        if (!isCCSReservedAnywhere(output)) {
+        if (!isCCSReservedAnywhere(output) && !usesCompactObjectHeaders(output)) {
             output.shouldContain("Narrow klass base: 0x0000000000000000");
         }
-        if (!Platform.isAArch64() && !Platform.isPPC()) {
+        if (!Platform.isAArch64()  && !usesCompactObjectHeaders(output) && !Platform.isPPC()) {
             // Currently relax this test for Aarch64 and ppc.
             output.shouldContain("Narrow klass shift: 0");
         }
         output.shouldHaveExitValue(0);
     }
diff a/test/hotspot/jtreg/runtime/CompressedOops/CompressedClassPointersEncodingScheme.java b/test/hotspot/jtreg/runtime/CompressedOops/CompressedClassPointersEncodingScheme.java
--- a/test/hotspot/jtreg/runtime/CompressedOops/CompressedClassPointersEncodingScheme.java
+++ b/test/hotspot/jtreg/runtime/CompressedOops/CompressedClassPointersEncodingScheme.java
@@ -40,17 +40,19 @@
 
 import java.io.IOException;
 
 public class CompressedClassPointersEncodingScheme {
 
-    private static void test(long forceAddress, long classSpaceSize, long expectedEncodingBase, int expectedEncodingShift) throws IOException {
+    private static void test(long forceAddress, boolean COH, long classSpaceSize, long expectedEncodingBase, int expectedEncodingShift) throws IOException {
         String forceAddressString = String.format("0x%016X", forceAddress).toLowerCase();
         String expectedEncodingBaseString = String.format("0x%016X", expectedEncodingBase).toLowerCase();
         ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(
                 "-Xshare:off", // to make CompressedClassSpaceBaseAddress work
                 "-XX:+UnlockDiagnosticVMOptions",
                 "-XX:-UseCompressedOops", // keep VM from optimizing heap location
+                "-XX:+UnlockExperimentalVMOptions",
+                "-XX:" + (COH ? "+" : "-") + "UseCompactObjectHeaders",
                 "-XX:CompressedClassSpaceBaseAddress=" + forceAddress,
                 "-XX:CompressedClassSpaceSize=" + classSpaceSize,
                 "-Xmx128m",
                 "-Xlog:metaspace*",
                 "-version");
@@ -58,11 +60,12 @@
 
         output.reportDiagnosticSummary();
 
         // We ignore cases where we were not able to map at the force address
         if (output.contains("reserving class space failed")) {
-            throw new SkippedException("Skipping because we cannot force ccs to " + forceAddressString);
+            System.out.println("Skipping because we cannot force ccs to " + forceAddressString);
+            return;
         }
 
         output.shouldHaveExitValue(0);
         output.shouldContain("Narrow klass base: " + expectedEncodingBaseString + ", Narrow klass shift: " + expectedEncodingShift);
     }
@@ -71,11 +74,51 @@
     final static long M = K * 1024;
     final static long G = M * 1024;
     public static void main(String[] args) throws Exception {
         // Test ccs nestling right at the end of the 4G range
         // Expecting base=0, shift=0
-        test(4 * G - 128 * M, 128 * M, 0, 0);
+        test(4 * G - 128 * M, false, 128 * M, 0, 0);
 
+        // Test ccs nestling right at the end of the 32G range
+        // Expecting:
+        // - non-aarch64: base=0, shift=3
+        // - aarch64: base to start of class range, shift 0
+        if (Platform.isAArch64()) {
+            // The best we can do on aarch64 is to be *near* the end of the 32g range, since a valid encoding base
+            // on aarch64 must be 4G aligned, and the max. class space size is 3G.
+            long forceAddress = 0x7_0000_0000L; // 28g, and also a valid EOR immediate
+            test(forceAddress, false, 3 * G, forceAddress, 0);
+        } else {
+            test(32 * G - 128 * M, false, 128 * M, 0, 3);
+        }
+
+        // Test ccs starting *below* 4G, but extending upwards beyond 4G. All platforms except aarch64 should pick
+        // zero based encoding.
+        if (Platform.isAArch64()) {
+            long forceAddress = 0xC000_0000L; // make sure we have a valid EOR immediate
+            test(forceAddress, false, G + (128 * M), forceAddress, 0);
+        } else {
+            test(4 * G - 128 * M, false, 2 * 128 * M, 0, 3);
+        }
         // add more...
 
+        // Compact Object Header Mode with tiny classpointers
+        // On all platforms we expect the VM to chose the smallest possible shift value needed to cover the encoding range
+        long forceAddress = 30 * G;
+
+        long ccsSize = 128 * M;
+        int expectedShift = 6;
+        test(forceAddress, true, ccsSize, forceAddress, expectedShift);
+
+        ccsSize = 512 * M;
+        expectedShift = 8;
+        test(forceAddress, true, ccsSize, forceAddress, expectedShift);
+
+        ccsSize = G;
+        expectedShift = 9;
+        test(forceAddress, true, ccsSize, forceAddress, expectedShift);
+
+        ccsSize = 3 * G;
+        expectedShift = 10;
+        test(forceAddress, true, ccsSize, forceAddress, expectedShift);
     }
 }
diff a/test/hotspot/jtreg/runtime/FieldLayout/ArrayBaseOffsets.java b/test/hotspot/jtreg/runtime/FieldLayout/ArrayBaseOffsets.java
--- a/test/hotspot/jtreg/runtime/FieldLayout/ArrayBaseOffsets.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright Amazon.com Inc. 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.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/*
- * @test id=with-coops-no-ccp
- * @library /test/lib
- * @requires vm.bits == "64"
- * @modules java.base/jdk.internal.misc
- * @run main/othervm -XX:+UseCompressedOops -XX:-UseCompressedClassPointers ArrayBaseOffsets
- */
-/*
- * @test id=with-coops-with-ccp
- * @library /test/lib
- * @requires vm.bits == "64"
- * @requires vm.opt.UseCompressedClassPointers != false
- * @modules java.base/jdk.internal.misc
- * @run main/othervm -XX:+UseCompressedOops -XX:+UseCompressedClassPointers ArrayBaseOffsets
- */
-/*
- * @test id=no-coops-no-ccp
- * @library /test/lib
- * @requires vm.bits == "64"
- * @modules java.base/jdk.internal.misc
- * @run main/othervm -XX:-UseCompressedOops -XX:-UseCompressedClassPointers ArrayBaseOffsets
- */
-/*
- * @test id=no-coops-with-ccp
- * @library /test/lib
- * @requires vm.bits == "64"
- * @requires vm.opt.UseCompressedClassPointers != false
- * @modules java.base/jdk.internal.misc
- * @run main/othervm -XX:-UseCompressedOops -XX:+UseCompressedClassPointers ArrayBaseOffsets
- */
-/*
- * @test id=32bit
- * @library /test/lib
- * @requires vm.bits == "32"
- * @modules java.base/jdk.internal.misc
- * @run main/othervm ArrayBaseOffsets
- */
-
-import jdk.internal.misc.Unsafe;
-
-import java.lang.management.ManagementFactory;
-import java.lang.management.RuntimeMXBean;
-import java.util.List;
-
-import jdk.test.lib.Asserts;
-import jdk.test.lib.Platform;
-
-public class ArrayBaseOffsets {
-
-    private static final boolean COOP;
-    private static final boolean CCP;
-
-    static {
-        if (Platform.is64bit()) {
-            RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
-            List<String> vmargs = runtime.getInputArguments();
-            CCP = !vmargs.contains("-XX:-UseCompressedClassPointers");
-            COOP = System.getProperty("java.vm.compressedOopsMode") != null;
-        } else {
-            COOP = CCP = false;
-        }
-    }
-
-    static public void main(String[] args) {
-        Unsafe unsafe = Unsafe.getUnsafe();
-        int intOffset, longOffset;
-        if (Platform.is64bit()) {
-            if (CCP) {
-                intOffset = 16;
-                longOffset = 16;
-            } else {
-                intOffset = 20;
-                longOffset = 24;
-            }
-        } else {
-            intOffset = 12;
-            longOffset = 16;
-        }
-        Asserts.assertEquals(unsafe.arrayBaseOffset(boolean[].class), intOffset,  "Misplaced boolean array base");
-        Asserts.assertEquals(unsafe.arrayBaseOffset(byte[].class),    intOffset,  "Misplaced byte    array base");
-        Asserts.assertEquals(unsafe.arrayBaseOffset(char[].class),    intOffset,  "Misplaced char    array base");
-        Asserts.assertEquals(unsafe.arrayBaseOffset(short[].class),   intOffset,  "Misplaced short   array base");
-        Asserts.assertEquals(unsafe.arrayBaseOffset(int[].class),     intOffset,  "Misplaced int     array base");
-        Asserts.assertEquals(unsafe.arrayBaseOffset(long[].class),    longOffset, "Misplaced long    array base");
-        Asserts.assertEquals(unsafe.arrayBaseOffset(float[].class),   intOffset,  "Misplaced float   array base");
-        Asserts.assertEquals(unsafe.arrayBaseOffset(double[].class),  longOffset, "Misplaced double  array base");
-        int expectedObjArrayOffset = (COOP || !Platform.is64bit()) ? intOffset : longOffset;
-        Asserts.assertEquals(unsafe.arrayBaseOffset(Object[].class),  expectedObjArrayOffset, "Misplaced object  array base");
-    }
-}
diff a/test/hotspot/jtreg/runtime/FieldLayout/BaseOffsets.java b/test/hotspot/jtreg/runtime/FieldLayout/BaseOffsets.java
--- /dev/null
+++ b/test/hotspot/jtreg/runtime/FieldLayout/BaseOffsets.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2022, 2023, 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test id=with-coops-with-ccp
+ * @library /test/lib /
+ * @requires vm.bits == "64"
+ * @modules java.base/jdk.internal.misc
+ *          java.management
+ * @build jdk.test.whitebox.WhiteBox
+ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
+ * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:+UnlockExperimentalVMOptions -XX:+UseCompressedOops -XX:+UseCompressedClassPointers -XX:-UseCompactObjectHeaders BaseOffsets
+ */
+/*
+ * @test id=no-coops-with-ccp
+ * @library /test/lib /
+ * @requires vm.bits == "64"
+ * @modules java.base/jdk.internal.misc
+ *          java.management
+ * @build jdk.test.whitebox.WhiteBox
+ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
+ * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:+UnlockExperimentalVMOptions -XX:-UseCompressedOops -XX:+UseCompressedClassPointers -XX:-UseCompactObjectHeaders BaseOffsets
+ */
+/*
+ * @test id=with-coops-no-ccp
+ * @library /test/lib /
+ * @requires vm.bits == "64"
+ * @modules java.base/jdk.internal.misc
+ *          java.management
+ * @build jdk.test.whitebox.WhiteBox
+ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
+ * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:+UnlockExperimentalVMOptions -XX:+UseCompressedOops -XX:-UseCompressedClassPointers -XX:-UseCompactObjectHeaders BaseOffsets
+ */
+/*
+ * @test id=no-coops-no-ccp
+ * @library /test/lib /
+ * @requires vm.bits == "64"
+ * @modules java.base/jdk.internal.misc
+ *          java.management
+ * @build jdk.test.whitebox.WhiteBox
+ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
+ * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:+UnlockExperimentalVMOptions -XX:-UseCompressedOops -XX:-UseCompressedClassPointers -XX:-UseCompactObjectHeaders BaseOffsets
+ */
+/*
+ * @test id=with-coop--with-coh
+ * @library /test/lib /
+ * @requires vm.bits == "64"
+ * @modules java.base/jdk.internal.misc
+ *          java.management
+ * @build jdk.test.whitebox.WhiteBox
+ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
+ * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:+UnlockExperimentalVMOptions -XX:+UseCompressedOops -XX:+UseCompactObjectHeaders BaseOffsets
+ */
+/*
+ * @test id=no-coops-with-coh
+ * @library /test/lib /
+ * @requires vm.bits == "64"
+ * @modules java.base/jdk.internal.misc
+ *          java.management
+ * @build jdk.test.whitebox.WhiteBox
+ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
+ * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:+UnlockExperimentalVMOptions -XX:-UseCompressedOops -XX:+UseCompactObjectHeaders BaseOffsets
+ */
+/*
+ * @test id=32bit
+ * @library /test/lib /
+ * @requires vm.bits == "32"
+ * @modules java.base/jdk.internal.misc
+ *          java.management
+ * @build jdk.test.whitebox.WhiteBox
+ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
+ * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI BaseOffsets
+ */
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Comparator;
+import jdk.internal.misc.Unsafe;
+
+import jdk.test.lib.Asserts;
+import jdk.test.lib.Platform;
+import jdk.test.whitebox.WhiteBox;
+
+public class BaseOffsets {
+
+    static class LIClass {
+        public int i;
+    }
+
+    public static final WhiteBox WB = WhiteBox.getWhiteBox();
+
+    static final long INT_OFFSET;
+    static final int  INT_ARRAY_OFFSET;
+    static final int  LONG_ARRAY_OFFSET;
+    static {
+        if (!Platform.is64bit() || WB.getBooleanVMFlag("UseCompactObjectHeaders")) {
+            INT_OFFSET = 8;
+            INT_ARRAY_OFFSET = 12;
+            LONG_ARRAY_OFFSET = 16;
+        } else if (WB.getBooleanVMFlag("UseCompressedClassPointers")) {
+            INT_OFFSET = 12;
+            INT_ARRAY_OFFSET = 16;
+            LONG_ARRAY_OFFSET = 16;
+        } else {
+            INT_OFFSET = 16;
+            INT_ARRAY_OFFSET = 20;
+            LONG_ARRAY_OFFSET = 24;
+        }
+    }
+
+    static public void main(String[] args) {
+        Unsafe unsafe = Unsafe.getUnsafe();
+        Class c = LIClass.class;
+        Field[] fields = c.getFields();
+        for (int i = 0; i < fields.length; i++) {
+            long offset = unsafe.objectFieldOffset(fields[i]);
+            if (fields[i].getType() == int.class) {
+                Asserts.assertEquals(offset, INT_OFFSET, "Misplaced int field");
+            } else {
+                Asserts.fail("Unexpected field type");
+            }
+        }
+
+        Asserts.assertEquals(unsafe.arrayBaseOffset(boolean[].class), INT_ARRAY_OFFSET,  "Misplaced boolean array base");
+        Asserts.assertEquals(unsafe.arrayBaseOffset(byte[].class),    INT_ARRAY_OFFSET,  "Misplaced byte    array base");
+        Asserts.assertEquals(unsafe.arrayBaseOffset(char[].class),    INT_ARRAY_OFFSET,  "Misplaced char    array base");
+        Asserts.assertEquals(unsafe.arrayBaseOffset(short[].class),   INT_ARRAY_OFFSET,  "Misplaced short   array base");
+        Asserts.assertEquals(unsafe.arrayBaseOffset(int[].class),     INT_ARRAY_OFFSET,  "Misplaced int     array base");
+        Asserts.assertEquals(unsafe.arrayBaseOffset(long[].class),    LONG_ARRAY_OFFSET, "Misplaced long    array base");
+        Asserts.assertEquals(unsafe.arrayBaseOffset(float[].class),   INT_ARRAY_OFFSET,  "Misplaced float   array base");
+        Asserts.assertEquals(unsafe.arrayBaseOffset(double[].class),  LONG_ARRAY_OFFSET, "Misplaced double  array base");
+        boolean narrowOops = System.getProperty("java.vm.compressedOopsMode") != null ||
+                             !Platform.is64bit();
+        int expected_objary_offset = narrowOops ? INT_ARRAY_OFFSET : LONG_ARRAY_OFFSET;
+        Asserts.assertEquals(unsafe.arrayBaseOffset(Object[].class),  expected_objary_offset, "Misplaced object  array base");
+    }
+}
diff a/test/hotspot/jtreg/runtime/FieldLayout/OldLayoutCheck.java b/test/hotspot/jtreg/runtime/FieldLayout/OldLayoutCheck.java
--- a/test/hotspot/jtreg/runtime/FieldLayout/OldLayoutCheck.java
+++ b/test/hotspot/jtreg/runtime/FieldLayout/OldLayoutCheck.java
@@ -23,45 +23,61 @@
 
 /*
  * @test
  * @bug 8239014
  * @summary -XX:-UseEmptySlotsInSupers sometime fails to reproduce the layout of the old code
- * @library /test/lib
+ * @library /test/lib /
  * @modules java.base/jdk.internal.misc
  *          java.management
  * @requires vm.bits == "64" & vm.opt.final.UseCompressedOops == true & vm.gc != "Z"
- * @run main/othervm -XX:+UseCompressedClassPointers -XX:-UseEmptySlotsInSupers OldLayoutCheck
+ * @build jdk.test.whitebox.WhiteBox
+ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
+ * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:+UseCompressedClassPointers -XX:-UseEmptySlotsInSupers OldLayoutCheck
  */
 
 /*
  * @test
  * @requires vm.bits == "32"
- * @library /test/lib
+ * @library /test/lib /
  * @modules java.base/jdk.internal.misc
  *          java.management
- * @run main/othervm -XX:-UseEmptySlotsInSupers OldLayoutCheck
+ * @build jdk.test.whitebox.WhiteBox
+ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
+ * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:-UseEmptySlotsInSupers OldLayoutCheck
  */
 
 import java.lang.reflect.Field;
 import java.util.Arrays;
 import java.util.Comparator;
 import jdk.internal.misc.Unsafe;
 
 import jdk.test.lib.Asserts;
 import jdk.test.lib.Platform;
+import jdk.test.whitebox.WhiteBox;
 
 public class OldLayoutCheck {
 
     static class LIClass {
         public long l;
         public int i;
     }
 
-    // 32-bit VMs: @0:  8 byte header,  @8: long field, @16:  int field
-    // 64-bit VMs: @0: 12 byte header, @12:  int field, @16: long field
-    static final long INT_OFFSET  = Platform.is64bit() ? 12L : 16L;
-    static final long LONG_OFFSET = Platform.is64bit() ? 16L :  8L;
+    public static final WhiteBox WB = WhiteBox.getWhiteBox();
+
+    // 32-bit VMs/compact headers: @0:  8 byte header,  @8: long field, @16:  int field
+    // 64-bit VMs:                 @0: 12 byte header, @12:  int field, @16: long field
+    static final long INT_OFFSET;
+    static final long LONG_OFFSET;
+    static {
+      if (!Platform.is64bit() || WB.getBooleanVMFlag("UseCompactObjectHeaders")) {
+        INT_OFFSET = 16L;
+        LONG_OFFSET = 8L;
+      } else {
+        INT_OFFSET = 12L;
+        LONG_OFFSET = 16L;
+      }
+    }
 
     static public void main(String[] args) {
         Unsafe unsafe = Unsafe.getUnsafe();
         Class c = LIClass.class;
         Field[] fields = c.getFields();
diff a/test/hotspot/jtreg/runtime/cds/TestDefaultArchiveLoading.java b/test/hotspot/jtreg/runtime/cds/TestDefaultArchiveLoading.java
--- /dev/null
+++ b/test/hotspot/jtreg/runtime/cds/TestDefaultArchiveLoading.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2024, Red Hat, Inc. 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test id=nocoops_nocoh
+ * @summary Test Loading of default archives in all configurations
+ * @requires vm.cds
+ * @requires vm.bits == 64
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.management
+ * @run driver TestDefaultArchiveLoading nocoops_nocoh
+ */
+
+/**
+ * @test id=nocoops_coh
+ * @summary Test Loading of default archives in all configurations (requires --enable-cds-archive-coh)
+ * @requires vm.cds
+ * @requires vm.bits == 64
+ * @requires !vm.gc.ZGenerational
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.management
+ * @run driver TestDefaultArchiveLoading nocoops_coh
+ */
+
+/**
+ * @test id=coops_nocoh
+ * @summary Test Loading of default archives in all configurations
+ * @requires vm.cds
+ * @requires vm.bits == 64
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.management
+ * @run driver TestDefaultArchiveLoading coops_nocoh
+ */
+
+/**
+ * @test id=coops_coh
+ * @summary Test Loading of default archives in all configurations (requires --enable-cds-archive-coh)
+ * @requires vm.cds
+ * @requires vm.bits == 64
+ * @requires !vm.gc.ZGenerational
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.management
+ * @run driver TestDefaultArchiveLoading coops_coh
+ */
+
+import jdk.test.lib.Platform;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+import jtreg.SkippedException;
+
+public class TestDefaultArchiveLoading {
+    public static void main(String[] args) throws Exception {
+
+        if (args.length != 1) {
+            throw new RuntimeException("Expected argument");
+        }
+
+        String archiveSuffix;
+        char coh, coops;
+
+        switch (args[0]) {
+            case "nocoops_nocoh":
+                coh = coops = '-';
+                archiveSuffix = "_nocoops";
+                break;
+            case "nocoops_coh":
+                coops = '-';
+                coh = '+';
+                archiveSuffix = "_nocoops_coh";
+                break;
+            case "coops_nocoh":
+                coops = '+';
+                coh = '-';
+                archiveSuffix = "";
+                break;
+            case "coops_coh":
+                coh = coops = '+';
+                archiveSuffix = "_coh";
+                break;
+            default: throw new RuntimeException("Invalid argument " + args[0]);
+        }
+
+        ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(
+                "-XX:+UnlockExperimentalVMOptions",
+                "-XX:" + coh + "UseCompactObjectHeaders",
+                "-XX:" + coops + "UseCompressedOops",
+                "-Xlog:cds",
+                "-Xshare:on", // fail if we cannot load archive
+                "-version");
+
+        OutputAnalyzer output = new OutputAnalyzer(pb.start());
+        output.shouldHaveExitValue(0);
+
+        output.shouldContain("classes" + archiveSuffix + ".jsa");
+
+    }
+}
diff a/test/hotspot/jtreg/runtime/cds/appcds/TestZGCWithCDS.java b/test/hotspot/jtreg/runtime/cds/appcds/TestZGCWithCDS.java
--- a/test/hotspot/jtreg/runtime/cds/appcds/TestZGCWithCDS.java
+++ b/test/hotspot/jtreg/runtime/cds/appcds/TestZGCWithCDS.java
@@ -54,26 +54,31 @@
     public final static String HELLO = "Hello World";
     public final static String UNABLE_TO_USE_ARCHIVE = "Unable to use shared archive.";
     public final static String ERR_MSG = "The saved state of UseCompressedOops and UseCompressedClassPointers is different from runtime, CDS will be disabled.";
     public static void main(String... args) throws Exception {
          String zGenerational = args[0];
+         String compactHeaders = "-XX:" + (zGenerational.equals("-XX:+ZGenerational") ? "+" : "-") + "UseCompactObjectHeaders";
          String helloJar = JarBuilder.build("hello", "Hello");
          System.out.println("0. Dump with ZGC");
          OutputAnalyzer out = TestCommon
                                   .dump(helloJar,
                                         new String[] {"Hello"},
                                         "-XX:+UseZGC",
                                         zGenerational,
+                                        "-XX:+UnlockExperimentalVMOptions",
+                                        compactHeaders,
                                         "-Xlog:cds");
          out.shouldContain("Dumping shared data to file:");
          out.shouldHaveExitValue(0);
 
          System.out.println("1. Run with same args of dump");
          out = TestCommon
                    .exec(helloJar,
                          "-XX:+UseZGC",
                          zGenerational,
+                         "-XX:+UnlockExperimentalVMOptions",
+                         compactHeaders,
                          "-Xlog:cds",
                          "Hello");
          out.shouldContain(HELLO);
          out.shouldHaveExitValue(0);
 
@@ -81,10 +86,12 @@
          out = TestCommon
                    .exec(helloJar,
                          "-XX:-UseZGC",
                          "-XX:+UseCompressedOops",           // in case turned off by vmoptions
                          "-XX:+UseCompressedClassPointers",  // by jtreg
+                         "-XX:+UnlockExperimentalVMOptions",
+                         compactHeaders,
                          "-Xlog:cds",
                          "Hello");
          out.shouldContain(UNABLE_TO_USE_ARCHIVE);
          out.shouldContain(ERR_MSG);
          out.shouldHaveExitValue(1);
@@ -93,10 +100,12 @@
          out = TestCommon
                    .exec(helloJar,
                          "-XX:+UseSerialGC",
                          "-XX:-UseCompressedOops",
                          "-XX:-UseCompressedClassPointers",
+                         "-XX:+UnlockExperimentalVMOptions",
+                         compactHeaders,
                          "-Xlog:cds",
                          "Hello");
          out.shouldContain(UNABLE_TO_USE_ARCHIVE);
          out.shouldContain(ERR_MSG);
          out.shouldHaveExitValue(1);
@@ -105,10 +114,12 @@
          out = TestCommon
                    .exec(helloJar,
                          "-XX:+UseSerialGC",
                          "-XX:-UseCompressedOops",
                          "-XX:+UseCompressedClassPointers",
+                         "-XX:+UnlockExperimentalVMOptions",
+                         compactHeaders,
                          "-Xlog:cds",
                          "Hello");
          out.shouldContain(HELLO);
          out.shouldHaveExitValue(0);
 
@@ -116,10 +127,12 @@
          out = TestCommon
                    .exec(helloJar,
                          "-XX:+UseSerialGC",
                          "-XX:+UseCompressedOops",
                          "-XX:-UseCompressedClassPointers",
+                         "-XX:+UnlockExperimentalVMOptions",
+                         compactHeaders,
                          "-Xlog:cds",
                          "Hello");
          out.shouldContain(UNABLE_TO_USE_ARCHIVE);
          out.shouldContain(ERR_MSG);
          out.shouldHaveExitValue(1);
@@ -128,10 +141,12 @@
          out = TestCommon
                    .exec(helloJar,
                          "-XX:+UseSerialGC",
                          "-XX:+UseCompressedOops",
                          "-XX:+UseCompressedClassPointers",
+                         "-XX:+UnlockExperimentalVMOptions",
+                         compactHeaders,
                          "-Xlog:cds",
                          "Hello");
          out.shouldContain(UNABLE_TO_USE_ARCHIVE);
          out.shouldContain(ERR_MSG);
          out.shouldHaveExitValue(1);
@@ -141,19 +156,23 @@
                    .dump(helloJar,
                          new String[] {"Hello"},
                          "-XX:+UseSerialGC",
                          "-XX:-UseCompressedOops",
                          "-XX:+UseCompressedClassPointers",
+                         "-XX:+UnlockExperimentalVMOptions",
+                         compactHeaders,
                          "-Xlog:cds");
          out.shouldContain("Dumping shared data to file:");
          out.shouldHaveExitValue(0);
 
          System.out.println("8. Run with ZGC");
          out = TestCommon
                    .exec(helloJar,
                          "-XX:+UseZGC",
                          zGenerational,
+                         "-XX:+UnlockExperimentalVMOptions",
+                         compactHeaders,
                          "-Xlog:cds",
                          "Hello");
          out.shouldContain(HELLO);
          out.shouldHaveExitValue(0);
     }
diff a/test/hotspot/jtreg/runtime/cds/appcds/loaderConstraints/DynamicLoaderConstraintsTest.java b/test/hotspot/jtreg/runtime/cds/appcds/loaderConstraints/DynamicLoaderConstraintsTest.java
--- a/test/hotspot/jtreg/runtime/cds/appcds/loaderConstraints/DynamicLoaderConstraintsTest.java
+++ b/test/hotspot/jtreg/runtime/cds/appcds/loaderConstraints/DynamicLoaderConstraintsTest.java
@@ -165,11 +165,11 @@
             if (useCustomLoader) {
                 if (useZGC) {
                     String zGenerational = "-XX:" + (useZGenerational ? "+" : "-") + "ZGenerational";
                     // Add options to force eager class unloading.
                     cmdLine = TestCommon.concat(cmdLine, "-cp", loaderJar,
-                                                "-XX:+UseZGC", zGenerational, "-XX:ZCollectionInterval=0.01",
+                                                "-XX:+UseZGC", zGenerational, "-XX:ZCollectionInterval=0.01", "-XX:+UnlockExperimentalVMOptions", "-XX:-UseCompactObjectHeaders",
                                                 loaderMainClass, appJar);
                     setBaseArchiveOptions("-XX:+UseZGC", "-Xlog:cds");
                 } else {
                     cmdLine = TestCommon.concat(cmdLine, "-cp", loaderJar,
                                                 loaderMainClass, appJar);
diff a/test/jdk/java/lang/instrument/GetObjectSizeIntrinsicsTest.java b/test/jdk/java/lang/instrument/GetObjectSizeIntrinsicsTest.java
--- a/test/jdk/java/lang/instrument/GetObjectSizeIntrinsicsTest.java
+++ b/test/jdk/java/lang/instrument/GetObjectSizeIntrinsicsTest.java
@@ -299,10 +299,11 @@
 import jdk.test.lib.Platform;
 import jdk.test.whitebox.WhiteBox;
 
 public class GetObjectSizeIntrinsicsTest extends ASimpleInstrumentationTestCase {
 
+    private static final boolean COMPACT_HEADERS = Platform.is64bit() && WhiteBox.getWhiteBox().getBooleanVMFlag("UseCompactObjectHeaders");
     static final Boolean COMPRESSED_OOPS = WhiteBox.getWhiteBox().getBooleanVMFlag("UseCompressedOops");
     static final long REF_SIZE = (COMPRESSED_OOPS == null || COMPRESSED_OOPS == true) ? 4 : 8;
 
     static final Long align = WhiteBox.getWhiteBox().getIntVMFlag("ObjectAlignmentInBytes");
     static final int OBJ_ALIGN = (align == null ? 8 : align.intValue());
@@ -372,29 +373,39 @@
 
     private static long roundUp(long v, long a) {
         return (v + a - 1) / a * a;
     }
 
+    private static long expectedSmallObjSize() {
+        long size;
+        if (!Platform.is64bit() || COMPACT_HEADERS) {
+            size = 8;
+        } else {
+            size = 16;
+        }
+        return roundUp(size, OBJ_ALIGN);
+    }
+
     private void testSize_newObject() {
-        long expected = roundUp(Platform.is64bit() ? 16 : 8, OBJ_ALIGN);
+        long expected = expectedSmallObjSize();
         for (int c = 0; c < ITERS; c++) {
             assertEquals(expected, fInst.getObjectSize(new Object()));
         }
     }
 
     private void testSize_localObject() {
-        long expected = roundUp(Platform.is64bit() ? 16 : 8, OBJ_ALIGN);
+        long expected = expectedSmallObjSize();
         Object o = new Object();
         for (int c = 0; c < ITERS; c++) {
             assertEquals(expected, fInst.getObjectSize(o));
         }
     }
 
     static Object staticO = new Object();
 
     private void testSize_fieldObject() {
-        long expected = roundUp(Platform.is64bit() ? 16 : 8, OBJ_ALIGN);
+        long expected = expectedSmallObjSize();
         for (int c = 0; c < ITERS; c++) {
             assertEquals(expected, fInst.getObjectSize(staticO));
         }
     }
 
diff a/test/micro/org/openjdk/bench/vm/lang/LockUnlock.java b/test/micro/org/openjdk/bench/vm/lang/LockUnlock.java
--- a/test/micro/org/openjdk/bench/vm/lang/LockUnlock.java
+++ b/test/micro/org/openjdk/bench/vm/lang/LockUnlock.java
@@ -52,37 +52,88 @@
     @Param("100")
     private int innerCount;
 
     public Object lockObject1;
     public Object lockObject2;
+    public volatile Object lockObject3Inflated;
+    public volatile Object lockObject4Inflated;
     public int factorial;
     public int dummyInt1;
     public int dummyInt2;
 
     @Setup
     public void setup() {
         lockObject1 = new Object();
         lockObject2 = new Object();
+        lockObject3Inflated = new Object();
+        lockObject4Inflated = new Object();
+
+        // Inflate the lock to use an ObjectMonitor
+        try {
+          synchronized (lockObject3Inflated) {
+            lockObject3Inflated.wait(1);
+          }
+          synchronized (lockObject4Inflated) {
+            lockObject4Inflated.wait(1);
+          }
+        } catch (InterruptedException e) {
+          throw new RuntimeException(e);
+        }
+
         dummyInt1 = 47;
         dummyInt2 = 11; // anything
     }
 
     /** Perform a synchronized on a local object within a loop. */
     @Benchmark
-    public void testSimpleLockUnlock() {
+    public void testBasicSimpleLockUnlockLocal() {
         Object localObject = lockObject1;
         for (int i = 0; i < innerCount; i++) {
             synchronized (localObject) {
                 dummyInt1++;
                 dummyInt2++;
             }
         }
     }
 
+    /** Perform a synchronized on an object within a loop. */
+    @Benchmark
+    public void testBasicSimpleLockUnlock() {
+        for (int i = 0; i < innerCount; i++) {
+            synchronized (lockObject1) {
+                dummyInt1++;
+                dummyInt2++;
+            }
+        }
+    }
+
+    /** Perform a synchronized on a local object within a loop. */
+    @Benchmark
+    public void testInflatedSimpleLockUnlockLocal() {
+        Object localObject = lockObject3Inflated;
+        for (int i = 0; i < innerCount; i++) {
+            synchronized (localObject) {
+                dummyInt1++;
+                dummyInt2++;
+            }
+        }
+    }
+
+    /** Perform a synchronized on an object within a loop. */
+    @Benchmark
+    public void testInflatedSimpleLockUnlock() {
+        for (int i = 0; i < innerCount; i++) {
+            synchronized (lockObject3Inflated) {
+                dummyInt1++;
+                dummyInt2++;
+            }
+        }
+    }
+
     /** Perform a recursive synchronized on a local object within a loop. */
     @Benchmark
-    public void testRecursiveLockUnlock() {
+    public void testBasicRecursiveLockUnlockLocal() {
         Object localObject = lockObject1;
         for (int i = 0; i < innerCount; i++) {
             synchronized (localObject) {
                 synchronized (localObject) {
                     dummyInt1++;
@@ -90,13 +141,26 @@
                 }
             }
         }
     }
 
+    /** Perform a recursive synchronized on an object within a loop. */
+    @Benchmark
+    public void testBasicRecursiveLockUnlock() {
+        for (int i = 0; i < innerCount; i++) {
+            synchronized (lockObject1) {
+                synchronized (lockObject1) {
+                    dummyInt1++;
+                    dummyInt2++;
+                }
+            }
+        }
+    }
+
     /** Perform two synchronized after each other on the same local object. */
     @Benchmark
-    public void testSerialLockUnlock() {
+    public void testBasicSerialLockUnlockLocal() {
         Object localObject = lockObject1;
         for (int i = 0; i < innerCount; i++) {
             synchronized (localObject) {
                 dummyInt1++;
             }
@@ -104,10 +168,130 @@
                 dummyInt2++;
             }
         }
     }
 
+  /** Perform two synchronized after each other on the same object. */
+    @Benchmark
+    public void testBasicSerialLockUnlock() {
+        for (int i = 0; i < innerCount; i++) {
+            synchronized (lockObject1) {
+                dummyInt1++;
+            }
+            synchronized (lockObject1) {
+                dummyInt2++;
+            }
+        }
+    }
+
+    /** Perform two synchronized after each other on the same local object. */
+    @Benchmark
+    public void testInflatedSerialLockUnlockLocal() {
+        Object localObject = lockObject3Inflated;
+        for (int i = 0; i < innerCount; i++) {
+            synchronized (localObject) {
+                dummyInt1++;
+            }
+            synchronized (localObject) {
+                dummyInt2++;
+            }
+        }
+    }
+
+  /** Perform two synchronized after each other on the same object. */
+    @Benchmark
+    public void testInflatedSerialLockUnlock() {
+        for (int i = 0; i < innerCount; i++) {
+            synchronized (lockObject3Inflated) {
+                dummyInt1++;
+            }
+            synchronized (lockObject3Inflated) {
+                dummyInt2++;
+            }
+        }
+    }
+
+    /** Perform two synchronized after each other on the same object. */
+    @Benchmark
+    public void testInflatedMultipleSerialLockUnlock() {
+        for (int i = 0; i < innerCount; i++) {
+            synchronized (lockObject3Inflated) {
+                dummyInt1++;
+            }
+            synchronized (lockObject4Inflated) {
+                dummyInt2++;
+            }
+        }
+    }
+
+    /** Perform two synchronized after each other on the same object. */
+    @Benchmark
+    public void testInflatedMultipleRecursiveLockUnlock() {
+        for (int i = 0; i < innerCount; i++) {
+            synchronized (lockObject3Inflated) {
+                dummyInt1++;
+                synchronized (lockObject4Inflated) {
+                    dummyInt2++;
+                }
+            }
+        }
+    }
+
+      /** Perform a recursive-only synchronized on a local object within a loop. */
+    @Benchmark
+    public void testInflatedRecursiveOnlyLockUnlockLocal() {
+        Object localObject = lockObject3Inflated;
+        synchronized (localObject) {
+            for (int i = 0; i < innerCount; i++) {
+                synchronized (localObject) {
+                    dummyInt1++;
+                    dummyInt2++;
+                }
+            }
+        }
+    }
+
+    /** Perform a recursive-only synchronized on an object within a loop. */
+    @Benchmark
+    public void testInflatedRecursiveOnlyLockUnlock() {
+        synchronized (lockObject3Inflated) {
+            for (int i = 0; i < innerCount; i++) {
+                synchronized (lockObject3Inflated) {
+                    dummyInt1++;
+                    dummyInt2++;
+                }
+            }
+        }
+    }
+
+    /** Perform a recursive-only synchronized on a local object within a loop. */
+    @Benchmark
+    public void testInflatedRecursiveLockUnlockLocal() {
+        Object localObject = lockObject3Inflated;
+        for (int i = 0; i < innerCount; i++) {
+            synchronized (localObject) {
+                synchronized (localObject) {
+                    dummyInt1++;
+                    dummyInt2++;
+                }
+            }
+        }
+    }
+
+    /** Perform a recursive-only synchronized on an object within a loop. */
+    @Benchmark
+    public void testInflatedRecursiveLockUnlock() {
+        for (int i = 0; i < innerCount; i++) {
+            synchronized (lockObject3Inflated) {
+                synchronized (lockObject3Inflated) {
+                    dummyInt1++;
+                    dummyInt2++;
+                }
+            }
+        }
+    }
+
     /**
      * Performs recursive synchronizations on the same local object.
      * <p/>
      * Result is 3628800
      */
