diff a/.jcheck/conf b/.jcheck/conf
--- a/.jcheck/conf
+++ b/.jcheck/conf
@@ -1,7 +1,7 @@
 [general]
-project=jdk-updates
+project=lilliput
 jbs=JDK
 version=21.0.5
 
 [checks]
 error=author,committer,reviewers,merge,issues,executable,symlink,message,hg-tag,whitespace,problemlists
@@ -21,11 +21,11 @@
 
 [checks "merge"]
 message=Merge
 
 [checks "reviewers"]
-reviewers=1
+committers=1
 ignore=duke
 
 [checks "committer"]
 role=committer
 
diff a/make/Images.gmk b/make/Images.gmk
--- a/make/Images.gmk
+++ b/make/Images.gmk
@@ -121,14 +121,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)
@@ -171,10 +177,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/RunTests.gmk b/make/RunTests.gmk
--- a/make/RunTests.gmk
+++ b/make/RunTests.gmk
@@ -830,10 +830,15 @@
 
   ifneq ($$($1_JTREG_PROBLEM_LIST), )
     $1_JTREG_BASIC_OPTIONS += $$(addprefix $$(JTREG_PROBLEM_LIST_PREFIX), $$($1_JTREG_PROBLEM_LIST))
   endif
 
+  # Add more Lilliput-specific ProblemLists when UCOH is enabled
+  ifneq ($$(findstring -XX:+UseCompactObjectHeaders, $$(TEST_OPTS)), )
+    JTREG_EXTRA_PROBLEM_LISTS += $(TOPDIR)/test/hotspot/jtreg/ProblemList-lilliput.txt
+  endif
+
   ifneq ($$(JTREG_EXTRA_PROBLEM_LISTS), )
     # Accept both absolute paths as well as relative to the current test root.
     $1_JTREG_BASIC_OPTIONS += $$(addprefix $$(JTREG_PROBLEM_LIST_PREFIX), $$(wildcard \
         $$(JTREG_EXTRA_PROBLEM_LISTS) \
         $$(addprefix $$($1_TEST_ROOT)/, $$(JTREG_EXTRA_PROBLEM_LISTS)) \
diff a/make/autoconf/configure.ac b/make/autoconf/configure.ac
--- a/make/autoconf/configure.ac
+++ b/make/autoconf/configure.ac
@@ -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
@@ -682,10 +682,37 @@
         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])
+          AVAILABLE=true
+        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.in b/make/autoconf/spec.gmk.in
--- a/make/autoconf/spec.gmk.in
+++ b/make/autoconf/spec.gmk.in
@@ -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
@@ -7144,20 +7144,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));
@@ -16435,27 +16449,27 @@
 // ============================================================================
 // inlined locking and unlocking
 
 instruct cmpFastLock(rFlagsReg cr, iRegP object, iRegP box, iRegPNoSp tmp, iRegPNoSp tmp2, iRegPNoSp tmp3)
 %{
+  predicate(LockingMode != LM_LIGHTWEIGHT);
   match(Set cr (FastLock object box));
   effect(TEMP tmp, TEMP tmp2, TEMP tmp3);
 
-  // TODO
-  // identify correct cost
   ins_cost(5 * INSN_COST);
-  format %{ "fastlock $object,$box\t! kills $tmp,$tmp2" %}
+  format %{ "fastlock $object,$box\t! kills $tmp,$tmp2,$tmp3" %}
 
   ins_encode %{
     __ fast_lock($object$$Register, $box$$Register, $tmp$$Register, $tmp2$$Register, $tmp3$$Register);
   %}
 
   ins_pipe(pipe_serial);
 %}
 
 instruct cmpFastUnlock(rFlagsReg cr, iRegP object, iRegP box, iRegPNoSp tmp, iRegPNoSp tmp2)
 %{
+  predicate(LockingMode != LM_LIGHTWEIGHT);
   match(Set cr (FastUnlock object box));
   effect(TEMP tmp, TEMP tmp2);
 
   ins_cost(5 * INSN_COST);
   format %{ "fastunlock $object,$box\t! kills $tmp, $tmp2" %}
@@ -16465,10 +16479,41 @@
   %}
 
   ins_pipe(pipe_serial);
 %}
 
+instruct cmpFastLockLightweight(rFlagsReg cr, iRegP object, iRegP box, iRegPNoSp tmp, iRegPNoSp tmp2)
+%{
+  predicate(LockingMode == LM_LIGHTWEIGHT);
+  match(Set cr (FastLock object box));
+  effect(TEMP tmp, TEMP tmp2);
+
+  ins_cost(5 * INSN_COST);
+  format %{ "fastlock $object,$box\t! kills $tmp,$tmp2" %}
+
+  ins_encode %{
+    __ fast_lock_lightweight($object$$Register, $box$$Register, $tmp$$Register, $tmp2$$Register);
+  %}
+
+  ins_pipe(pipe_serial);
+%}
+
+instruct cmpFastUnlockLightweight(rFlagsReg cr, iRegP object, iRegP box, iRegPNoSp tmp, iRegPNoSp tmp2)
+%{
+  predicate(LockingMode == LM_LIGHTWEIGHT);
+  match(Set cr (FastUnlock object box));
+  effect(TEMP tmp, TEMP tmp2);
+
+  ins_cost(5 * INSN_COST);
+  format %{ "fastunlock $object,$box\t! kills $tmp, $tmp2" %}
+
+  ins_encode %{
+    __ fast_unlock_lightweight($object$$Register, $box$$Register, $tmp$$Register, $tmp2$$Register);
+  %}
+
+  ins_pipe(pipe_serial);
+%}
 
 // ============================================================================
 // Safepoint Instructions
 
 // TODO
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,10 +232,17 @@
   }
   __ adr(lr, _continuation);
   __ far_jump(RuntimeAddress(Runtime1::entry_for(exit_id)));
 }
 
+void LoadKlassStub::emit_code(LIR_Assembler* ce) {
+  assert(UseCompactObjectHeaders, "Only use with compact object headers");
+  __ bind(_entry);
+  Register d = _result->as_register();
+  __ ldr(d, Address(d, OM_OFFSET_NO_MONITOR_VALUE_TAG(header)));
+  __ 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:
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
@@ -1228,11 +1228,11 @@
     }
     __ allocate_array(op->obj()->as_register(),
                       len,
                       tmp1,
                       tmp2,
-                      arrayOopDesc::header_size(op->type()),
+                      arrayOopDesc::base_offset_in_bytes(op->type()),
                       array_element_size(op->type()),
                       op->klass()->as_register(),
                       *op->stub()->entry());
   }
   __ bind(*op->stub()->continuation());
@@ -2288,12 +2288,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());
   }
@@ -2350,19 +2348,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;
@@ -2480,40 +2470,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);
@@ -2591,11 +2559,22 @@
   if (info != nullptr) {
     add_debug_info_for_null_check_here(info);
   }
 
   if (UseCompressedClassPointers) {
-    __ ldrw(result, Address (obj, oopDesc::klass_offset_in_bytes()));
+    if (UseCompactObjectHeaders) {
+      // Check if we can take the (common) fast path, if obj is unlocked.
+      __ ldr(result, Address(obj, oopDesc::mark_offset_in_bytes()));
+      __ tst(result, markWord::monitor_value);
+      __ br(Assembler::NE, *op->stub()->entry());
+      __ bind(*op->stub()->continuation());
+
+      // Shift to get proper narrow Klass*.
+      __ 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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2014, 2021, 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
@@ -78,16 +78,16 @@
     ldrw(hdr, Address(hdr, Klass::access_flags_offset()));
     tstw(hdr, JVM_ACC_IS_VALUE_BASED_CLASS);
     br(Assembler::NE, slow_case);
   }
 
-  // Load object header
-  ldr(hdr, Address(obj, hdr_offset));
   if (LockingMode == LM_LIGHTWEIGHT) {
     lightweight_lock(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
     orr(hdr, hdr, markWord::unlocked_value);
     // save unlocked object header into the displaced header location on the stack
     str(hdr, Address(disp_hdr, 0));
     // test if object header is still the same (i.e. unlocked), and if so, store the
@@ -142,15 +142,10 @@
   // load object
   ldr(obj, Address(disp_hdr, BasicObjectLock::obj_offset()));
   verify_oop(obj);
 
   if (LockingMode == LM_LIGHTWEIGHT) {
-    ldr(hdr, Address(obj, oopDesc::mark_offset_in_bytes()));
-    // We cannot use tbnz here, the target might be too far away and cannot
-    // be encoded.
-    tst(hdr, markWord::monitor_value);
-    br(Assembler::NE, slow_case);
     lightweight_unlock(obj, hdr, temp, rscratch2, slow_case);
   } else if (LockingMode == LM_LEGACY) {
     // test if object header is pointing to the displaced header, and if so, restore
     // the displaced header in the object - if the object header is not pointing to
     // the displaced header, get the object header instead
@@ -178,24 +173,29 @@
   }
 }
 
 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()));
-  } else if (UseCompressedClassPointers) {
+  } else if (UseCompressedClassPointers && !UseCompactObjectHeaders) {
     store_klass_gap(obj, zr);
   }
 }
 
 // preserves obj, destroys len_in_bytes
@@ -269,11 +269,11 @@
     far_call(RuntimeAddress(Runtime1::entry_for(Runtime1::dtrace_object_alloc_id)));
   }
 
   verify_oop(obj);
 }
-void C1_MacroAssembler::allocate_array(Register obj, Register len, Register t1, Register t2, int header_size, int f, Register klass, Label& slow_case) {
+void C1_MacroAssembler::allocate_array(Register obj, Register len, Register t1, Register t2, int base_offset_in_bytes, int f, Register klass, Label& slow_case) {
   assert_different_registers(obj, len, t1, t2, klass);
 
   // determine alignment mask
   assert(!(BytesPerWord & 1), "must be a multiple of 2 for masking code to work");
 
@@ -282,20 +282,31 @@
   cmp(len, rscratch1);
   br(Assembler::HS, slow_case);
 
   const Register arr_size = t2; // okay to be the same
   // align object end
-  mov(arr_size, (int32_t)header_size * BytesPerWord + MinObjAlignmentInBytesMask);
+  mov(arr_size, (int32_t)base_offset_in_bytes + MinObjAlignmentInBytesMask);
   add(arr_size, arr_size, len, ext::uxtw, f);
   andr(arr_size, arr_size, ~MinObjAlignmentInBytesMask);
 
   try_allocate(obj, arr_size, 0, t1, t2, slow_case);
 
   initialize_header(obj, klass, len, t1, t2);
 
+  // Clear leading 4 bytes, if necessary.
+  // TODO: This could perhaps go into initialize_body() and also clear the leading 4 bytes
+  // for non-array objects, thereby replacing the klass-gap clearing code in initialize_header().
+  int base_offset = base_offset_in_bytes;
+  if (!is_aligned(base_offset, BytesPerWord)) {
+    assert(is_aligned(base_offset, BytesPerInt), "must be 4-byte aligned");
+    strw(zr, Address(obj, base_offset));
+    base_offset += BytesPerInt;
+  }
+  assert(is_aligned(base_offset, BytesPerWord), "must be word-aligned");
+
   // clear rest of allocated space
-  initialize_body(obj, arr_size, header_size * BytesPerWord, t1, t2);
+  initialize_body(obj, arr_size, base_offset, t1, t2);
   if (Compilation::current()->bailed_out()) {
     return;
   }
 
   membar(StoreStore);
@@ -310,13 +321,11 @@
 
 
 void C1_MacroAssembler::inline_cache_check(Register receiver, Register iCache) {
   verify_oop(receiver);
   // explicit null check not needed since load from [klass_offset] causes a trap
-  // check against inline cache
-  assert(!MacroAssembler::needs_explicit_null_check(oopDesc::klass_offset_in_bytes()), "must add explicit null check");
-
+  // check against inline cache. This is checked in Universe::genesis()..
   cmp_klass(receiver, iCache, rscratch1);
 }
 
 
 void C1_MacroAssembler::build_frame(int framesize, int bang_size_in_bytes) {
diff a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.hpp
--- a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.hpp
+++ b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.hpp
@@ -98,11 +98,11 @@
   // len        : array length in number of elements
   // t          : scratch register - contents destroyed
   // header_size: size of object header in words
   // f          : element scale factor
   // slow_case  : exit to slow case implementation if fast allocation fails
-  void allocate_array(Register obj, Register len, Register t, Register t2, int header_size, int f, Register klass, Label& slow_case);
+  void allocate_array(Register obj, Register len, Register t, Register t2, int base_offset_in_bytes, int f, Register klass, Label& slow_case);
 
   int  rsp_offset() const { return _rsp_offset; }
   void set_rsp_offset(int n) { _rsp_offset = n; }
 
   void invalidate_registers(bool inv_r0, bool inv_r19, bool inv_r2, bool inv_r3, bool inv_r4, bool inv_r5) PRODUCT_RETURN;
diff a/src/hotspot/cpu/aarch64/c2_CodeStubs_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_CodeStubs_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/c2_CodeStubs_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/c2_CodeStubs_aarch64.cpp
@@ -89,6 +89,17 @@
   __ strw(t, Address(rthread, JavaThread::lock_stack_top_offset()));
 
   __ b(continuation());
 }
 
+int C2LoadNKlassStub::max_size() const {
+  return 8;
+}
+
+void C2LoadNKlassStub::emit(C2_MacroAssembler& masm) {
+  __ bind(entry());
+  Register d = dst();
+  __ ldr(d, Address(d, OM_OFFSET_NO_MONITOR_VALUE_TAG(header)));
+  __ b(continuation());
+}
+
 #undef __
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
@@ -30,10 +30,11 @@
 #include "opto/intrinsicnode.hpp"
 #include "opto/matcher.hpp"
 #include "opto/output.hpp"
 #include "opto/subnode.hpp"
 #include "runtime/stubRoutines.hpp"
+#include "utilities/globalDefinitions.hpp"
 
 #ifdef PRODUCT
 #define BLOCK_COMMENT(str) /* nothing */
 #define STOP(error) stop(error)
 #else
@@ -53,10 +54,11 @@
   Register tmp = tmp2Reg;
   Label cont;
   Label object_has_monitor;
   Label count, no_count;
 
+  assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking should use fast_lock_lightweight");
   assert_different_registers(oop, box, tmp, disp_hdr);
 
   // Load markWord from object into displaced_header.
   ldr(disp_hdr, Address(oop, oopDesc::mark_offset_in_bytes()));
 
@@ -71,11 +73,12 @@
   tbnz(disp_hdr, exact_log2(markWord::monitor_value), object_has_monitor);
 
   if (LockingMode == LM_MONITOR) {
     tst(oop, oop); // Set NE to indicate 'failure' -> take slow-path. We know that oop != 0.
     b(cont);
-  } else if (LockingMode == LM_LEGACY) {
+  } else {
+    assert(LockingMode == LM_LEGACY, "must be");
     // Set tmp to be (markWord of object | UNLOCK_VALUE).
     orr(tmp, disp_hdr, markWord::unlocked_value);
 
     // Initialize the box. (Must happen before we update the object mark!)
     str(tmp, Address(box, BasicLock::displaced_header_offset_in_bytes()));
@@ -100,14 +103,10 @@
     // If condition is true we are cont and hence we can store 0 as the
     // displaced header in the box, which indicates that it is a recursive lock.
     ands(tmp/*==0?*/, disp_hdr, tmp);   // Sets flags for result
     str(tmp/*==0, perhaps*/, Address(box, BasicLock::displaced_header_offset_in_bytes()));
     b(cont);
-  } else {
-    assert(LockingMode == LM_LIGHTWEIGHT, "must be");
-    lightweight_lock(oop, disp_hdr, tmp, tmp3Reg, no_count);
-    b(count);
   }
 
   // Handle existing monitor.
   bind(object_has_monitor);
 
@@ -117,18 +116,17 @@
   // Try to CAS m->owner from NULL to current thread.
   add(tmp, disp_hdr, (in_bytes(ObjectMonitor::owner_offset())-markWord::monitor_value));
   cmpxchg(tmp, zr, rthread, Assembler::xword, /*acquire*/ true,
           /*release*/ true, /*weak*/ false, rscratch1); // Sets flags for result
 
-  if (LockingMode != LM_LIGHTWEIGHT) {
-    // Store a non-null value into the box to avoid looking like a re-entrant
-    // lock. The fast-path monitor unlock code checks for
-    // markWord::monitor_value so use markWord::unused_mark which has the
-    // relevant bit set, and also matches ObjectSynchronizer::enter.
-    mov(tmp, (address)markWord::unused_mark().value());
-    str(tmp, Address(box, BasicLock::displaced_header_offset_in_bytes()));
-  }
+  // Store a non-null value into the box to avoid looking like a re-entrant
+  // lock. The fast-path monitor unlock code checks for
+  // markWord::monitor_value so use markWord::unused_mark which has the
+  // relevant bit set, and also matches ObjectSynchronizer::enter.
+  mov(tmp, (address)markWord::unused_mark().value());
+  str(tmp, Address(box, BasicLock::displaced_header_offset_in_bytes()));
+
   br(Assembler::EQ, cont); // CAS success means locking succeeded
 
   cmp(rscratch1, rthread);
   br(Assembler::NE, cont); // Check for recursive locking
 
@@ -155,10 +153,11 @@
   Register tmp = tmp2Reg;
   Label cont;
   Label object_has_monitor;
   Label count, no_count;
 
+  assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking should use fast_unlock_lightweight");
   assert_different_registers(oop, box, tmp, disp_hdr);
 
   if (LockingMode == LM_LEGACY) {
     // Find the lock address and load the displaced header from the stack.
     ldr(disp_hdr, Address(box, BasicLock::displaced_header_offset_in_bytes()));
@@ -173,44 +172,28 @@
   tbnz(tmp, exact_log2(markWord::monitor_value), object_has_monitor);
 
   if (LockingMode == LM_MONITOR) {
     tst(oop, oop); // Set NE to indicate 'failure' -> take slow-path. We know that oop != 0.
     b(cont);
-  } else if (LockingMode == LM_LEGACY) {
+  } else {
+    assert(LockingMode == LM_LEGACY, "must be");
     // Check if it is still a light weight lock, this is is true if we
     // see the stack address of the basicLock in the markWord of the
     // object.
 
     cmpxchg(oop, box, disp_hdr, Assembler::xword, /*acquire*/ false,
             /*release*/ true, /*weak*/ false, tmp);
     b(cont);
-  } else {
-    assert(LockingMode == LM_LIGHTWEIGHT, "must be");
-    lightweight_unlock(oop, tmp, box, disp_hdr, no_count);
-    b(count);
   }
 
   assert(oopDesc::mark_offset_in_bytes() == 0, "offset of _mark is not 0");
 
   // Handle existing monitor.
   bind(object_has_monitor);
   STATIC_ASSERT(markWord::monitor_value <= INT_MAX);
   add(tmp, tmp, -(int)markWord::monitor_value); // monitor
 
-  if (LockingMode == LM_LIGHTWEIGHT) {
-    // If the owner is anonymous, we need to fix it -- in an outline stub.
-    Register tmp2 = disp_hdr;
-    ldr(tmp2, Address(tmp, ObjectMonitor::owner_offset()));
-    // We cannot use tbnz here, the target might be too far away and cannot
-    // be encoded.
-    tst(tmp2, (uint64_t)ObjectMonitor::ANONYMOUS_OWNER);
-    C2HandleAnonOMOwnerStub* stub = new (Compile::current()->comp_arena()) C2HandleAnonOMOwnerStub(tmp, tmp2);
-    Compile::current()->output()->add_stub(stub);
-    br(Assembler::NE, stub->entry());
-    bind(stub->continuation());
-  }
-
   ldr(disp_hdr, Address(tmp, ObjectMonitor::recursions_offset()));
 
   Label notRecursive;
   cbz(disp_hdr, notRecursive);
 
@@ -239,10 +222,266 @@
   decrement(Address(rthread, JavaThread::held_monitor_count_offset()));
 
   bind(no_count);
 }
 
+void C2_MacroAssembler::fast_lock_lightweight(Register obj, Register t1,
+                                              Register t2, Register t3) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  assert_different_registers(obj, 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;
+
+  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;
+
+  { // 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);
+
+    // Check if recursive.
+    subw(t3_t, t2_top, oopSize);
+    ldr(t3_t, Address(rthread, t3_t));
+    cmp(obj, t3_t);
+    br(Assembler::EQ, push);
+
+    // Relaxed normal load to check for monitor. Optimization for monitor case.
+    ldr(t1_mark, Address(obj, oopDesc::mark_offset_in_bytes()));
+    tbnz(t1_mark, exact_log2(markWord::monitor_value), inflated);
+
+    // Not inflated
+    assert(oopDesc::mark_offset_in_bytes() == 0, "required to avoid a lea");
+
+    // Try to lock. Transition lock-bits 0b01 => 0b00
+    orr(t1_mark, t1_mark, markWord::unlocked_value);
+    eor(t3_t, t1_mark, markWord::unlocked_value);
+    cmpxchg(/*addr*/ obj, /*expected*/ t1_mark, /*new*/ t3_t, Assembler::xword,
+            /*acquire*/ true, /*release*/ false, /*weak*/ false, noreg);
+    br(Assembler::NE, slow_path);
+
+    bind(push);
+    // After successful lock, push object on lock-stack.
+    str(obj, Address(rthread, t2_top));
+    addw(t2_top, t2_top, oopSize);
+    strw(t2_top, Address(rthread, JavaThread::lock_stack_top_offset()));
+    b(locked);
+  }
+
+  { // 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;
+
+    // Compute owner address.
+    lea(t2_owner_addr, Address(t1_tagged_monitor, (in_bytes(ObjectMonitor::owner_offset()) - monitor_tag)));
+
+    // 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);
+
+    // Check if recursive.
+    cmp(t3_owner, rthread);
+    br(Assembler::NE, slow_path);
+
+    // Recursive.
+    increment(Address(t1_tagged_monitor, in_bytes(ObjectMonitor::recursions_offset()) - monitor_tag), 1);
+  }
+
+  bind(locked);
+  increment(Address(rthread, JavaThread::held_monitor_count_offset()));
+
+#ifdef ASSERT
+  // Check that locked label is reached with Flags == EQ.
+  Label flag_correct;
+  br(Assembler::EQ, flag_correct);
+  stop("Fast Lock Flag != EQ");
+#endif
+
+  bind(slow_path);
+#ifdef ASSERT
+  // Check that slow_path label is reached with Flags == NE.
+  br(Assembler::NE, flag_correct);
+  stop("Fast Lock Flag != NE");
+  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) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  assert_different_registers(obj, t1, t2, t3);
+
+  // Handle inflated monitor.
+  Label inflated, inflated_load_monitor;
+  // Finish fast unlock successfully. MUST branch to with flag == EQ
+  Label unlocked;
+  // Finish fast unlock unsuccessfully. MUST branch to with flag == NE
+  Label slow_path;
+
+  const Register t1_mark = t1;
+  const Register t2_top = t2;
+  const Register t3_t = t3;
+
+  { // Lightweight unlock
+
+    // 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);
+    // Top of lock stack was not obj. Must be monitor.
+    br(Assembler::NE, inflated_load_monitor);
+
+    // Pop lock-stack.
+    DEBUG_ONLY(str(zr, Address(rthread, t2_top));)
+    strw(t2_top, Address(rthread, JavaThread::lock_stack_top_offset()));
+
+    // Check if recursive.
+    subw(t3_t, t2_top, oopSize);
+    ldr(t3_t, Address(rthread, t3_t));
+    cmp(obj, t3_t);
+    br(Assembler::EQ, unlocked);
+
+    // 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);
+
+    // 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);
+
+    // 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()));
+    b(slow_path);
+  }
+
+
+  { // Handle inflated monitor.
+    bind(inflated_load_monitor);
+    ldr(t1_mark, Address(obj, oopDesc::mark_offset_in_bytes()));
+#ifdef ASSERT
+    tbnz(t1_mark, exact_log2(markWord::monitor_value), inflated);
+    stop("Fast Unlock not monitor");
+#endif
+
+    bind(inflated);
+
+#ifdef ASSERT
+    Label check_done;
+    subw(t2_top, t2_top, oopSize);
+    cmpw(t2_top, in_bytes(JavaThread::lock_stack_base_offset()));
+    br(Assembler::LT, check_done);
+    ldr(t3_t, Address(rthread, t2_top));
+    cmp(obj, t3_t);
+    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);
+  }
+
+  bind(unlocked);
+  decrement(Address(rthread, JavaThread::held_monitor_count_offset()));
+
+#ifdef ASSERT
+  // Check that unlocked label is reached with Flags == EQ.
+  Label flag_correct;
+  br(Assembler::EQ, flag_correct);
+  stop("Fast Unlock Flag != EQ");
+#endif
+
+  bind(slow_path);
+#ifdef ASSERT
+  // Check that slow_path label is reached with Flags == NE.
+  br(Assembler::NE, flag_correct);
+  stop("Fast Unlock Flag != NE");
+  bind(flag_correct);
+#endif
+  // C2 uses the value of Flags (NE vs EQ) to determine the continuation.
+}
+
 // Search for str1 in str2 and return index or -1
 // Clobbers: rscratch1, rscratch2, rflags. May also clobber v0-v1, when icnt1==-1.
 void C2_MacroAssembler::string_indexof(Register str2, Register str1,
                                        Register cnt2, Register cnt1,
                                        Register tmp1, Register tmp2,
@@ -2252,5 +2491,32 @@
       return true;
     }
   }
   return MacroAssembler::in_scratch_emit_size();
 }
+
+void C2_MacroAssembler::load_nklass_compact(Register dst, Register obj, Register index, int scale, int disp) {
+  C2LoadNKlassStub* stub = new (Compile::current()->comp_arena()) C2LoadNKlassStub(dst);
+  Compile::current()->output()->add_stub(stub);
+
+  // 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));
+  }
+  // NOTE: We can't use tbnz here, because the target is sometimes too far away
+  // and cannot be encoded.
+  tst(dst, markWord::monitor_value);
+  br(Assembler::NE, stub->entry());
+  bind(stub->continuation());
+  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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 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.
@@ -34,13 +34,15 @@
   void neon_reduce_logical_helper(int opc, bool sf, Register Rd, Register Rn, Register Rm,
                                   enum shift_kind kind = Assembler::LSL, unsigned shift = 0);
 
  public:
   // Code used by cmpFastLock and cmpFastUnlock mach instructions in .ad file.
-  // See full description in macroAssembler_aarch64.cpp.
   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 string_compare(Register str1, Register str2,
                       Register cnt1, Register cnt2, Register result,
                       Register tmp1, Register tmp2, FloatRegister vtmp1,
                       FloatRegister vtmp2, FloatRegister vtmp3,
@@ -175,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/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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2014, 2020, 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
@@ -765,11 +765,10 @@
       tstw(tmp, JVM_ACC_IS_VALUE_BASED_CLASS);
       br(Assembler::NE, slow_case);
     }
 
     if (LockingMode == LM_LIGHTWEIGHT) {
-      ldr(tmp, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
       lightweight_lock(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()));
@@ -882,26 +881,10 @@
     // Free entry
     str(zr, Address(lock_reg, BasicObjectLock::obj_offset()));
 
     if (LockingMode == LM_LIGHTWEIGHT) {
       Label slow_case;
-
-      // Check for non-symmetric locking. This is allowed by the spec and the interpreter
-      // must handle it.
-      Register tmp = rscratch1;
-      // First check for lock-stack underflow.
-      ldrw(tmp, Address(rthread, JavaThread::lock_stack_top_offset()));
-      cmpw(tmp, (unsigned)LockStack::start_offset());
-      br(Assembler::LE, slow_case);
-      // Then check if the top of the lock-stack matches the unlocked object.
-      subw(tmp, tmp, oopSize);
-      ldr(tmp, Address(rthread, tmp));
-      cmpoop(tmp, obj_reg);
-      br(Assembler::NE, slow_case);
-
-      ldr(header_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
-      tbnz(header_reg, exact_log2(markWord::monitor_value), slow_case);
       lightweight_unlock(obj_reg, header_reg, swap_reg, tmp_reg, slow_case);
       b(count);
       bind(slow_case);
     } else if (LockingMode == LM_LEGACY) {
       // Load the old header from BasicLock structure
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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2014, 2021, 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
@@ -21,12 +21,10 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  *
  */
 
-#include <sys/types.h>
-
 #include "precompiled.hpp"
 #include "asm/assembler.hpp"
 #include "asm/assembler.inline.hpp"
 #include "ci/ciEnv.hpp"
 #include "compiler/compileTask.hpp"
@@ -52,10 +50,11 @@
 #include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/javaThread.hpp"
 #include "runtime/jniHandles.inline.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "runtime/stubRoutines.hpp"
+#include "utilities/globalDefinitions.hpp"
 #include "utilities/powerOfTwo.hpp"
 #ifdef COMPILER1
 #include "c1/c1_LIRAssembler.hpp"
 #endif
 #ifdef COMPILER2
@@ -63,10 +62,12 @@
 #include "opto/compile.hpp"
 #include "opto/node.hpp"
 #include "opto/output.hpp"
 #endif
 
+#include <sys/types.h>
+
 #ifdef PRODUCT
 #define BLOCK_COMMENT(str) /* nothing */
 #else
 #define BLOCK_COMMENT(str) block_comment(str)
 #endif
@@ -4428,12 +4429,34 @@
   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).
+void MacroAssembler::load_nklass_compact(Register dst, Register src) {
+  assert(UseCompactObjectHeaders, "expects UseCompactObjectHeaders");
+
+  Label fast;
+
+  // Check if we can take the (common) fast path, if obj is unlocked.
+  ldr(dst, Address(src, oopDesc::mark_offset_in_bytes()));
+  tbz(dst, exact_log2(markWord::monitor_value), fast);
+
+  // Fetch displaced header
+  ldr(dst, Address(dst, OM_OFFSET_NO_MONITOR_VALUE_TAG(header)));
+
+  // Fast-path: shift to get narrowKlass.
+  bind(fast);
+  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()));
   }
@@ -4468,12 +4491,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) {
@@ -4486,22 +4514,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()));
   }
 }
@@ -6320,99 +6366,124 @@
       strd(src.first()->as_FloatRegister(), Address(sp, reg2offset_out(dst.first())));
   }
 }
 
 // Implements lightweight-locking.
-// Branches to slow upon failure to lock the object, with ZF cleared.
-// Falls through upon success with ZF set.
 //
 //  - obj: the object to be locked
-//  - hdr: the header, already loaded from obj, will be destroyed
-//  - t1, t2: temporary registers, will be destroyed
-void MacroAssembler::lightweight_lock(Register obj, Register hdr, Register t1, Register t2, Label& slow) {
+//  - 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) {
   assert(LockingMode == LM_LIGHTWEIGHT, "only used with new lightweight locking");
-  assert_different_registers(obj, hdr, t1, t2, rscratch1);
-
-  // Check if we would have space on lock-stack for the object.
-  ldrw(t1, Address(rthread, JavaThread::lock_stack_top_offset()));
-  cmpw(t1, (unsigned)LockStack::end_offset() - 1);
-  br(Assembler::GT, slow);
-
-  // Load (object->mark() | 1) into hdr
-  orr(hdr, hdr, markWord::unlocked_value);
-  // Clear lock-bits, into t2
-  eor(t2, hdr, markWord::unlocked_value);
-  // Try to swing header from unlocked to locked
-  // Clobbers rscratch1 when UseLSE is false
-  cmpxchg(/*addr*/ obj, /*expected*/ hdr, /*new*/ t2, Assembler::xword,
-          /*acquire*/ true, /*release*/ true, /*weak*/ false, t1);
+  assert_different_registers(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()));
+
+  // 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);
+
+  // Check for recursion.
+  subw(t, top, oopSize);
+  ldr(t, Address(rthread, t));
+  cmp(obj, t);
+  br(Assembler::EQ, push);
+
+  // Check header for monitor (0b10).
+  tst(mark, markWord::monitor_value);
   br(Assembler::NE, slow);
 
-  // After successful lock, push object on lock-stack
-  ldrw(t1, Address(rthread, JavaThread::lock_stack_top_offset()));
-  str(obj, Address(rthread, t1));
-  addw(t1, t1, oopSize);
-  strw(t1, Address(rthread, JavaThread::lock_stack_top_offset()));
+  // Try to lock. Transition lock bits 0b01 => 0b00
+  assert(oopDesc::mark_offset_in_bytes() == 0, "required to avoid lea");
+  orr(mark, mark, markWord::unlocked_value);
+  eor(t, mark, markWord::unlocked_value);
+  cmpxchg(/*addr*/ obj, /*expected*/ mark, /*new*/ t, Assembler::xword,
+          /*acquire*/ true, /*release*/ false, /*weak*/ false, noreg);
+  br(Assembler::NE, slow);
+
+  bind(push);
+  // After successful lock, push object on lock-stack.
+  str(obj, Address(rthread, top));
+  addw(top, top, oopSize);
+  strw(top, Address(rthread, JavaThread::lock_stack_top_offset()));
 }
 
 // Implements lightweight-unlocking.
-// Branches to slow upon failure, with ZF cleared.
-// Falls through upon success, with ZF set.
 //
 // - obj: the object to be unlocked
-// - hdr: the (pre-loaded) header of the object
-// - t1, t2: temporary registers
-void MacroAssembler::lightweight_unlock(Register obj, Register hdr, Register t1, Register t2, Label& slow) {
+// - t1, t2, t3: temporary registers
+// - slow: branched to if unlocking fails, absolute offset may larger than 32KB (imm14 encoding).
+void MacroAssembler::lightweight_unlock(Register obj, Register t1, Register t2, Register t3, Label& slow) {
   assert(LockingMode == LM_LIGHTWEIGHT, "only used with new lightweight locking");
-  assert_different_registers(obj, hdr, t1, t2, rscratch1);
+  // cmpxchg clobbers rscratch1.
+  assert_different_registers(obj, t1, t2, t3, rscratch1);
 
 #ifdef ASSERT
   {
-    // The following checks rely on the fact that LockStack is only ever modified by
-    // its owning thread, even if the lock got inflated concurrently; removal of LockStack
-    // entries after inflation will happen delayed in that case.
-
     // Check for lock-stack underflow.
     Label stack_ok;
     ldrw(t1, Address(rthread, JavaThread::lock_stack_top_offset()));
     cmpw(t1, (unsigned)LockStack::start_offset());
-    br(Assembler::GT, stack_ok);
+    br(Assembler::GE, stack_ok);
     STOP("Lock-stack underflow");
     bind(stack_ok);
   }
-  {
-    // Check if the top of the lock-stack matches the unlocked object.
-    Label tos_ok;
-    subw(t1, t1, oopSize);
-    ldr(t1, Address(rthread, t1));
-    cmpoop(t1, obj);
-    br(Assembler::EQ, tos_ok);
-    STOP("Top of lock-stack does not match the unlocked object");
-    bind(tos_ok);
-  }
-  {
-    // Check that hdr is fast-locked.
-    Label hdr_ok;
-    tst(hdr, markWord::lock_mask_in_place);
-    br(Assembler::EQ, hdr_ok);
-    STOP("Header is not fast-locked");
-    bind(hdr_ok);
-  }
 #endif
 
-  // Load the new header (unlocked) into t1
-  orr(t1, hdr, markWord::unlocked_value);
+  Label unlocked, push_and_slow;
+  const Register top = t1;
+  const Register mark = t2;
+  const Register t = t3;
 
-  // Try to swing header from locked to unlocked
-  // Clobbers rscratch1 when UseLSE is false
-  cmpxchg(obj, hdr, t1, Assembler::xword,
-          /*acquire*/ true, /*release*/ true, /*weak*/ false, t2);
+  // Check if obj is top of lock-stack.
+  ldrw(top, Address(rthread, JavaThread::lock_stack_top_offset()));
+  subw(top, top, oopSize);
+  ldr(t, Address(rthread, top));
+  cmp(obj, t);
   br(Assembler::NE, slow);
 
-  // After successful unlock, pop object from lock-stack
-  ldrw(t1, Address(rthread, JavaThread::lock_stack_top_offset()));
-  subw(t1, t1, oopSize);
+  // Pop lock-stack.
+  DEBUG_ONLY(str(zr, Address(rthread, top));)
+  strw(top, Address(rthread, JavaThread::lock_stack_top_offset()));
+
+  // Check if recursive.
+  subw(t, top, oopSize);
+  ldr(t, Address(rthread, t));
+  cmp(obj, t);
+  br(Assembler::EQ, unlocked);
+
+  // Not recursive. Check header for monitor (0b10).
+  ldr(mark, Address(obj, oopDesc::mark_offset_in_bytes()));
+  tbnz(mark, log2i_exact(markWord::monitor_value), push_and_slow);
+
 #ifdef ASSERT
-  str(zr, Address(rthread, t1));
+  // Check header not unlocked (0b01).
+  Label not_unlocked;
+  tbz(mark, log2i_exact(markWord::unlocked_value), not_unlocked);
+  stop("lightweight_unlock already unlocked");
+  bind(not_unlocked);
 #endif
-  strw(t1, Address(rthread, JavaThread::lock_stack_top_offset()));
+
+  // Try to unlock. Transition lock bits 0b00 => 0b01
+  assert(oopDesc::mark_offset_in_bytes() == 0, "required to avoid lea");
+  orr(t, mark, markWord::unlocked_value);
+  cmpxchg(obj, mark, t, Assembler::xword,
+          /*acquire*/ false, /*release*/ true, /*weak*/ false, noreg);
+  br(Assembler::EQ, unlocked);
+
+  bind(push_and_slow);
+  // Restore lock-stack and handle the unlock in runtime.
+  DEBUG_ONLY(str(obj, Address(rthread, top));)
+  addw(top, top, oopSize);
+  strw(top, Address(rthread, JavaThread::lock_stack_top_offset()));
+  b(slow);
+
+  bind(unlocked);
 }
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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2014, 2021, 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
@@ -847,13 +847,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);
 
@@ -1589,12 +1591,12 @@
   void cache_wbsync(bool is_pre);
 
   // Code for java.lang.Thread::onSpinWait() intrinsic.
   void spin_wait();
 
-  void lightweight_lock(Register obj, Register hdr, Register t1, Register t2, Label& slow);
-  void lightweight_unlock(Register obj, Register hdr, Register t1, Register t2, Label& slow);
+  void lightweight_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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2014, 2021, Red Hat Inc. All rights reserved.
  * Copyright (c) 2021, Azul Systems, 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
@@ -1815,11 +1815,10 @@
       // 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");
-      __ ldr(swap_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
       __ lightweight_lock(obj_reg, swap_reg, tmp, lock_tmp, slow_path_lock);
     }
     __ bind(count);
     __ increment(Address(rthread, JavaThread::held_monitor_count_offset()));
 
@@ -1955,12 +1954,10 @@
       __ cmpxchg_obj_header(r0, old_hdr, obj_reg, rscratch1, count, &slow_path_unlock);
       __ bind(count);
       __ decrement(Address(rthread, JavaThread::held_monitor_count_offset()));
     } else {
       assert(LockingMode == LM_LIGHTWEIGHT, "");
-      __ ldr(old_hdr, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
-      __ tbnz(old_hdr, exact_log2(markWord::monitor_value), slow_path_unlock);
       __ lightweight_unlock(obj_reg, old_hdr, swap_reg, lock_tmp, slow_path_unlock);
       __ decrement(Address(rthread, JavaThread::held_monitor_count_offset()));
     }
 
     // slow path re-enters 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
@@ -3566,30 +3566,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/aarch64/vm_version_aarch64.hpp b/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp
--- a/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp
+++ b/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2014, 2020, 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
@@ -166,10 +166,11 @@
   static int dcache_line_size() { return _dcache_line_size; }
   static int get_initial_sve_vector_length()  { return _initial_sve_vector_length; };
 
   static bool supports_fast_class_init_checks() { return true; }
   constexpr static bool supports_stack_watermark_barrier() { return true; }
+  constexpr static bool supports_recursive_lightweight_locking() { return true; }
 
   static void get_compatible_board(char *buf, int buflen);
 
   static const SpinWait& spin_wait_desc() { return _spin_wait; }
 
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,10 +215,14 @@
                              Runtime1::monitorexit_nofpu_id;
   __ call(Runtime1::entry_for(exit_id), relocInfo::runtime_call_type);
   __ b(_continuation);
 }
 
+void LoadKlassStub::emit_code(LIR_Assembler* ce) {
+  // Currently not needed.
+  Unimplemented();
+}
 
 // Call return is directly after patch word
 int PatchingStub::_patch_info_offset = 0;
 
 void PatchingStub::align_patch_site(MacroAssembler* masm) {
diff a/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp b/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp
--- a/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp
+++ b/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp
@@ -969,11 +969,11 @@
     __ allocate_array(op->obj()->as_register(),
                       op->len()->as_register(),
                       op->tmp1()->as_register(),
                       op->tmp2()->as_register(),
                       op->tmp3()->as_register(),
-                      arrayOopDesc::header_size(op->type()),
+                      arrayOopDesc::base_offset_in_bytes(op->type()),
                       type2aelembytes(op->type()),
                       op->klass()->as_register(),
                       *op->stub()->entry());
   }
   __ bind(*op->stub()->continuation());
diff a/src/hotspot/cpu/ppc/assembler_ppc.hpp b/src/hotspot/cpu/ppc/assembler_ppc.hpp
--- a/src/hotspot/cpu/ppc/assembler_ppc.hpp
+++ b/src/hotspot/cpu/ppc/assembler_ppc.hpp
@@ -335,19 +335,10 @@
 
     MTCTR_OPCODE  = (MTSPR_OPCODE | 9 << SPR_0_4_SHIFT),
     MFCTR_OPCODE  = (MFSPR_OPCODE | 9 << SPR_0_4_SHIFT),
 
     // Attention: Higher and lower half are inserted in reversed order.
-    MTTFHAR_OPCODE   = (MTSPR_OPCODE | 4 << SPR_5_9_SHIFT | 0 << SPR_0_4_SHIFT),
-    MFTFHAR_OPCODE   = (MFSPR_OPCODE | 4 << SPR_5_9_SHIFT | 0 << SPR_0_4_SHIFT),
-    MTTFIAR_OPCODE   = (MTSPR_OPCODE | 4 << SPR_5_9_SHIFT | 1 << SPR_0_4_SHIFT),
-    MFTFIAR_OPCODE   = (MFSPR_OPCODE | 4 << SPR_5_9_SHIFT | 1 << SPR_0_4_SHIFT),
-    MTTEXASR_OPCODE  = (MTSPR_OPCODE | 4 << SPR_5_9_SHIFT | 2 << SPR_0_4_SHIFT),
-    MFTEXASR_OPCODE  = (MFSPR_OPCODE | 4 << SPR_5_9_SHIFT | 2 << SPR_0_4_SHIFT),
-    MTTEXASRU_OPCODE = (MTSPR_OPCODE | 4 << SPR_5_9_SHIFT | 3 << SPR_0_4_SHIFT),
-    MFTEXASRU_OPCODE = (MFSPR_OPCODE | 4 << SPR_5_9_SHIFT | 3 << SPR_0_4_SHIFT),
-
     MTVRSAVE_OPCODE  = (MTSPR_OPCODE | 8 << SPR_5_9_SHIFT | 0 << SPR_0_4_SHIFT),
     MFVRSAVE_OPCODE  = (MFSPR_OPCODE | 8 << SPR_5_9_SHIFT | 0 << SPR_0_4_SHIFT),
 
     MFTB_OPCODE   = (MFSPR_OPCODE | 8 << SPR_5_9_SHIFT | 12 << SPR_0_4_SHIFT),
 
@@ -764,21 +755,10 @@
     VPMSUMW_OPCODE      = (4u  << OPCODE_SHIFT | 1160u),
 
     // Vector Permute and Xor (introduced with Power 8)
     VPERMXOR_OPCODE     = (4u  << OPCODE_SHIFT |   45u),
 
-    // Transactional Memory instructions (introduced with Power 8)
-    TBEGIN_OPCODE    = (31u << OPCODE_SHIFT |  654u << 1),
-    TEND_OPCODE      = (31u << OPCODE_SHIFT |  686u << 1),
-    TABORT_OPCODE    = (31u << OPCODE_SHIFT |  910u << 1),
-    TABORTWC_OPCODE  = (31u << OPCODE_SHIFT |  782u << 1),
-    TABORTWCI_OPCODE = (31u << OPCODE_SHIFT |  846u << 1),
-    TABORTDC_OPCODE  = (31u << OPCODE_SHIFT |  814u << 1),
-    TABORTDCI_OPCODE = (31u << OPCODE_SHIFT |  878u << 1),
-    TSR_OPCODE       = (31u << OPCODE_SHIFT |  750u << 1),
-    TCHECK_OPCODE    = (31u << OPCODE_SHIFT |  718u << 1),
-
     // Icache and dcache related instructions
     DCBA_OPCODE    = (31u << OPCODE_SHIFT |  758u << 1),
     DCBZ_OPCODE    = (31u << OPCODE_SHIFT | 1014u << 1),
     DCBST_OPCODE   = (31u << OPCODE_SHIFT |   54u << 1),
     DCBF_OPCODE    = (31u << OPCODE_SHIFT |   86u << 1),
@@ -1812,37 +1792,10 @@
   inline void mftb(Register d);
   // Introduced with Power 8:
   // Data Stream Control Register
   inline void mtdscr(Register s1);
   inline void mfdscr(Register d );
-  // Transactional Memory Registers
-  inline void mftfhar(Register d);
-  inline void mftfiar(Register d);
-  inline void mftexasr(Register d);
-  inline void mftexasru(Register d);
-
-  // TEXASR bit description
-  enum transaction_failure_reason {
-    // Upper half (TEXASRU):
-    tm_failure_code       =  0, // The Failure Code is copied from tabort or treclaim operand.
-    tm_failure_persistent =  7, // The failure is likely to recur on each execution.
-    tm_disallowed         =  8, // The instruction is not permitted.
-    tm_nesting_of         =  9, // The maximum transaction level was exceeded.
-    tm_footprint_of       = 10, // The tracking limit for transactional storage accesses was exceeded.
-    tm_self_induced_cf    = 11, // A self-induced conflict occurred in Suspended state.
-    tm_non_trans_cf       = 12, // A conflict occurred with a non-transactional access by another processor.
-    tm_trans_cf           = 13, // A conflict occurred with another transaction.
-    tm_translation_cf     = 14, // A conflict occurred with a TLB invalidation.
-    tm_inst_fetch_cf      = 16, // An instruction fetch was performed from a block that was previously written transactionally.
-    tm_tabort             = 31, // Termination was caused by the execution of an abort instruction.
-    // Lower half:
-    tm_suspended          = 32, // Failure was recorded in Suspended state.
-    tm_failure_summary    = 36, // Failure has been detected and recorded.
-    tm_tfiar_exact        = 37, // Value in the TFIAR is exact.
-    tm_rot                = 38, // Rollback-only transaction.
-    tm_transaction_level  = 52, // Transaction level (nesting depth + 1).
-  };
 
   // PPC 1, section 2.4.1 Branch Instructions
   inline void b(  address a, relocInfo::relocType rt = relocInfo::none);
   inline void b(  Label& L);
   inline void bl( address a, relocInfo::relocType rt = relocInfo::none);
@@ -2450,29 +2403,10 @@
   inline void vpmsumw(  VectorRegister d, VectorRegister a, VectorRegister b);
 
   // Vector Permute and Xor (introduced with Power 8)
   inline void vpermxor( VectorRegister d, VectorRegister a, VectorRegister b, VectorRegister c);
 
-  // Transactional Memory instructions (introduced with Power 8)
-  inline void tbegin_();    // R=0
-  inline void tbeginrot_(); // R=1 Rollback-Only Transaction
-  inline void tend_();    // A=0
-  inline void tendall_(); // A=1
-  inline void tabort_();
-  inline void tabort_(Register a);
-  inline void tabortwc_(int t, Register a, Register b);
-  inline void tabortwci_(int t, Register a, int si);
-  inline void tabortdc_(int t, Register a, Register b);
-  inline void tabortdci_(int t, Register a, int si);
-  inline void tsuspend_(); // tsr with L=0
-  inline void tresume_();  // tsr with L=1
-  inline void tcheck(int f);
-
-  static bool is_tbegin(int x) {
-    return TBEGIN_OPCODE == (x & (0x3f << OPCODE_SHIFT | 0x3ff << 1));
-  }
-
   // The following encoders use r0 as second operand. These instructions
   // read r0 as '0'.
   inline void lwzx( Register d, Register s2);
   inline void lwz(  Register d, int si16);
   inline void lwax( Register d, Register s2);
diff a/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp b/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp
--- a/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp
+++ b/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp
@@ -431,17 +431,11 @@
 inline void Assembler::mftb(Register d )          { emit_int32(MFTB_OPCODE  | rt(d)); }
 // Introduced with Power 8:
 // Data Stream Control Register
 inline void Assembler::mtdscr(Register s1)        { emit_int32(MTDSCR_OPCODE | rs(s1)); }
 inline void Assembler::mfdscr(Register d )        { emit_int32(MFDSCR_OPCODE | rt(d)); }
-// Transactional Memory Registers
-inline void Assembler::mftfhar(Register d )       { emit_int32(MFTFHAR_OPCODE   | rt(d)); }
-inline void Assembler::mftfiar(Register d )       { emit_int32(MFTFIAR_OPCODE   | rt(d)); }
-inline void Assembler::mftexasr(Register d )      { emit_int32(MFTEXASR_OPCODE  | rt(d)); }
-inline void Assembler::mftexasru(Register d )     { emit_int32(MFTEXASRU_OPCODE | rt(d)); }
 
-// SAP JVM 2006-02-13 PPC branch instruction.
 // PPC 1, section 2.4.1 Branch Instructions
 inline void Assembler::b( address a, relocInfo::relocType rt) { emit_data(BXX_OPCODE| li(disp( intptr_t(a), intptr_t(pc()))) |aa(0)|lk(0), rt); }
 inline void Assembler::b( Label& L)                           { b( target(L)); }
 inline void Assembler::bl(address a, relocInfo::relocType rt) { emit_data(BXX_OPCODE| li(disp( intptr_t(a), intptr_t(pc()))) |aa(0)|lk(1), rt); }
 inline void Assembler::bl(Label& L)                           { bl(target(L)); }
@@ -1046,25 +1040,10 @@
 inline void Assembler::vpmsumw(  VectorRegister d, VectorRegister a, VectorRegister b) { emit_int32( VPMSUMW_OPCODE | vrt(d) | vra(a) | vrb(b)); }
 
 // Vector Permute and Xor (introduced with Power 8)
 inline void Assembler::vpermxor( VectorRegister d, VectorRegister a, VectorRegister b, VectorRegister c) { emit_int32( VPERMXOR_OPCODE | vrt(d) | vra(a) | vrb(b) | vrc(c)); }
 
-// Transactional Memory instructions (introduced with Power 8)
-inline void Assembler::tbegin_()                                { emit_int32( TBEGIN_OPCODE | rc(1)); }
-inline void Assembler::tbeginrot_()                             { emit_int32( TBEGIN_OPCODE | /*R=1*/ 1u << (31-10) | rc(1)); }
-inline void Assembler::tend_()                                  { emit_int32( TEND_OPCODE | rc(1)); }
-inline void Assembler::tendall_()                               { emit_int32( TEND_OPCODE | /*A=1*/ 1u << (31-6) | rc(1)); }
-inline void Assembler::tabort_()                                { emit_int32( TABORT_OPCODE | rc(1)); }
-inline void Assembler::tabort_(Register a)                      { assert(a != R0, "r0 not allowed"); emit_int32( TABORT_OPCODE | ra(a) | rc(1)); }
-inline void Assembler::tabortwc_(int t, Register a, Register b) { emit_int32( TABORTWC_OPCODE | to(t) | ra(a) | rb(b) | rc(1)); }
-inline void Assembler::tabortwci_(int t, Register a, int si)    { emit_int32( TABORTWCI_OPCODE | to(t) | ra(a) | sh1620(si) | rc(1)); }
-inline void Assembler::tabortdc_(int t, Register a, Register b) { emit_int32( TABORTDC_OPCODE | to(t) | ra(a) | rb(b) | rc(1)); }
-inline void Assembler::tabortdci_(int t, Register a, int si)    { emit_int32( TABORTDCI_OPCODE | to(t) | ra(a) | sh1620(si) | rc(1)); }
-inline void Assembler::tsuspend_()                              { emit_int32( TSR_OPCODE | rc(1)); }
-inline void Assembler::tresume_()                               { emit_int32( TSR_OPCODE | /*L=1*/ 1u << (31-10) | rc(1)); }
-inline void Assembler::tcheck(int f)                            { emit_int32( TCHECK_OPCODE | bf(f)); }
-
 // Deliver A Random Number (introduced with POWER9)
 inline void Assembler::darn(Register d, int l /* =1 */) { emit_int32( DARN_OPCODE | rt(d) | l14(l)); }
 
 // ra0 version
 inline void Assembler::lwzx( Register d, Register s2) { emit_int32( LWZX_OPCODE | rt(d) | rb(s2));}
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,10 +296,14 @@
   __ mtctr(R0);
   __ bctrl();
   __ b(_continuation);
 }
 
+void LoadKlassStub::emit_code(LIR_Assembler* ce) {
+  // Currently not needed.
+  Unimplemented();
+}
 
 // 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:
diff a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp
--- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp
+++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp
@@ -2298,11 +2298,11 @@
     __ allocate_array(op->obj()->as_register(),
                       op->len()->as_register(),
                       op->tmp1()->as_register(),
                       op->tmp2()->as_register(),
                       op->tmp3()->as_register(),
-                      arrayOopDesc::header_size(op->type()),
+                      arrayOopDesc::base_offset_in_bytes(op->type()),
                       type2aelembytes(op->type()),
                       op->klass()->as_register(),
                       *op->stub()->entry());
   }
   __ bind(*op->stub()->continuation());
diff a/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.cpp
--- a/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.cpp
+++ b/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.cpp
@@ -176,13 +176,10 @@
   // Load object.
   ld(Roop, in_bytes(BasicObjectLock::obj_offset()), Rbox);
   verify_oop(Roop, FILE_AND_LINE);
 
   if (LockingMode == LM_LIGHTWEIGHT) {
-    ld(Rmark, oopDesc::mark_offset_in_bytes(), Roop);
-    andi_(R0, Rmark, markWord::monitor_value);
-    bne(CCR0, slow_int);
     lightweight_unlock(Roop, Rmark, slow_int);
   } else if (LockingMode == LM_LEGACY) {
     // Check if it is still a light weight lock, this is is true if we see
     // the stack address of the basicLock in the markWord of the object.
     cmpxchgd(/*flag=*/CCR0,
@@ -331,11 +328,11 @@
   Register obj,                        // result: pointer to array after successful allocation
   Register len,                        // array length
   Register t1,                         // temp register
   Register t2,                         // temp register
   Register t3,                         // temp register
-  int      hdr_size,                   // object header size in words
+  int      base_offset_in_bytes,       // elements offset in bytes
   int      elt_size,                   // element size in bytes
   Register klass,                      // object klass
   Label&   slow_case                   // continuation point if fast allocation fails
 ) {
   assert_different_registers(obj, len, t1, t2, t3, klass);
@@ -363,22 +360,32 @@
   Register arr_len_in_bytes = len;
   if (elt_size != 1) {
     sldi(t1, len, log2_elt_size);
     arr_len_in_bytes = t1;
   }
-  addi(arr_size, arr_len_in_bytes, hdr_size * wordSize + MinObjAlignmentInBytesMask); // Add space for header & alignment.
+  addi(arr_size, arr_len_in_bytes, base_offset_in_bytes + MinObjAlignmentInBytesMask); // Add space for header & alignment.
   clrrdi(arr_size, arr_size, LogMinObjAlignmentInBytes);                              // Align array size.
 
   // Allocate space & initialize header.
   try_allocate(obj, arr_size, 0, t2, t3, slow_case);
   initialize_header(obj, klass, len, t2, t3);
 
   // Initialize body.
   const Register base  = t2;
   const Register index = t3;
-  addi(base, obj, hdr_size * wordSize);               // compute address of first element
-  addi(index, arr_size, -(hdr_size * wordSize));      // compute index = number of bytes to clear
+  addi(base, obj, base_offset_in_bytes);               // compute address of first element
+  addi(index, arr_size, -(base_offset_in_bytes));      // compute index = number of bytes to clear
+
+  // Zero first 4 bytes, if start offset is not word aligned.
+  if (!is_aligned(base_offset_in_bytes, BytesPerWord)) {
+    assert(is_aligned(base_offset_in_bytes, BytesPerInt), "must be 4-byte aligned");
+    li(t1, 0);
+    stw(t1, 0, base);
+    addi(base, base, BytesPerInt);
+    // Note: initialize_body will align index down, no need to correct it here.
+  }
+
   initialize_body(base, index);
 
   if (CURRENT_ENV->dtrace_alloc_probes()) {
     Unimplemented();
     //assert(obj == O0, "must be");
diff a/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.hpp b/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.hpp
--- a/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.hpp
+++ b/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.hpp
@@ -78,11 +78,11 @@
     Register obj,                      // result: pointer to array after successful allocation
     Register len,                      // array length
     Register t1,                       // temp register
     Register t2,                       // temp register
     Register t3,                       // temp register
-    int      hdr_size,                 // object header size in words
+    int      base_offset_in_bytes,     // elements offset in bytes
     int      elt_size,                 // element size in bytes
     Register klass,                    // object klass
     Label&   slow_case                 // continuation point if fast allocation fails
   );
 
diff a/src/hotspot/cpu/ppc/c2_MacroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c2_MacroAssembler_ppc.cpp
--- a/src/hotspot/cpu/ppc/c2_MacroAssembler_ppc.cpp
+++ b/src/hotspot/cpu/ppc/c2_MacroAssembler_ppc.cpp
@@ -34,10 +34,21 @@
 #else
 #define BLOCK_COMMENT(str) block_comment(str)
 #endif
 #define BIND(label) bind(label); BLOCK_COMMENT(#label ":")
 
+
+void C2_MacroAssembler::fast_lock_lightweight(ConditionRegister flag, Register obj, Register box,
+                                              Register tmp1, Register tmp2, Register tmp3) {
+  compiler_fast_lock_lightweight_object(flag, obj, tmp1, tmp2, tmp3);
+}
+
+void C2_MacroAssembler::fast_unlock_lightweight(ConditionRegister flag, Register obj, Register box,
+                                                Register tmp1, Register tmp2, Register tmp3) {
+  compiler_fast_unlock_lightweight_object(flag, obj, tmp1, tmp2, tmp3);
+}
+
 // Intrinsics for CompactStrings
 
 // Compress char[] to byte[] by compressing 16 bytes at once.
 void C2_MacroAssembler::string_compress_16(Register src, Register dst, Register cnt,
                                            Register tmp1, Register tmp2, Register tmp3, Register tmp4, Register tmp5,
diff a/src/hotspot/cpu/ppc/c2_MacroAssembler_ppc.hpp b/src/hotspot/cpu/ppc/c2_MacroAssembler_ppc.hpp
--- a/src/hotspot/cpu/ppc/c2_MacroAssembler_ppc.hpp
+++ b/src/hotspot/cpu/ppc/c2_MacroAssembler_ppc.hpp
@@ -26,10 +26,16 @@
 #define CPU_PPC_C2_MACROASSEMBLER_PPC_HPP
 
 // C2_MacroAssembler contains high-level macros for C2
 
  public:
+  // Code used by cmpFastLockLightweight and cmpFastUnlockLightweight mach instructions in .ad file.
+  void fast_lock_lightweight(ConditionRegister flag, Register obj, Register box,
+                             Register tmp1, Register tmp2, Register tmp3);
+  void fast_unlock_lightweight(ConditionRegister flag, Register obj, Register box,
+                               Register tmp1, Register tmp2, Register tmp3);
+
   // Intrinsics for CompactStrings
   // Compress char[] to byte[] by compressing 16 bytes at once.
   void string_compress_16(Register src, Register dst, Register cnt,
                           Register tmp1, Register tmp2, Register tmp3, Register tmp4, Register tmp5,
                           Label& Lfailure, bool ascii = false);
diff a/src/hotspot/cpu/ppc/globalDefinitions_ppc.hpp b/src/hotspot/cpu/ppc/globalDefinitions_ppc.hpp
--- a/src/hotspot/cpu/ppc/globalDefinitions_ppc.hpp
+++ b/src/hotspot/cpu/ppc/globalDefinitions_ppc.hpp
@@ -49,17 +49,10 @@
 // So we must not #define CPU_MULTI_COPY_ATOMIC
 
 // The expected size in bytes of a cache line, used to pad data structures.
 #define DEFAULT_CACHE_LINE_SIZE 128
 
-#if defined(COMPILER2) && (defined(AIX) || defined(LINUX))
-// Include Transactional Memory lock eliding optimization
-#define INCLUDE_RTM_OPT 1
-#else
-#define INCLUDE_RTM_OPT 0
-#endif
-
 #define SUPPORT_RESERVED_STACK_AREA
 
 // If UseSIGTRAP is active, we only use the poll bit and no polling page.
 // Otherwise, we fall back to usage of the polling page in nmethods.
 // Define the condition to use this -XX flag.
diff a/src/hotspot/cpu/ppc/globals_ppc.hpp b/src/hotspot/cpu/ppc/globals_ppc.hpp
--- a/src/hotspot/cpu/ppc/globals_ppc.hpp
+++ b/src/hotspot/cpu/ppc/globals_ppc.hpp
@@ -149,53 +149,10 @@
                                                                             \
   product(bool, TraceTraps, false, DIAGNOSTIC,                              \
           "Trace all traps the signal handler handles.")                    \
                                                                             \
   develop(bool, ZapMemory, false,                                           \
-          "Write 0x0101... to empty memory. Use this to ease debugging.")   \
-                                                                            \
-  /* Use Restricted Transactional Memory for lock elision */                \
-  product(bool, UseRTMLocking, false,                                       \
-          "Enable RTM lock eliding for inflated locks in compiled code")    \
-                                                                            \
-  product(bool, UseRTMForStackLocks, false, EXPERIMENTAL,                   \
-          "Enable RTM lock eliding for stack locks in compiled code")       \
-                                                                            \
-  product(bool, UseRTMDeopt, false,                                         \
-          "Perform deopt and recompilation based on RTM abort ratio")       \
-                                                                            \
-  product(int, RTMRetryCount, 5,                                            \
-          "Number of RTM retries on lock abort or busy")                    \
-          range(0, max_jint)                                                \
-                                                                            \
-  product(int, RTMSpinLoopCount, 100, EXPERIMENTAL,                         \
-          "Spin count for lock to become free before RTM retry")            \
-          range(0, 32767) /* immediate operand limit on ppc */              \
-                                                                            \
-  product(int, RTMAbortThreshold, 1000, EXPERIMENTAL,                       \
-          "Calculate abort ratio after this number of aborts")              \
-          range(0, max_jint)                                                \
-                                                                            \
-  product(int, RTMLockingThreshold, 10000, EXPERIMENTAL,                    \
-          "Lock count at which to do RTM lock eliding without "             \
-          "abort ratio calculation")                                        \
-          range(0, max_jint)                                                \
-                                                                            \
-  product(int, RTMAbortRatio, 50, EXPERIMENTAL,                             \
-          "Lock abort ratio at which to stop use RTM lock eliding")         \
-          range(0, 100) /* natural range */                                 \
-                                                                            \
-  product(int, RTMTotalCountIncrRate, 64, EXPERIMENTAL,                     \
-          "Increment total RTM attempted lock count once every n times")    \
-          range(1, 32767) /* immediate operand limit on ppc */              \
-          constraint(RTMTotalCountIncrRateConstraintFunc,AfterErgo)         \
-                                                                            \
-  product(intx, RTMLockingCalculationDelay, 0, EXPERIMENTAL,                \
-          "Number of milliseconds to wait before start calculating aborts " \
-          "for RTM locking")                                                \
-                                                                            \
-  product(bool, UseRTMXendForLockBusy, true, EXPERIMENTAL,                  \
-          "Use RTM Xend instead of Xabort when lock busy")
+          "Write 0x0101... to empty memory. Use this to ease debugging.")
 
 // end of ARCH_FLAGS
 
 #endif // CPU_PPC_GLOBALS_PPC_HPP
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
@@ -954,24 +954,23 @@
 
     assert_different_registers(header, object_mark_addr, current_header, tmp);
 
     // markWord displaced_header = obj->mark().set_unlocked();
 
-    // Load markWord from object into header.
-    ld(header, oopDesc::mark_offset_in_bytes(), object);
-
     if (DiagnoseSyncOnValueBasedClasses != 0) {
       load_klass(tmp, object);
       lwz(tmp, in_bytes(Klass::access_flags_offset()), tmp);
       testbitdi(CCR0, R0, tmp, exact_log2(JVM_ACC_IS_VALUE_BASED_CLASS));
       bne(CCR0, slow_case);
     }
 
     if (LockingMode == LM_LIGHTWEIGHT) {
-      lightweight_lock(object, /* mark word */ header, tmp, slow_case);
+      lightweight_lock(object, header, tmp, slow_case);
       b(count_locking);
     } else if (LockingMode == LM_LEGACY) {
+      // Load markWord from object into header.
+      ld(header, oopDesc::mark_offset_in_bytes(), object);
 
       // Set displaced_header to be (markWord of object | UNLOCK_VALUE).
       ori(header, header, markWord::unlocked_value);
 
       // monitor->lock()->set_displaced_header(displaced_header);
@@ -1099,26 +1098,10 @@
 
     // The object address from the monitor is in object.
     ld(object, in_bytes(BasicObjectLock::obj_offset()), monitor);
 
     if (LockingMode == LM_LIGHTWEIGHT) {
-      // Check for non-symmetric locking. This is allowed by the spec and the interpreter
-      // must handle it.
-      Register tmp = current_header;
-      // First check for lock-stack underflow.
-      lwz(tmp, in_bytes(JavaThread::lock_stack_top_offset()), R16_thread);
-      cmplwi(CCR0, tmp, (unsigned)LockStack::start_offset());
-      ble(CCR0, slow_case);
-      // Then check if the top of the lock-stack matches the unlocked object.
-      addi(tmp, tmp, -oopSize);
-      ldx(tmp, tmp, R16_thread);
-      cmpd(CCR0, tmp, object);
-      bne(CCR0, slow_case);
-
-      ld(header, oopDesc::mark_offset_in_bytes(), object);
-      andi_(R0, header, markWord::monitor_value);
-      bne(CCR0, slow_case);
       lightweight_unlock(object, header, slow_case);
     } else {
       addi(object_mark_addr, object, oopDesc::mark_offset_in_bytes());
 
       // We have the displaced header in displaced_header. If the lock is still
diff a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
--- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
+++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
@@ -33,10 +33,11 @@
 #include "memory/resourceArea.hpp"
 #include "nativeInst_ppc.hpp"
 #include "oops/klass.inline.hpp"
 #include "oops/methodData.hpp"
 #include "prims/methodHandles.hpp"
+#include "register_ppc.hpp"
 #include "runtime/icache.hpp"
 #include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/objectMonitor.hpp"
 #include "runtime/os.hpp"
 #include "runtime/safepoint.hpp"
@@ -2171,467 +2172,15 @@
   // End the stub.
   end_a_stub();
   return stub;
 }
 
-// TM on PPC64.
-void MacroAssembler::atomic_inc_ptr(Register addr, Register result, int simm16) {
-  Label retry;
-  bind(retry);
-  ldarx(result, addr, /*hint*/ false);
-  addi(result, result, simm16);
-  stdcx_(result, addr);
-  if (UseStaticBranchPredictionInCompareAndSwapPPC64) {
-    bne_predict_not_taken(CCR0, retry); // stXcx_ sets CCR0
-  } else {
-    bne(                  CCR0, retry); // stXcx_ sets CCR0
-  }
-}
-
-void MacroAssembler::atomic_ori_int(Register addr, Register result, int uimm16) {
-  Label retry;
-  bind(retry);
-  lwarx(result, addr, /*hint*/ false);
-  ori(result, result, uimm16);
-  stwcx_(result, addr);
-  if (UseStaticBranchPredictionInCompareAndSwapPPC64) {
-    bne_predict_not_taken(CCR0, retry); // stXcx_ sets CCR0
-  } else {
-    bne(                  CCR0, retry); // stXcx_ sets CCR0
-  }
-}
-
-#if INCLUDE_RTM_OPT
-
-// Update rtm_counters based on abort status
-// input: abort_status
-//        rtm_counters_Reg (RTMLockingCounters*)
-void MacroAssembler::rtm_counters_update(Register abort_status, Register rtm_counters_Reg) {
-  // Mapping to keep PreciseRTMLockingStatistics similar to x86.
-  // x86 ppc (! means inverted, ? means not the same)
-  //  0   31  Set if abort caused by XABORT instruction.
-  //  1  ! 7  If set, the transaction may succeed on a retry. This bit is always clear if bit 0 is set.
-  //  2   13  Set if another logical processor conflicted with a memory address that was part of the transaction that aborted.
-  //  3   10  Set if an internal buffer overflowed.
-  //  4  ?12  Set if a debug breakpoint was hit.
-  //  5  ?32  Set if an abort occurred during execution of a nested transaction.
-  const int failure_bit[] = {tm_tabort, // Signal handler will set this too.
-                             tm_failure_persistent,
-                             tm_non_trans_cf,
-                             tm_trans_cf,
-                             tm_footprint_of,
-                             tm_failure_code,
-                             tm_transaction_level};
-
-  const int num_failure_bits = sizeof(failure_bit) / sizeof(int);
-  const int num_counters = RTMLockingCounters::ABORT_STATUS_LIMIT;
-
-  const int bit2counter_map[][num_counters] =
-  // 0 = no map; 1 = mapped, no inverted logic; -1 = mapped, inverted logic
-  // Inverted logic means that if a bit is set don't count it, or vice-versa.
-  // Care must be taken when mapping bits to counters as bits for a given
-  // counter must be mutually exclusive. Otherwise, the counter will be
-  // incremented more than once.
-  // counters:
-  // 0        1        2         3         4         5
-  // abort  , persist, conflict, overflow, debug   , nested         bits:
-  {{ 1      , 0      , 0       , 0       , 0       , 0      },   // abort
-   { 0      , -1     , 0       , 0       , 0       , 0      },   // failure_persistent
-   { 0      , 0      , 1       , 0       , 0       , 0      },   // non_trans_cf
-   { 0      , 0      , 1       , 0       , 0       , 0      },   // trans_cf
-   { 0      , 0      , 0       , 1       , 0       , 0      },   // footprint_of
-   { 0      , 0      , 0       , 0       , -1      , 0      },   // failure_code = 0xD4
-   { 0      , 0      , 0       , 0       , 0       , 1      }};  // transaction_level > 1
-  // ...
-
-  // Move abort_status value to R0 and use abort_status register as a
-  // temporary register because R0 as third operand in ld/std is treated
-  // as base address zero (value). Likewise, R0 as second operand in addi
-  // is problematic because it amounts to li.
-  const Register temp_Reg = abort_status;
-  const Register abort_status_R0 = R0;
-  mr(abort_status_R0, abort_status);
-
-  // Increment total abort counter.
-  int counters_offs = RTMLockingCounters::abort_count_offset();
-  ld(temp_Reg, counters_offs, rtm_counters_Reg);
-  addi(temp_Reg, temp_Reg, 1);
-  std(temp_Reg, counters_offs, rtm_counters_Reg);
-
-  // Increment specific abort counters.
-  if (PrintPreciseRTMLockingStatistics) {
-
-    // #0 counter offset.
-    int abortX_offs = RTMLockingCounters::abortX_count_offset();
-
-    for (int nbit = 0; nbit < num_failure_bits; nbit++) {
-      for (int ncounter = 0; ncounter < num_counters; ncounter++) {
-        if (bit2counter_map[nbit][ncounter] != 0) {
-          Label check_abort;
-          int abort_counter_offs = abortX_offs + (ncounter << 3);
-
-          if (failure_bit[nbit] == tm_transaction_level) {
-            // Don't check outer transaction, TL = 1 (bit 63). Hence only
-            // 11 bits in the TL field are checked to find out if failure
-            // occurred in a nested transaction. This check also matches
-            // the case when nesting_of = 1 (nesting overflow).
-            rldicr_(temp_Reg, abort_status_R0, failure_bit[nbit], 10);
-          } else if (failure_bit[nbit] == tm_failure_code) {
-            // Check failure code for trap or illegal caught in TM.
-            // Bits 0:7 are tested as bit 7 (persistent) is copied from
-            // tabort or treclaim source operand.
-            // On Linux: trap or illegal is TM_CAUSE_SIGNAL (0xD4).
-            rldicl(temp_Reg, abort_status_R0, 8, 56);
-            cmpdi(CCR0, temp_Reg, 0xD4);
-          } else {
-            rldicr_(temp_Reg, abort_status_R0, failure_bit[nbit], 0);
-          }
-
-          if (bit2counter_map[nbit][ncounter] == 1) {
-            beq(CCR0, check_abort);
-          } else {
-            bne(CCR0, check_abort);
-          }
-
-          // We don't increment atomically.
-          ld(temp_Reg, abort_counter_offs, rtm_counters_Reg);
-          addi(temp_Reg, temp_Reg, 1);
-          std(temp_Reg, abort_counter_offs, rtm_counters_Reg);
-
-          bind(check_abort);
-        }
-      }
-    }
-  }
-  // Restore abort_status.
-  mr(abort_status, abort_status_R0);
-}
-
-// Branch if (random & (count-1) != 0), count is 2^n
-// tmp and CR0 are killed
-void MacroAssembler::branch_on_random_using_tb(Register tmp, int count, Label& brLabel) {
-  mftb(tmp);
-  andi_(tmp, tmp, count-1);
-  bne(CCR0, brLabel);
-}
-
-// Perform abort ratio calculation, set no_rtm bit if high ratio.
-// input:  rtm_counters_Reg (RTMLockingCounters* address) - KILLED
-void MacroAssembler::rtm_abort_ratio_calculation(Register rtm_counters_Reg,
-                                                 RTMLockingCounters* rtm_counters,
-                                                 Metadata* method_data) {
-  Label L_done, L_check_always_rtm1, L_check_always_rtm2;
-
-  if (RTMLockingCalculationDelay > 0) {
-    // Delay calculation.
-    ld(rtm_counters_Reg, (RegisterOrConstant)(intptr_t)RTMLockingCounters::rtm_calculation_flag_addr());
-    cmpdi(CCR0, rtm_counters_Reg, 0);
-    beq(CCR0, L_done);
-    load_const_optimized(rtm_counters_Reg, (address)rtm_counters, R0); // reload
-  }
-  // Abort ratio calculation only if abort_count > RTMAbortThreshold.
-  //   Aborted transactions = abort_count * 100
-  //   All transactions = total_count *  RTMTotalCountIncrRate
-  //   Set no_rtm bit if (Aborted transactions >= All transactions * RTMAbortRatio)
-  ld(R0, RTMLockingCounters::abort_count_offset(), rtm_counters_Reg);
-  if (is_simm(RTMAbortThreshold, 16)) {   // cmpdi can handle 16bit immediate only.
-    cmpdi(CCR0, R0, RTMAbortThreshold);
-    blt(CCR0, L_check_always_rtm2);  // reload of rtm_counters_Reg not necessary
-  } else {
-    load_const_optimized(rtm_counters_Reg, RTMAbortThreshold);
-    cmpd(CCR0, R0, rtm_counters_Reg);
-    blt(CCR0, L_check_always_rtm1);  // reload of rtm_counters_Reg required
-  }
-  mulli(R0, R0, 100);
-
-  const Register tmpReg = rtm_counters_Reg;
-  ld(tmpReg, RTMLockingCounters::total_count_offset(), rtm_counters_Reg);
-  mulli(tmpReg, tmpReg, RTMTotalCountIncrRate); // allowable range: int16
-  mulli(tmpReg, tmpReg, RTMAbortRatio);         // allowable range: int16
-  cmpd(CCR0, R0, tmpReg);
-  blt(CCR0, L_check_always_rtm1); // jump to reload
-  if (method_data != nullptr) {
-    // Set rtm_state to "no rtm" in MDO.
-    // Not using a metadata relocation. Method and Class Loader are kept alive anyway.
-    // (See nmethod::metadata_do and CodeBuffer::finalize_oop_references.)
-    load_const(R0, (address)method_data + in_bytes(MethodData::rtm_state_offset()), tmpReg);
-    atomic_ori_int(R0, tmpReg, NoRTM);
-  }
-  b(L_done);
-
-  bind(L_check_always_rtm1);
-  load_const_optimized(rtm_counters_Reg, (address)rtm_counters, R0); // reload
-  bind(L_check_always_rtm2);
-  ld(tmpReg, RTMLockingCounters::total_count_offset(), rtm_counters_Reg);
-  int64_t thresholdValue = RTMLockingThreshold / RTMTotalCountIncrRate;
-  if (is_simm(thresholdValue, 16)) {   // cmpdi can handle 16bit immediate only.
-    cmpdi(CCR0, tmpReg, thresholdValue);
-  } else {
-    load_const_optimized(R0, thresholdValue);
-    cmpd(CCR0, tmpReg, R0);
-  }
-  blt(CCR0, L_done);
-  if (method_data != nullptr) {
-    // Set rtm_state to "always rtm" in MDO.
-    // Not using a metadata relocation. See above.
-    load_const(R0, (address)method_data + in_bytes(MethodData::rtm_state_offset()), tmpReg);
-    atomic_ori_int(R0, tmpReg, UseRTM);
-  }
-  bind(L_done);
-}
-
-// Update counters and perform abort ratio calculation.
-// input: abort_status_Reg
-void MacroAssembler::rtm_profiling(Register abort_status_Reg, Register temp_Reg,
-                                   RTMLockingCounters* rtm_counters,
-                                   Metadata* method_data,
-                                   bool profile_rtm) {
-
-  assert(rtm_counters != nullptr, "should not be null when profiling RTM");
-  // Update rtm counters based on state at abort.
-  // Reads abort_status_Reg, updates flags.
-  assert_different_registers(abort_status_Reg, temp_Reg);
-  load_const_optimized(temp_Reg, (address)rtm_counters, R0);
-  rtm_counters_update(abort_status_Reg, temp_Reg);
-  if (profile_rtm) {
-    assert(rtm_counters != nullptr, "should not be null when profiling RTM");
-    rtm_abort_ratio_calculation(temp_Reg, rtm_counters, method_data);
-  }
-}
-
-// Retry on abort if abort's status indicates non-persistent failure.
-// inputs: retry_count_Reg
-//       : abort_status_Reg
-// output: retry_count_Reg decremented by 1
-void MacroAssembler::rtm_retry_lock_on_abort(Register retry_count_Reg, Register abort_status_Reg,
-                                             Label& retryLabel, Label* checkRetry) {
-  Label doneRetry;
-
-  // Don't retry if failure is persistent.
-  // The persistent bit is set when a (A) Disallowed operation is performed in
-  // transactional state, like for instance trying to write the TFHAR after a
-  // transaction is started; or when there is (B) a Nesting Overflow (too many
-  // nested transactions); or when (C) the Footprint overflows (too many
-  // addresses touched in TM state so there is no more space in the footprint
-  // area to track them); or in case of (D) a Self-Induced Conflict, i.e. a
-  // store is performed to a given address in TM state, then once in suspended
-  // state the same address is accessed. Failure (A) is very unlikely to occur
-  // in the JVM. Failure (D) will never occur because Suspended state is never
-  // used in the JVM. Thus mostly (B) a Nesting Overflow or (C) a Footprint
-  // Overflow will set the persistent bit.
-  rldicr_(R0, abort_status_Reg, tm_failure_persistent, 0);
-  bne(CCR0, doneRetry);
-
-  // Don't retry if transaction was deliberately aborted, i.e. caused by a
-  // tabort instruction.
-  rldicr_(R0, abort_status_Reg, tm_tabort, 0);
-  bne(CCR0, doneRetry);
-
-  // Retry if transaction aborted due to a conflict with another thread.
-  if (checkRetry) { bind(*checkRetry); }
-  addic_(retry_count_Reg, retry_count_Reg, -1);
-  blt(CCR0, doneRetry);
-  b(retryLabel);
-  bind(doneRetry);
-}
-
-// Spin and retry if lock is busy.
-// inputs: owner_addr_Reg (monitor address)
-//       : retry_count_Reg
-// output: retry_count_Reg decremented by 1
-// CTR is killed
-void MacroAssembler::rtm_retry_lock_on_busy(Register retry_count_Reg, Register owner_addr_Reg, Label& retryLabel) {
-  Label SpinLoop, doneRetry, doRetry;
-  addic_(retry_count_Reg, retry_count_Reg, -1);
-  blt(CCR0, doneRetry);
-
-  if (RTMSpinLoopCount > 1) {
-    li(R0, RTMSpinLoopCount);
-    mtctr(R0);
-  }
-
-  // low thread priority
-  smt_prio_low();
-  bind(SpinLoop);
-
-  if (RTMSpinLoopCount > 1) {
-    bdz(doRetry);
-    ld(R0, 0, owner_addr_Reg);
-    cmpdi(CCR0, R0, 0);
-    bne(CCR0, SpinLoop);
-  }
-
-  bind(doRetry);
-
-  // restore thread priority to default in userspace
-#ifdef LINUX
-  smt_prio_medium_low();
-#else
-  smt_prio_medium();
-#endif
-
-  b(retryLabel);
-
-  bind(doneRetry);
-}
-
-// Use RTM for normal stack locks.
-// Input: objReg (object to lock)
-void MacroAssembler::rtm_stack_locking(ConditionRegister flag,
-                                       Register obj, Register mark_word, Register tmp,
-                                       Register retry_on_abort_count_Reg,
-                                       RTMLockingCounters* stack_rtm_counters,
-                                       Metadata* method_data, bool profile_rtm,
-                                       Label& DONE_LABEL, Label& IsInflated) {
-  assert(UseRTMForStackLocks, "why call this otherwise?");
-  Label L_rtm_retry, L_decrement_retry, L_on_abort;
-
-  if (RTMRetryCount > 0) {
-    load_const_optimized(retry_on_abort_count_Reg, RTMRetryCount); // Retry on abort
-    bind(L_rtm_retry);
-  }
-  andi_(R0, mark_word, markWord::monitor_value);  // inflated vs stack-locked|neutral
-  bne(CCR0, IsInflated);
-
-  if (PrintPreciseRTMLockingStatistics || profile_rtm) {
-    Label L_noincrement;
-    if (RTMTotalCountIncrRate > 1) {
-      branch_on_random_using_tb(tmp, RTMTotalCountIncrRate, L_noincrement);
-    }
-    assert(stack_rtm_counters != nullptr, "should not be null when profiling RTM");
-    load_const_optimized(tmp, (address)stack_rtm_counters->total_count_addr(), R0);
-    //atomic_inc_ptr(tmp, /*temp, will be reloaded*/mark_word); We don't increment atomically
-    ldx(mark_word, tmp);
-    addi(mark_word, mark_word, 1);
-    stdx(mark_word, tmp);
-    bind(L_noincrement);
-  }
-  tbegin_();
-  beq(CCR0, L_on_abort);
-  ld(mark_word, oopDesc::mark_offset_in_bytes(), obj);   // Reload in transaction, conflicts need to be tracked.
-  andi(R0, mark_word, markWord::lock_mask_in_place);     // look at 2 lock bits
-  cmpwi(flag, R0, markWord::unlocked_value);             // bits = 01 unlocked
-  beq(flag, DONE_LABEL);                                 // all done if unlocked
-
-  if (UseRTMXendForLockBusy) {
-    tend_();
-    b(L_decrement_retry);
-  } else {
-    tabort_();
-  }
-  bind(L_on_abort);
-  const Register abort_status_Reg = tmp;
-  mftexasr(abort_status_Reg);
-  if (PrintPreciseRTMLockingStatistics || profile_rtm) {
-    rtm_profiling(abort_status_Reg, /*temp*/mark_word, stack_rtm_counters, method_data, profile_rtm);
-  }
-  ld(mark_word, oopDesc::mark_offset_in_bytes(), obj); // reload
-  if (RTMRetryCount > 0) {
-    // Retry on lock abort if abort status is not permanent.
-    rtm_retry_lock_on_abort(retry_on_abort_count_Reg, abort_status_Reg, L_rtm_retry, &L_decrement_retry);
-  } else {
-    bind(L_decrement_retry);
-  }
-}
-
-// Use RTM for inflating locks
-// inputs: obj       (object to lock)
-//         mark_word (current header - KILLED)
-//         boxReg    (on-stack box address (displaced header location) - KILLED)
-void MacroAssembler::rtm_inflated_locking(ConditionRegister flag,
-                                          Register obj, Register mark_word, Register boxReg,
-                                          Register retry_on_busy_count_Reg, Register retry_on_abort_count_Reg,
-                                          RTMLockingCounters* rtm_counters,
-                                          Metadata* method_data, bool profile_rtm,
-                                          Label& DONE_LABEL) {
-  assert(UseRTMLocking, "why call this otherwise?");
-  Label L_rtm_retry, L_decrement_retry, L_on_abort;
-  // Clean monitor_value bit to get valid pointer.
-  int owner_offset = in_bytes(ObjectMonitor::owner_offset()) - markWord::monitor_value;
-
-  // Store non-null, using boxReg instead of (intptr_t)markWord::unused_mark().
-  std(boxReg, BasicLock::displaced_header_offset_in_bytes(), boxReg);
-  const Register tmpReg = boxReg;
-  const Register owner_addr_Reg = mark_word;
-  addi(owner_addr_Reg, mark_word, owner_offset);
-
-  if (RTMRetryCount > 0) {
-    load_const_optimized(retry_on_busy_count_Reg, RTMRetryCount);  // Retry on lock busy.
-    load_const_optimized(retry_on_abort_count_Reg, RTMRetryCount); // Retry on abort.
-    bind(L_rtm_retry);
-  }
-  if (PrintPreciseRTMLockingStatistics || profile_rtm) {
-    Label L_noincrement;
-    if (RTMTotalCountIncrRate > 1) {
-      branch_on_random_using_tb(R0, RTMTotalCountIncrRate, L_noincrement);
-    }
-    assert(rtm_counters != nullptr, "should not be null when profiling RTM");
-    load_const(R0, (address)rtm_counters->total_count_addr(), tmpReg);
-    //atomic_inc_ptr(R0, tmpReg); We don't increment atomically
-    ldx(tmpReg, R0);
-    addi(tmpReg, tmpReg, 1);
-    stdx(tmpReg, R0);
-    bind(L_noincrement);
-  }
-  tbegin_();
-  beq(CCR0, L_on_abort);
-  // We don't reload mark word. Will only be reset at safepoint.
-  ld(R0, 0, owner_addr_Reg); // Load in transaction, conflicts need to be tracked.
-  cmpdi(flag, R0, 0);
-  beq(flag, DONE_LABEL);
-
-  if (UseRTMXendForLockBusy) {
-    tend_();
-    b(L_decrement_retry);
-  } else {
-    tabort_();
-  }
-  bind(L_on_abort);
-  const Register abort_status_Reg = tmpReg;
-  mftexasr(abort_status_Reg);
-  if (PrintPreciseRTMLockingStatistics || profile_rtm) {
-    rtm_profiling(abort_status_Reg, /*temp*/ owner_addr_Reg, rtm_counters, method_data, profile_rtm);
-    // Restore owner_addr_Reg
-    ld(mark_word, oopDesc::mark_offset_in_bytes(), obj);
-#ifdef ASSERT
-    andi_(R0, mark_word, markWord::monitor_value);
-    asm_assert_ne("must be inflated"); // Deflating only allowed at safepoint.
-#endif
-    addi(owner_addr_Reg, mark_word, owner_offset);
-  }
-  if (RTMRetryCount > 0) {
-    // Retry on lock abort if abort status is not permanent.
-    rtm_retry_lock_on_abort(retry_on_abort_count_Reg, abort_status_Reg, L_rtm_retry);
-  }
-
-  // Appears unlocked - try to swing _owner from null to non-null.
-  cmpxchgd(flag, /*current val*/ R0, (intptr_t)0, /*new val*/ R16_thread, owner_addr_Reg,
-           MacroAssembler::MemBarRel | MacroAssembler::MemBarAcq,
-           MacroAssembler::cmpxchgx_hint_acquire_lock(), noreg, &L_decrement_retry, true);
-
-  if (RTMRetryCount > 0) {
-    // success done else retry
-    b(DONE_LABEL);
-    bind(L_decrement_retry);
-    // Spin and retry if lock is busy.
-    rtm_retry_lock_on_busy(retry_on_busy_count_Reg, owner_addr_Reg, L_rtm_retry);
-  } else {
-    bind(L_decrement_retry);
-  }
-}
-
-#endif //  INCLUDE_RTM_OPT
-
 // "The box" is the space on the stack where we copy the object mark.
 void MacroAssembler::compiler_fast_lock_object(ConditionRegister flag, Register oop, Register box,
-                                               Register temp, Register displaced_header, Register current_header,
-                                               RTMLockingCounters* rtm_counters,
-                                               RTMLockingCounters* stack_rtm_counters,
-                                               Metadata* method_data,
-                                               bool use_rtm, bool profile_rtm) {
+                                               Register temp, Register displaced_header, Register current_header) {
+  assert(LockingMode != LM_LIGHTWEIGHT, "uses fast_lock_lightweight");
   assert_different_registers(oop, box, temp, displaced_header, current_header);
-  assert(LockingMode != LM_LIGHTWEIGHT || flag == CCR0, "bad condition register");
   Label object_has_monitor;
   Label cas_failed;
   Label success, failure;
 
   // Load markWord from object into displaced_header.
@@ -2642,28 +2191,21 @@
     lwz(temp, in_bytes(Klass::access_flags_offset()), temp);
     testbitdi(flag, R0, temp, exact_log2(JVM_ACC_IS_VALUE_BASED_CLASS));
     bne(flag, failure);
   }
 
-#if INCLUDE_RTM_OPT
-  if (UseRTMForStackLocks && use_rtm) {
-    rtm_stack_locking(flag, oop, displaced_header, temp, /*temp*/ current_header,
-                      stack_rtm_counters, method_data, profile_rtm,
-                      success, object_has_monitor);
-  }
-#endif // INCLUDE_RTM_OPT
-
   // Handle existing monitor.
   // The object has an existing monitor iff (mark & monitor_value) != 0.
   andi_(temp, displaced_header, markWord::monitor_value);
   bne(CCR0, object_has_monitor);
 
   if (LockingMode == LM_MONITOR) {
     // Set NE to indicate 'failure' -> take slow-path.
     crandc(flag, Assembler::equal, flag, Assembler::equal);
     b(failure);
-  } else if (LockingMode == LM_LEGACY) {
+  } else {
+    assert(LockingMode == LM_LEGACY, "must be");
     // Set displaced_header to be (markWord of object | UNLOCK_VALUE).
     ori(displaced_header, displaced_header, markWord::unlocked_value);
 
     // Load Compare Value application register.
 
@@ -2703,44 +2245,29 @@
     if (flag != CCR0) {
       mcrf(flag, CCR0);
     }
     beq(CCR0, success);
     b(failure);
-  } else {
-    assert(LockingMode == LM_LIGHTWEIGHT, "must be");
-    lightweight_lock(oop, displaced_header, temp, failure);
-    b(success);
   }
 
   // Handle existing monitor.
   bind(object_has_monitor);
   // The object's monitor m is unlocked iff m->owner is null,
   // otherwise m->owner may contain a thread or a stack address.
 
-#if INCLUDE_RTM_OPT
-  // Use the same RTM locking code in 32- and 64-bit VM.
-  if (use_rtm) {
-    rtm_inflated_locking(flag, oop, displaced_header, box, temp, /*temp*/ current_header,
-                         rtm_counters, method_data, profile_rtm, success);
-    bne(flag, failure);
-  } else {
-#endif // INCLUDE_RTM_OPT
-
   // Try to CAS m->owner from null to current thread.
   addi(temp, displaced_header, in_bytes(ObjectMonitor::owner_offset()) - markWord::monitor_value);
   cmpxchgd(/*flag=*/flag,
            /*current_value=*/current_header,
            /*compare_value=*/(intptr_t)0,
            /*exchange_value=*/R16_thread,
            /*where=*/temp,
            MacroAssembler::MemBarRel | MacroAssembler::MemBarAcq,
            MacroAssembler::cmpxchgx_hint_acquire_lock());
 
-  if (LockingMode != LM_LIGHTWEIGHT) {
-    // Store a non-null value into the box.
-    std(box, BasicLock::displaced_header_offset_in_bytes(), box);
-  }
+  // Store a non-null value into the box.
+  std(box, BasicLock::displaced_header_offset_in_bytes(), box);
   beq(flag, success);
 
   // Check for recursive locking.
   cmpd(flag, current_header, R16_thread);
   bne(flag, failure);
@@ -2749,41 +2276,23 @@
   Register recursions = displaced_header;
   ld(recursions, in_bytes(ObjectMonitor::recursions_offset() - ObjectMonitor::owner_offset()), temp);
   addi(recursions, recursions, 1);
   std(recursions, in_bytes(ObjectMonitor::recursions_offset() - ObjectMonitor::owner_offset()), temp);
 
-#if INCLUDE_RTM_OPT
-  } // use_rtm()
-#endif
-
   // flag == EQ indicates success, increment held monitor count
   // flag == NE indicates failure
   bind(success);
   inc_held_monitor_count(temp);
   bind(failure);
 }
 
 void MacroAssembler::compiler_fast_unlock_object(ConditionRegister flag, Register oop, Register box,
-                                                 Register temp, Register displaced_header, Register current_header,
-                                                 bool use_rtm) {
+                                                 Register temp, Register displaced_header, Register current_header) {
+  assert(LockingMode != LM_LIGHTWEIGHT, "uses fast_unlock_lightweight");
   assert_different_registers(oop, box, temp, displaced_header, current_header);
-  assert(LockingMode != LM_LIGHTWEIGHT || flag == CCR0, "bad condition register");
   Label success, failure, object_has_monitor, notRecursive;
 
-#if INCLUDE_RTM_OPT
-  if (UseRTMForStackLocks && use_rtm) {
-    Label L_regular_unlock;
-    ld(current_header, oopDesc::mark_offset_in_bytes(), oop);   // fetch markword
-    andi(R0, current_header, markWord::lock_mask_in_place);     // look at 2 lock bits
-    cmpwi(flag, R0, markWord::unlocked_value);                  // bits = 01 unlocked
-    bne(flag, L_regular_unlock);                                // else RegularLock
-    tend_();                                                    // otherwise end...
-    b(success);                                                 // ... and we're done
-    bind(L_regular_unlock);
-  }
-#endif
-
   if (LockingMode == LM_LEGACY) {
     // Find the lock address and load the displaced header from the stack.
     ld(displaced_header, BasicLock::displaced_header_offset_in_bytes(), box);
 
     // If the displaced header is 0, we have a recursive unlock.
@@ -2791,20 +2300,20 @@
     beq(flag, success);
   }
 
   // Handle existing monitor.
   // The object has an existing monitor iff (mark & monitor_value) != 0.
-  RTM_OPT_ONLY( if (!(UseRTMForStackLocks && use_rtm)) ) // skip load if already done
   ld(current_header, oopDesc::mark_offset_in_bytes(), oop);
   andi_(R0, current_header, markWord::monitor_value);
   bne(CCR0, object_has_monitor);
 
   if (LockingMode == LM_MONITOR) {
     // Set NE to indicate 'failure' -> take slow-path.
     crandc(flag, Assembler::equal, flag, Assembler::equal);
     b(failure);
-  } else if (LockingMode == LM_LEGACY) {
+  } else {
+    assert(LockingMode == LM_LEGACY, "must be");
     // Check if it is still a light weight lock, this is is true if we see
     // the stack address of the basicLock in the markWord of the object.
     // Cmpxchg sets flag to cmpd(current_header, box).
     cmpxchgd(/*flag=*/flag,
              /*current_value=*/current_header,
@@ -2815,35 +2324,18 @@
              MacroAssembler::cmpxchgx_hint_release_lock(),
              noreg,
              &failure);
     assert(oopDesc::mark_offset_in_bytes() == 0, "offset of _mark is not 0");
     b(success);
-  } else {
-    assert(LockingMode == LM_LIGHTWEIGHT, "must be");
-    lightweight_unlock(oop, current_header, failure);
-    b(success);
   }
 
   // Handle existing monitor.
   bind(object_has_monitor);
   STATIC_ASSERT(markWord::monitor_value <= INT_MAX);
   addi(current_header, current_header, -(int)markWord::monitor_value); // monitor
   ld(temp,             in_bytes(ObjectMonitor::owner_offset()), current_header);
 
-  // It's inflated.
-#if INCLUDE_RTM_OPT
-  if (use_rtm) {
-    Label L_regular_inflated_unlock;
-    // Clean monitor_value bit to get valid pointer
-    cmpdi(flag, temp, 0);
-    bne(flag, L_regular_inflated_unlock);
-    tend_();
-    b(success);
-    bind(L_regular_inflated_unlock);
-  }
-#endif
-
   // In case of LM_LIGHTWEIGHT, we may reach here with (temp & ObjectMonitor::ANONYMOUS_OWNER) != 0.
   // This is handled like owner thread mismatches: We take the slow path.
   cmpd(flag, temp, R16_thread);
   bne(flag, failure);
 
@@ -2871,10 +2363,280 @@
   bind(success);
   dec_held_monitor_count(temp);
   bind(failure);
 }
 
+void MacroAssembler::compiler_fast_lock_lightweight_object(ConditionRegister flag, Register obj, Register tmp1,
+                                                           Register tmp2, Register tmp3) {
+  assert_different_registers(obj, tmp1, tmp2, tmp3);
+  assert(flag == CCR0, "bad condition register");
+
+  // Handle inflated monitor.
+  Label inflated;
+  // Finish fast lock successfully. MUST reach to with flag == NE
+  Label locked;
+  // Finish fast lock unsuccessfully. MUST branch to with flag == EQ
+  Label slow_path;
+
+  if (DiagnoseSyncOnValueBasedClasses != 0) {
+    load_klass(tmp1, obj);
+    lwz(tmp1, in_bytes(Klass::access_flags_offset()), tmp1);
+    testbitdi(flag, R0, tmp1, exact_log2(JVM_ACC_IS_VALUE_BASED_CLASS));
+    bne(flag, slow_path);
+  }
+
+  const Register mark = tmp1;
+  const Register t = tmp3; // Usage of R0 allowed!
+
+  { // Lightweight locking
+
+    // Push lock to the lock stack and finish successfully. MUST reach to with flag == EQ
+    Label push;
+
+    const Register top = tmp2;
+
+    // Check if lock-stack is full.
+    lwz(top, in_bytes(JavaThread::lock_stack_top_offset()), R16_thread);
+    cmplwi(flag, top, LockStack::end_offset() - 1);
+    bgt(flag, slow_path);
+
+    // The underflow check is elided. The recursive check will always fail
+    // when the lock stack is empty because of the _bad_oop_sentinel field.
+
+    // Check if recursive.
+    subi(t, top, oopSize);
+    ldx(t, R16_thread, t);
+    cmpd(flag, obj, t);
+    beq(flag, push);
+
+    // Check for monitor (0b10) or locked (0b00).
+    ld(mark, oopDesc::mark_offset_in_bytes(), obj);
+    andi_(t, mark, markWord::lock_mask_in_place);
+    cmpldi(flag, t, markWord::unlocked_value);
+    bgt(flag, inflated);
+    bne(flag, slow_path);
+
+    // Not inflated.
+
+    // Try to lock. Transition lock bits 0b00 => 0b01
+    assert(oopDesc::mark_offset_in_bytes() == 0, "required to avoid a lea");
+    atomically_flip_locked_state(/* is_unlock */ false, obj, mark, slow_path, MacroAssembler::MemBarAcq);
+
+    bind(push);
+    // After successful lock, push object on lock-stack.
+    stdx(obj, R16_thread, top);
+    addi(top, top, oopSize);
+    stw(top, in_bytes(JavaThread::lock_stack_top_offset()), R16_thread);
+    b(locked);
+  }
+
+  { // Handle inflated monitor.
+    bind(inflated);
+
+    // mark contains the tagged ObjectMonitor*.
+    const Register tagged_monitor = mark;
+    const uintptr_t monitor_tag = markWord::monitor_value;
+    const Register owner_addr = tmp2;
+
+    // Compute owner address.
+    addi(owner_addr, tagged_monitor, in_bytes(ObjectMonitor::owner_offset()) - monitor_tag);
+
+    // CAS owner (null => current thread).
+    cmpxchgd(/*flag=*/flag,
+            /*current_value=*/t,
+            /*compare_value=*/(intptr_t)0,
+            /*exchange_value=*/R16_thread,
+            /*where=*/owner_addr,
+            MacroAssembler::MemBarRel | MacroAssembler::MemBarAcq,
+            MacroAssembler::cmpxchgx_hint_acquire_lock());
+    beq(flag, locked);
+
+    // Check if recursive.
+    cmpd(flag, t, R16_thread);
+    bne(flag, slow_path);
+
+    // Recursive.
+    ld(tmp1, in_bytes(ObjectMonitor::recursions_offset() - ObjectMonitor::owner_offset()), owner_addr);
+    addi(tmp1, tmp1, 1);
+    std(tmp1, in_bytes(ObjectMonitor::recursions_offset() - ObjectMonitor::owner_offset()), owner_addr);
+  }
+
+  bind(locked);
+  inc_held_monitor_count(tmp1);
+
+#ifdef ASSERT
+  // Check that locked label is reached with flag == EQ.
+  Label flag_correct;
+  beq(flag, flag_correct);
+  stop("Fast Lock Flag != EQ");
+#endif
+  bind(slow_path);
+#ifdef ASSERT
+  // Check that slow_path label is reached with flag == NE.
+  bne(flag, flag_correct);
+  stop("Fast Lock Flag != NE");
+  bind(flag_correct);
+#endif
+  // C2 uses the value of flag (NE vs EQ) to determine the continuation.
+}
+
+void MacroAssembler::compiler_fast_unlock_lightweight_object(ConditionRegister flag, Register obj, Register tmp1,
+                                                             Register tmp2, Register tmp3) {
+  assert_different_registers(obj, tmp1, tmp2, tmp3);
+  assert(flag == CCR0, "bad condition register");
+
+  // Handle inflated monitor.
+  Label inflated, inflated_load_monitor;
+  // Finish fast unlock successfully. MUST reach to with flag == EQ.
+  Label unlocked;
+  // Finish fast unlock unsuccessfully. MUST branch to with flag == NE.
+  Label slow_path;
+
+  const Register mark = tmp1;
+  const Register top = tmp2;
+  const Register t = tmp3;
+
+  { // Lightweight unlock
+    Label push_and_slow;
+
+    // Check if obj is top of lock-stack.
+    lwz(top, in_bytes(JavaThread::lock_stack_top_offset()), R16_thread);
+    subi(top, top, oopSize);
+    ldx(t, R16_thread, top);
+    cmpd(flag, obj, t);
+    // Top of lock stack was not obj. Must be monitor.
+    bne(flag, inflated_load_monitor);
+
+    // Pop lock-stack.
+    DEBUG_ONLY(li(t, 0);)
+    DEBUG_ONLY(stdx(t, R16_thread, top);)
+    stw(top, in_bytes(JavaThread::lock_stack_top_offset()), R16_thread);
+
+    // The underflow check is elided. The recursive check will always fail
+    // when the lock stack is empty because of the _bad_oop_sentinel field.
+
+    // Check if recursive.
+    subi(t, top, oopSize);
+    ldx(t, R16_thread, t);
+    cmpd(flag, obj, t);
+    beq(flag, unlocked);
+
+    // Not recursive.
+
+    // Check for monitor (0b10).
+    ld(mark, oopDesc::mark_offset_in_bytes(), obj);
+    andi_(t, mark, markWord::monitor_value);
+    bne(CCR0, inflated);
+
+#ifdef ASSERT
+    // Check header not unlocked (0b01).
+    Label not_unlocked;
+    andi_(t, mark, markWord::unlocked_value);
+    beq(CCR0, not_unlocked);
+    stop("lightweight_unlock already unlocked");
+    bind(not_unlocked);
+#endif
+
+    // Try to unlock. Transition lock bits 0b00 => 0b01
+    atomically_flip_locked_state(/* is_unlock */ true, obj, mark, push_and_slow, MacroAssembler::MemBarRel);
+    b(unlocked);
+
+    bind(push_and_slow);
+    // Restore lock-stack and handle the unlock in runtime.
+    DEBUG_ONLY(stdx(obj, R16_thread, top);)
+    addi(top, top, oopSize);
+    stw(top, in_bytes(JavaThread::lock_stack_top_offset()), R16_thread);
+    b(slow_path);
+  }
+
+  { // Handle inflated monitor.
+    bind(inflated_load_monitor);
+    ld(mark, oopDesc::mark_offset_in_bytes(), obj);
+#ifdef ASSERT
+    andi_(t, mark, markWord::monitor_value);
+    bne(CCR0, inflated);
+    stop("Fast Unlock not monitor");
+#endif
+
+    bind(inflated);
+
+#ifdef ASSERT
+    Label check_done;
+    subi(top, top, oopSize);
+    cmplwi(CCR0, top, in_bytes(JavaThread::lock_stack_base_offset()));
+    blt(CCR0, check_done);
+    ldx(t, R16_thread, top);
+    cmpd(flag, obj, t);
+    bne(flag, inflated);
+    stop("Fast Unlock lock on stack");
+    bind(check_done);
+#endif
+
+    // mark contains the tagged ObjectMonitor*.
+    const Register monitor = mark;
+    const uintptr_t monitor_tag = markWord::monitor_value;
+
+    // Untag the monitor.
+    subi(monitor, mark, monitor_tag);
+
+    const Register recursions = tmp2;
+    Label not_recursive;
+
+    // Check if recursive.
+    ld(recursions, in_bytes(ObjectMonitor::recursions_offset()), monitor);
+    addic_(recursions, recursions, -1);
+    blt(CCR0, not_recursive);
+
+    // Recursive unlock.
+    std(recursions, in_bytes(ObjectMonitor::recursions_offset()), monitor);
+    crorc(CCR0, Assembler::equal, CCR0, Assembler::equal);
+    b(unlocked);
+
+    bind(not_recursive);
+
+    Label release_;
+    const Register t2 = tmp2;
+
+    // Check if the entry lists are empty.
+    ld(t, in_bytes(ObjectMonitor::EntryList_offset()), monitor);
+    ld(t2, in_bytes(ObjectMonitor::cxq_offset()), monitor);
+    orr(t, t, t2);
+    cmpdi(flag, t, 0);
+    beq(flag, 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.
+    std(R16_thread, in_bytes(ObjectMonitor::owner_offset()), monitor);
+    b(slow_path);
+
+    bind(release_);
+    // Set owner to null.
+    release();
+    // t contains 0
+    std(t, in_bytes(ObjectMonitor::owner_offset()), monitor);
+  }
+
+  bind(unlocked);
+  dec_held_monitor_count(t);
+
+#ifdef ASSERT
+  // Check that unlocked label is reached with flag == EQ.
+  Label flag_correct;
+  beq(flag, flag_correct);
+  stop("Fast Lock Flag != EQ");
+#endif
+  bind(slow_path);
+#ifdef ASSERT
+  // Check that slow_path label is reached with flag == NE.
+  bne(flag, flag_correct);
+  stop("Fast Lock Flag != NE");
+  bind(flag_correct);
+#endif
+  // C2 uses the value of flag (NE vs EQ) to determine the continuation.
+}
+
 void MacroAssembler::safepoint_poll(Label& slow_path, Register temp, bool at_return, bool in_nmethod) {
   ld(temp, in_bytes(JavaThread::polling_word_offset()), R16_thread);
 
   if (at_return) {
     if (in_nmethod) {
@@ -4490,95 +4252,129 @@
     isync();
   }
 }
 
 // Implements lightweight-locking.
-// Branches to slow upon failure to lock the object, with CCR0 NE.
-// Falls through upon success with CCR0 EQ.
 //
 //  - obj: the object to be locked
-//  - hdr: the header, already loaded from obj, will be destroyed
-//  - t1: temporary register
-void MacroAssembler::lightweight_lock(Register obj, Register hdr, Register t1, Label& slow) {
+//  - t1, t2: temporary register
+void MacroAssembler::lightweight_lock(Register obj, Register t1, Register t2, Label& slow) {
   assert(LockingMode == LM_LIGHTWEIGHT, "only used with new lightweight locking");
-  assert_different_registers(obj, hdr, t1);
+  assert_different_registers(obj, t1, t2);
+
+  Label push;
+  const Register top = t1;
+  const Register mark = t2;
+  const Register t = R0;
+
+  // Check if the lock-stack is full.
+  lwz(top, in_bytes(JavaThread::lock_stack_top_offset()), R16_thread);
+  cmplwi(CCR0, top, LockStack::end_offset());
+  bge(CCR0, slow);
+
+  // The underflow check is elided. The recursive check will always fail
+  // when the lock stack is empty because of the _bad_oop_sentinel field.
 
-  // Check if we would have space on lock-stack for the object.
-  lwz(t1, in_bytes(JavaThread::lock_stack_top_offset()), R16_thread);
-  cmplwi(CCR0, t1, LockStack::end_offset() - 1);
-  bgt(CCR0, slow);
+  // Check for recursion.
+  subi(t, top, oopSize);
+  ldx(t, R16_thread, t);
+  cmpd(CCR0, obj, t);
+  beq(CCR0, push);
 
-  // Quick check: Do not reserve cache line for atomic update if not unlocked.
-  // (Similar to contention_hint in cmpxchg solutions.)
-  xori(R0, hdr, markWord::unlocked_value); // flip unlocked bit
-  andi_(R0, R0, markWord::lock_mask_in_place);
-  bne(CCR0, slow); // failed if new header doesn't contain locked_value (which is 0)
+  // Check header for monitor (0b10) or locked (0b00).
+  ld(mark, oopDesc::mark_offset_in_bytes(), obj);
+  xori(t, mark, markWord::unlocked_value);
+  andi_(t, t, markWord::lock_mask_in_place);
+  bne(CCR0, slow);
 
-  // Note: We're not publishing anything (like the displaced header in LM_LEGACY)
-  // to other threads at this point. Hence, no release barrier, here.
-  // (The obj has been written to the BasicObjectLock at obj_offset() within the own thread stack.)
-  atomically_flip_locked_state(/* is_unlock */ false, obj, hdr, slow, MacroAssembler::MemBarAcq);
+  // Try to lock. Transition lock bits 0b00 => 0b01
+  atomically_flip_locked_state(/* is_unlock */ false, obj, mark, slow, MacroAssembler::MemBarAcq);
 
+  bind(push);
   // After successful lock, push object on lock-stack
-  stdx(obj, t1, R16_thread);
-  addi(t1, t1, oopSize);
-  stw(t1, in_bytes(JavaThread::lock_stack_top_offset()), R16_thread);
+  stdx(obj, R16_thread, top);
+  addi(top, top, oopSize);
+  stw(top, in_bytes(JavaThread::lock_stack_top_offset()), R16_thread);
 }
 
 // Implements lightweight-unlocking.
-// Branches to slow upon failure, with CCR0 NE.
-// Falls through upon success, with CCR0 EQ.
 //
 // - obj: the object to be unlocked
-// - hdr: the (pre-loaded) header of the object, will be destroyed
-void MacroAssembler::lightweight_unlock(Register obj, Register hdr, Label& slow) {
+//  - t1: temporary register
+void MacroAssembler::lightweight_unlock(Register obj, Register t1, Label& slow) {
   assert(LockingMode == LM_LIGHTWEIGHT, "only used with new lightweight locking");
-  assert_different_registers(obj, hdr);
+  assert_different_registers(obj, t1);
 
 #ifdef ASSERT
-  {
-    // Check that hdr is fast-locked.
-    Label hdr_ok;
-    andi_(R0, hdr, markWord::lock_mask_in_place);
-    beq(CCR0, hdr_ok);
-    stop("Header is not fast-locked");
-    bind(hdr_ok);
-  }
-  Register t1 = hdr; // Reuse in debug build.
   {
     // The following checks rely on the fact that LockStack is only ever modified by
     // its owning thread, even if the lock got inflated concurrently; removal of LockStack
     // entries after inflation will happen delayed in that case.
 
     // Check for lock-stack underflow.
     Label stack_ok;
     lwz(t1, in_bytes(JavaThread::lock_stack_top_offset()), R16_thread);
     cmplwi(CCR0, t1, LockStack::start_offset());
-    bgt(CCR0, stack_ok);
+    bge(CCR0, stack_ok);
     stop("Lock-stack underflow");
     bind(stack_ok);
   }
-  {
-    // Check if the top of the lock-stack matches the unlocked object.
-    Label tos_ok;
-    addi(t1, t1, -oopSize);
-    ldx(t1, t1, R16_thread);
-    cmpd(CCR0, t1, obj);
-    beq(CCR0, tos_ok);
-    stop("Top of lock-stack does not match the unlocked object");
-    bind(tos_ok);
-  }
 #endif
 
-  // Release the lock.
-  atomically_flip_locked_state(/* is_unlock */ true, obj, hdr, slow, MacroAssembler::MemBarRel);
+  Label unlocked, push_and_slow;
+  const Register top = t1;
+  const Register mark = R0;
+  Register t = R0;
+
+  // Check if obj is top of lock-stack.
+  lwz(top, in_bytes(JavaThread::lock_stack_top_offset()), R16_thread);
+  subi(top, top, oopSize);
+  ldx(t, R16_thread, top);
+  cmpd(CCR0, obj, t);
+  bne(CCR0, slow);
+
+  // Pop lock-stack.
+  DEBUG_ONLY(li(t, 0);)
+  DEBUG_ONLY(stdx(t, R16_thread, top);)
+  stw(top, in_bytes(JavaThread::lock_stack_top_offset()), R16_thread);
+
+  // The underflow check is elided. The recursive check will always fail
+  // when the lock stack is empty because of the _bad_oop_sentinel field.
+
+  // Check if recursive.
+  subi(t, top, oopSize);
+  ldx(t, R16_thread, t);
+  cmpd(CCR0, obj, t);
+  beq(CCR0, unlocked);
+
+  // Use top as tmp
+  t = top;
+
+  // Not recursive. Check header for monitor (0b10).
+  ld(mark, oopDesc::mark_offset_in_bytes(), obj);
+  andi_(t, mark, markWord::monitor_value);
+  bne(CCR0, push_and_slow);
 
-  // After successful unlock, pop object from lock-stack
-  Register t2 = hdr;
-  lwz(t2, in_bytes(JavaThread::lock_stack_top_offset()), R16_thread);
-  addi(t2, t2, -oopSize);
 #ifdef ASSERT
-  li(R0, 0);
-  stdx(R0, t2, R16_thread);
+  // Check header not unlocked (0b01).
+  Label not_unlocked;
+  andi_(t, mark, markWord::unlocked_value);
+  beq(CCR0, not_unlocked);
+  stop("lightweight_unlock already unlocked");
+  bind(not_unlocked);
 #endif
-  stw(t2, in_bytes(JavaThread::lock_stack_top_offset()), R16_thread);
+
+  // Try to unlock. Transition lock bits 0b00 => 0b01
+  atomically_flip_locked_state(/* is_unlock */ true, obj, t, push_and_slow, MacroAssembler::MemBarRel);
+  b(unlocked);
+
+  bind(push_and_slow);
+
+  // Restore lock-stack and handle the unlock in runtime.
+  lwz(top, in_bytes(JavaThread::lock_stack_top_offset()), R16_thread);
+  DEBUG_ONLY(stdx(obj, R16_thread, top);)
+  addi(top, top, oopSize);
+  stw(top, in_bytes(JavaThread::lock_stack_top_offset()), R16_thread);
+  b(slow);
+
+  bind(unlocked);
 }
diff a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp
--- a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp
+++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp
@@ -26,11 +26,10 @@
 #ifndef CPU_PPC_MACROASSEMBLER_PPC_HPP
 #define CPU_PPC_MACROASSEMBLER_PPC_HPP
 
 #include "asm/assembler.hpp"
 #include "oops/accessDecorators.hpp"
-#include "runtime/rtmLocking.hpp"
 #include "utilities/macros.hpp"
 
 // MacroAssembler extends Assembler by a few frequently used macros.
 
 class ciTypeArray;
@@ -605,12 +604,12 @@
   void push_cont_fastpath();
   void pop_cont_fastpath();
   void inc_held_monitor_count(Register tmp);
   void dec_held_monitor_count(Register tmp);
   void atomically_flip_locked_state(bool is_unlock, Register obj, Register tmp, Label& failed, int semantics);
-  void lightweight_lock(Register obj, Register hdr, Register t1, Label& slow);
-  void lightweight_unlock(Register obj, Register hdr, Label& slow);
+  void lightweight_lock(Register obj, Register t1, Register t2, Label& slow);
+  void lightweight_unlock(Register obj, Register t1, Label& slow);
 
   // allocation (for C1)
   void tlab_allocate(
     Register obj,                      // result: pointer to object after successful allocation
     Register var_size_in_bytes,        // object size in bytes if unknown at compile time; invalid otherwise
@@ -621,45 +620,21 @@
   void incr_allocated_bytes(RegisterOrConstant size_in_bytes, Register t1, Register t2);
 
   enum { trampoline_stub_size = 6 * 4 };
   address emit_trampoline_stub(int destination_toc_offset, int insts_call_instruction_offset, Register Rtoc = noreg);
 
-  void atomic_inc_ptr(Register addr, Register result, int simm16 = 1);
-  void atomic_ori_int(Register addr, Register result, int uimm16);
-
-#if INCLUDE_RTM_OPT
-  void rtm_counters_update(Register abort_status, Register rtm_counters);
-  void branch_on_random_using_tb(Register tmp, int count, Label& brLabel);
-  void rtm_abort_ratio_calculation(Register rtm_counters_reg, RTMLockingCounters* rtm_counters,
-                                   Metadata* method_data);
-  void rtm_profiling(Register abort_status_Reg, Register temp_Reg,
-                     RTMLockingCounters* rtm_counters, Metadata* method_data, bool profile_rtm);
-  void rtm_retry_lock_on_abort(Register retry_count, Register abort_status,
-                               Label& retryLabel, Label* checkRetry = nullptr);
-  void rtm_retry_lock_on_busy(Register retry_count, Register owner_addr, Label& retryLabel);
-  void rtm_stack_locking(ConditionRegister flag, Register obj, Register mark_word, Register tmp,
-                         Register retry_on_abort_count,
-                         RTMLockingCounters* stack_rtm_counters,
-                         Metadata* method_data, bool profile_rtm,
-                         Label& DONE_LABEL, Label& IsInflated);
-  void rtm_inflated_locking(ConditionRegister flag, Register obj, Register mark_word, Register box,
-                            Register retry_on_busy_count, Register retry_on_abort_count,
-                            RTMLockingCounters* rtm_counters,
-                            Metadata* method_data, bool profile_rtm,
-                            Label& DONE_LABEL);
-#endif
-
   void compiler_fast_lock_object(ConditionRegister flag, Register oop, Register box,
-                                 Register tmp1, Register tmp2, Register tmp3,
-                                 RTMLockingCounters* rtm_counters = nullptr,
-                                 RTMLockingCounters* stack_rtm_counters = nullptr,
-                                 Metadata* method_data = nullptr,
-                                 bool use_rtm = false, bool profile_rtm = false);
+                                 Register tmp1, Register tmp2, Register tmp3);
 
   void compiler_fast_unlock_object(ConditionRegister flag, Register oop, Register box,
-                                   Register tmp1, Register tmp2, Register tmp3,
-                                   bool use_rtm = false);
+                                   Register tmp1, Register tmp2, Register tmp3);
+
+  void compiler_fast_lock_lightweight_object(ConditionRegister flag, Register oop, Register tmp1,
+                                             Register tmp2, Register tmp3);
+
+  void compiler_fast_unlock_lightweight_object(ConditionRegister flag, Register oop, Register tmp1,
+                                               Register tmp2, Register tmp3);
 
   // Check if safepoint requested and if so branch
   void safepoint_poll(Label& slow_path, Register temp, bool at_return, bool in_nmethod);
 
   void resolve_jobject(Register value, Register tmp1, Register tmp2,
diff a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad
--- a/src/hotspot/cpu/ppc/ppc.ad
+++ b/src/hotspot/cpu/ppc/ppc.ad
@@ -12145,13 +12145,13 @@
 %}
 
 // inlined locking and unlocking
 
 instruct cmpFastLock(flagsRegCR0 crx, iRegPdst oop, iRegPdst box, iRegPdst tmp1, iRegPdst tmp2) %{
+  predicate(LockingMode != LM_LIGHTWEIGHT);
   match(Set crx (FastLock oop box));
   effect(TEMP tmp1, TEMP tmp2);
-  predicate(!Compile::current()->use_rtm());
 
   format %{ "FASTLOCK  $oop, $box, $tmp1, $tmp2" %}
   ins_encode %{
     __ compiler_fast_lock_object($crx$$CondRegister, $oop$$Register, $box$$Register,
                                  $tmp1$$Register, $tmp2$$Register, /*tmp3*/ R0);
@@ -12160,57 +12160,51 @@
     // _complete_monitor_locking_Java for the case where crx is 'NE'.
   %}
   ins_pipe(pipe_class_compare);
 %}
 
-// Separate version for TM. Use bound register for box to enable USE_KILL.
-instruct cmpFastLock_tm(flagsReg crx, iRegPdst oop, rarg2RegP box, iRegPdst tmp1, iRegPdst tmp2, iRegPdst tmp3) %{
-  match(Set crx (FastLock oop box));
-  effect(TEMP tmp1, TEMP tmp2, TEMP tmp3, USE_KILL box);
-  predicate(Compile::current()->use_rtm());
-
-  format %{ "FASTLOCK  $oop, $box, $tmp1, $tmp2, $tmp3 (TM)" %}
-  ins_encode %{
-    __ compiler_fast_lock_object($crx$$CondRegister, $oop$$Register, $box$$Register,
-                                 $tmp1$$Register, $tmp2$$Register, $tmp3$$Register,
-                                 _rtm_counters, _stack_rtm_counters,
-                                 ((Method*)(ra_->C->method()->constant_encoding()))->method_data(),
-                                 /*RTM*/ true, ra_->C->profile_rtm());
-    // If locking was successful, crx should indicate 'EQ'.
-    // The compiler generates a branch to the runtime call to
-    // _complete_monitor_locking_Java for the case where crx is 'NE'.
-  %}
-  ins_pipe(pipe_class_compare);
-%}
-
 instruct cmpFastUnlock(flagsRegCR0 crx, iRegPdst oop, iRegPdst box, iRegPdst tmp1, iRegPdst tmp2, iRegPdst tmp3) %{
+  predicate(LockingMode != LM_LIGHTWEIGHT);
   match(Set crx (FastUnlock oop box));
   effect(TEMP tmp1, TEMP tmp2, TEMP tmp3);
-  predicate(!Compile::current()->use_rtm());
 
   format %{ "FASTUNLOCK  $oop, $box, $tmp1, $tmp2" %}
   ins_encode %{
     __ compiler_fast_unlock_object($crx$$CondRegister, $oop$$Register, $box$$Register,
-                                   $tmp1$$Register, $tmp2$$Register, $tmp3$$Register,
-                                   false);
+                                   $tmp1$$Register, $tmp2$$Register, $tmp3$$Register);
     // If unlocking was successful, crx should indicate 'EQ'.
     // The compiler generates a branch to the runtime call to
     // _complete_monitor_unlocking_Java for the case where crx is 'NE'.
   %}
   ins_pipe(pipe_class_compare);
 %}
 
-instruct cmpFastUnlock_tm(flagsReg crx, iRegPdst oop, iRegPdst box, iRegPdst tmp1, iRegPdst tmp2, iRegPdst tmp3) %{
+instruct cmpFastLockLightweight(flagsRegCR0 crx, iRegPdst oop, iRegPdst box, iRegPdst tmp1, iRegPdst tmp2) %{
+  predicate(LockingMode == LM_LIGHTWEIGHT);
+  match(Set crx (FastLock oop box));
+  effect(TEMP tmp1, TEMP tmp2);
+
+  format %{ "FASTLOCK  $oop, $box, $tmp1, $tmp2" %}
+  ins_encode %{
+    __ fast_lock_lightweight($crx$$CondRegister, $oop$$Register, $box$$Register,
+                             $tmp1$$Register, $tmp2$$Register, /*tmp3*/ R0);
+    // If locking was successful, crx should indicate 'EQ'.
+    // The compiler generates a branch to the runtime call to
+    // _complete_monitor_locking_Java for the case where crx is 'NE'.
+  %}
+  ins_pipe(pipe_class_compare);
+%}
+
+instruct cmpFastUnlockLightweight(flagsRegCR0 crx, iRegPdst oop, iRegPdst box, iRegPdst tmp1, iRegPdst tmp2, iRegPdst tmp3) %{
+  predicate(LockingMode == LM_LIGHTWEIGHT);
   match(Set crx (FastUnlock oop box));
   effect(TEMP tmp1, TEMP tmp2, TEMP tmp3);
-  predicate(Compile::current()->use_rtm());
 
-  format %{ "FASTUNLOCK  $oop, $box, $tmp1, $tmp2 (TM)" %}
+  format %{ "FASTUNLOCK  $oop, $box, $tmp1, $tmp2" %}
   ins_encode %{
-    __ compiler_fast_unlock_object($crx$$CondRegister, $oop$$Register, $box$$Register,
-                                   $tmp1$$Register, $tmp2$$Register, $tmp3$$Register,
-                                   /*RTM*/ true);
+    __ fast_unlock_lightweight($crx$$CondRegister, $oop$$Register, $box$$Register,
+                               $tmp1$$Register, $tmp2$$Register, $tmp3$$Register);
     // If unlocking was successful, crx should indicate 'EQ'.
     // The compiler generates a branch to the runtime call to
     // _complete_monitor_unlocking_Java for the case where crx is 'NE'.
   %}
   ins_pipe(pipe_class_compare);
diff a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp
--- a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp
+++ b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp
@@ -2275,17 +2275,10 @@
 
   // Generate the Verified Entry Point (VEP).
   // --------------------------------------------------------------------------
   vep_start_pc = (intptr_t)__ pc();
 
-  if (UseRTMLocking) {
-    // Abort RTM transaction before calling JNI
-    // because critical section can be large and
-    // abort anyway. Also nmethod can be deoptimized.
-    __ tabort_();
-  }
-
   if (VM_Version::supports_fast_class_init_checks() && method->needs_clinit_barrier()) {
     Label L_skip_barrier;
     Register klass = r_temp_1;
     // Notify OOP recorder (don't need the relocation)
     AddressLiteral md = __ constant_metadata_address(method->method_holder());
@@ -2468,12 +2461,17 @@
 
     // Get the lock box slot's address.
     __ addi(r_box, R1_SP, lock_offset);
 
     // Try fastpath for locking.
-    // fast_lock kills r_temp_1, r_temp_2, r_temp_3.
-    __ compiler_fast_lock_object(CCR0, r_oop, r_box, r_temp_1, r_temp_2, r_temp_3);
+    if (LockingMode == LM_LIGHTWEIGHT) {
+      // fast_lock kills r_temp_1, r_temp_2, r_temp_3.
+      __ compiler_fast_lock_lightweight_object(CCR0, r_oop, r_temp_1, r_temp_2, r_temp_3);
+    } else {
+      // fast_lock kills r_temp_1, r_temp_2, r_temp_3.
+      __ compiler_fast_lock_object(CCR0, r_oop, r_box, r_temp_1, r_temp_2, r_temp_3);
+    }
     __ beq(CCR0, locked);
 
     // None of the above fast optimizations worked so we have to get into the
     // slow case of monitor enter. Inline a special case of call_VM that
     // disallows any pending_exception.
@@ -2679,11 +2677,15 @@
       __ ld(r_oop, receiver_offset, R1_SP);
     }
     __ addi(r_box, R1_SP, lock_offset);
 
     // Try fastpath for unlocking.
-    __ compiler_fast_unlock_object(CCR0, r_oop, r_box, r_temp_1, r_temp_2, r_temp_3);
+    if (LockingMode == LM_LIGHTWEIGHT) {
+      __ compiler_fast_unlock_lightweight_object(CCR0, r_oop, r_temp_1, r_temp_2, r_temp_3);
+    } else {
+      __ compiler_fast_unlock_object(CCR0, r_oop, r_box, r_temp_1, r_temp_2, r_temp_3);
+    }
     __ beq(CCR0, done);
 
     // Save and restore any potential method result value around the unlocking operation.
     save_native_result(masm, ret_type, workspace_slot_offset);
 
@@ -3163,15 +3165,10 @@
   // Setup code generation tools.
   CodeBuffer buffer("uncommon_trap_blob", 2048, 1024);
   InterpreterMacroAssembler* masm = new InterpreterMacroAssembler(&buffer);
   address start = __ pc();
 
-  if (UseRTMLocking) {
-    // Abort RTM transaction before possible nmethod deoptimization.
-    __ tabort_();
-  }
-
   Register unroll_block_reg = R21_tmp1;
   Register klass_index_reg  = R22_tmp2;
   Register unc_trap_reg     = R23_tmp3;
   Register r_return_pc      = R27_tmp7;
 
@@ -3318,17 +3315,10 @@
   } else {
     // Use thread()->saved_exception_pc() as return pc.
     return_pc_location = RegisterSaver::return_pc_is_thread_saved_exception_pc;
   }
 
-  if (UseRTMLocking) {
-    // Abort RTM transaction before calling runtime
-    // because critical section can be large and so
-    // will abort anyway. Also nmethod can be deoptimized.
-    __ tabort_();
-  }
-
   bool save_vectors = (poll_type == POLL_AT_VECTOR_LOOP);
 
   // Save registers, fpu state, and flags. Set R31 = return pc.
   map = RegisterSaver::push_frame_reg_args_and_save_live_registers(masm,
                                                                    &frame_size_in_bytes,
diff a/src/hotspot/cpu/ppc/vm_version_ppc.cpp b/src/hotspot/cpu/ppc/vm_version_ppc.cpp
--- a/src/hotspot/cpu/ppc/vm_version_ppc.cpp
+++ b/src/hotspot/cpu/ppc/vm_version_ppc.cpp
@@ -43,18 +43,10 @@
 #if defined(_AIX)
 #include "os_aix.hpp"
 #include <libperfstat.h>
 #endif
 
-#if defined(LINUX) && defined(VM_LITTLE_ENDIAN)
-#include <sys/auxv.h>
-
-#ifndef PPC_FEATURE2_HTM_NOSC
-#define PPC_FEATURE2_HTM_NOSC (1 << 24)
-#endif
-#endif
-
 bool VM_Version::_is_determine_features_test_running = false;
 uint64_t VM_Version::_dscr_val = 0;
 
 #define MSG(flag)   \
   if (flag && !FLAG_IS_DEFAULT(flag))                                  \
@@ -180,11 +172,11 @@
 #endif
 
   // Create and print feature-string.
   char buf[(num_features+1) * 16]; // Max 16 chars per feature.
   jio_snprintf(buf, sizeof(buf),
-               "ppc64%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
+               "ppc64%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
                (has_fsqrt()   ? " fsqrt"   : ""),
                (has_isel()    ? " isel"    : ""),
                (has_lxarxeh() ? " lxarxeh" : ""),
                (has_cmpb()    ? " cmpb"    : ""),
                (has_popcntb() ? " popcntb" : ""),
@@ -197,11 +189,10 @@
                (has_mfdscr()  ? " mfdscr"  : ""),
                (has_vsx()     ? " vsx"     : ""),
                (has_ldbrx()   ? " ldbrx"   : ""),
                (has_stdbrx()  ? " stdbrx"  : ""),
                (has_vshasig() ? " sha"     : ""),
-               (has_tm()      ? " rtm"     : ""),
                (has_darn()    ? " darn"    : ""),
                (has_brw()     ? " brw"     : "")
                // Make sure number of %s matches num_features!
               );
   _features_string = os::strdup(buf);
@@ -374,53 +365,10 @@
   if (UseVectorizedMismatchIntrinsic) {
     warning("UseVectorizedMismatchIntrinsic specified, but not available on this CPU.");
     FLAG_SET_DEFAULT(UseVectorizedMismatchIntrinsic, false);
   }
 
-
-  // Adjust RTM (Restricted Transactional Memory) flags.
-  if (UseRTMLocking) {
-    // If CPU or OS do not support RTM:
-    if (PowerArchitecturePPC64 < 8 || PowerArchitecturePPC64 > 9) {
-      vm_exit_during_initialization("RTM instructions are not available on this CPU.");
-    }
-
-    if (!has_tm()) {
-      vm_exit_during_initialization("RTM is not supported on this OS version.");
-    }
-
-#if INCLUDE_RTM_OPT
-    if (!FLAG_IS_CMDLINE(UseRTMLocking)) {
-      // RTM locking should be used only for applications with
-      // high lock contention. For now we do not use it by default.
-      vm_exit_during_initialization("UseRTMLocking flag should be only set on command line");
-    }
-    if (LockingMode != LM_LEGACY) {
-      warning("UseRTMLocking requires LockingMode = 1");
-      FLAG_SET_DEFAULT(UseRTMLocking, false);
-    }
-#else
-    // Only C2 does RTM locking optimization.
-    vm_exit_during_initialization("RTM locking optimization is not supported in this VM");
-#endif
-  } else { // !UseRTMLocking
-    if (UseRTMForStackLocks) {
-      if (!FLAG_IS_DEFAULT(UseRTMForStackLocks)) {
-        warning("UseRTMForStackLocks flag should be off when UseRTMLocking flag is off");
-      }
-      FLAG_SET_DEFAULT(UseRTMForStackLocks, false);
-    }
-    if (UseRTMDeopt) {
-      FLAG_SET_DEFAULT(UseRTMDeopt, false);
-    }
-#ifdef COMPILER2
-    if (PrintPreciseRTMLockingStatistics) {
-      FLAG_SET_DEFAULT(PrintPreciseRTMLockingStatistics, false);
-    }
-#endif
-  }
-
   // This machine allows unaligned memory accesses
   if (FLAG_IS_DEFAULT(UseUnalignedAccesses)) {
     FLAG_SET_DEFAULT(UseUnalignedAccesses, true);
   }
 
@@ -599,11 +547,10 @@
   a->mfdscr(R0);                               // code[12] -> mfdscr
   a->lxvd2x(VSR0, R3_ARG1);                    // code[13] -> vsx
   a->ldbrx(R7, R3_ARG1, R4_ARG2);              // code[14] -> ldbrx
   a->stdbrx(R7, R3_ARG1, R4_ARG2);             // code[15] -> stdbrx
   a->vshasigmaw(VR0, VR1, 1, 0xF);             // code[16] -> vshasig
-  // rtm is determined by OS
   a->darn(R7);                                 // code[17] -> darn
   a->brw(R5, R6);                              // code[18] -> brw
   a->blr();
 
   // Emit function to set one cache line to zero. Emit function descriptor and get pointer to it.
@@ -653,11 +600,10 @@
   if (code[feature_cntr++]) features |= mfdscr_m;
   if (code[feature_cntr++]) features |= vsx_m;
   if (code[feature_cntr++]) features |= ldbrx_m;
   if (code[feature_cntr++]) features |= stdbrx_m;
   if (code[feature_cntr++]) features |= vshasig_m;
-  // feature rtm_m is determined by OS
   if (code[feature_cntr++]) features |= darn_m;
   if (code[feature_cntr++]) features |= brw_m;
 
   // Print the detection code.
   if (PrintAssembly) {
@@ -665,41 +611,10 @@
     tty->print_cr("Decoding cpu-feature detection stub at " INTPTR_FORMAT " after execution:", p2i(code));
     Disassembler::decode((u_char*)code, (u_char*)code_end, tty);
   }
 
   _features = features;
-
-#ifdef AIX
-  // To enable it on AIX it's necessary POWER8 or above and at least AIX 7.2.
-  // Actually, this is supported since AIX 7.1.. Unfortunately, this first
-  // contained bugs, so that it can only be enabled after AIX 7.1.3.30.
-  // The Java property os.version, which is used in RTM tests to decide
-  // whether the feature is available, only knows major and minor versions.
-  // We don't want to change this property, as user code might depend on it.
-  // So the tests can not check on subversion 3.30, and we only enable RTM
-  // with AIX 7.2.
-  if (has_lqarx() && !has_brw()) { // POWER8 or POWER9
-    if (os::Aix::os_version() >= 0x07020000) { // At least AIX 7.2.
-      _features |= rtm_m;
-    }
-  }
-#endif
-#if defined(LINUX) && defined(VM_LITTLE_ENDIAN)
-  unsigned long auxv = getauxval(AT_HWCAP2);
-
-  if (auxv & PPC_FEATURE2_HTM_NOSC) {
-    if (auxv & PPC_FEATURE2_HAS_HTM) {
-      // TM on POWER8 and POWER9 in compat mode (VM) is supported by the JVM.
-      // TM on POWER9 DD2.1 NV (baremetal) is not supported by the JVM (TM on
-      // POWER9 DD2.1 NV has a few issues that need a couple of firmware
-      // and kernel workarounds, so there is a new mode only supported
-      // on non-virtualized P9 machines called HTM with no Suspend Mode).
-      // TM on POWER9 D2.2+ NV is not supported at all by Linux.
-      _features |= rtm_m;
-    }
-  }
-#endif
 }
 
 // Power 8: Configure Data Stream Control Register.
 void VM_Version::config_dscr() {
   // 7 InstWords for each call (function descriptor + blr instruction).
diff a/src/hotspot/cpu/ppc/vm_version_ppc.hpp b/src/hotspot/cpu/ppc/vm_version_ppc.hpp
--- a/src/hotspot/cpu/ppc/vm_version_ppc.hpp
+++ b/src/hotspot/cpu/ppc/vm_version_ppc.hpp
@@ -47,11 +47,10 @@
     mfdscr,
     vsx,
     ldbrx,
     stdbrx,
     vshasig,
-    rtm,
     darn,
     brw,
     num_features // last entry to count features
   };
   enum Feature_Flag_Set {
@@ -71,11 +70,10 @@
     mfdscr_m              = (1 << mfdscr ),
     vsx_m                 = (1 << vsx    ),
     ldbrx_m               = (1 << ldbrx  ),
     stdbrx_m              = (1 << stdbrx ),
     vshasig_m             = (1 << vshasig),
-    rtm_m                 = (1 << rtm    ),
     darn_m                = (1 << darn   ),
     brw_m                 = (1 << brw    ),
     all_features_m        = (unsigned long)-1
   };
 
@@ -94,10 +92,11 @@
   static void print_platform_virtualization_info(outputStream*);
 
   // PPC64 supports fast class initialization checks for static methods.
   static bool supports_fast_class_init_checks() { return true; }
   constexpr static bool supports_stack_watermark_barrier() { return true; }
+  constexpr static bool supports_recursive_lightweight_locking() { return true; }
 
   static bool is_determine_features_test_running() { return _is_determine_features_test_running; }
   // CPU instruction support
   static bool has_fsqrt()   { return (_features & fsqrt_m) != 0; }
   static bool has_fsqrts()  { return (_features & fsqrts_m) != 0; }
@@ -114,11 +113,10 @@
   static bool has_mfdscr()  { return (_features & mfdscr_m) != 0; }
   static bool has_vsx()     { return (_features & vsx_m) != 0; }
   static bool has_ldbrx()   { return (_features & ldbrx_m) != 0; }
   static bool has_stdbrx()  { return (_features & stdbrx_m) != 0; }
   static bool has_vshasig() { return (_features & vshasig_m) != 0; }
-  static bool has_tm()      { return (_features & rtm_m) != 0; }
   static bool has_darn()    { return (_features & darn_m) != 0; }
   static bool has_brw()     { return (_features & brw_m) != 0; }
 
   static bool has_mtfprd()  { return has_vpmsumb(); } // alias for P8
 
diff a/src/hotspot/cpu/riscv/c1_CodeStubs_riscv.cpp b/src/hotspot/cpu/riscv/c1_CodeStubs_riscv.cpp
--- a/src/hotspot/cpu/riscv/c1_CodeStubs_riscv.cpp
+++ b/src/hotspot/cpu/riscv/c1_CodeStubs_riscv.cpp
@@ -226,10 +226,15 @@
   }
   __ la(ra, _continuation);
   __ far_jump(RuntimeAddress(Runtime1::entry_for(exit_id)));
 }
 
+void LoadKlassStub::emit_code(LIR_Assembler* ce) {
+  // Currently not needed.
+  Unimplemented();
+}
+
 // 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/riscv/c1_LIRAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp
--- a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp
+++ b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp
@@ -1038,11 +1038,11 @@
     }
     __ allocate_array(op->obj()->as_register(),
                       len,
                       tmp1,
                       tmp2,
-                      arrayOopDesc::header_size(op->type()),
+                      arrayOopDesc::base_offset_in_bytes(op->type()),
                       array_element_size(op->type()),
                       op->klass()->as_register(),
                       *op->stub()->entry());
   }
   __ bind(*op->stub()->continuation());
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
@@ -67,17 +67,16 @@
     lwu(hdr, Address(hdr, Klass::access_flags_offset()));
     test_bit(temp, hdr, exact_log2(JVM_ACC_IS_VALUE_BASED_CLASS));
     bnez(temp, slow_case, true /* is_far */);
   }
 
-  // Load object header
-  ld(hdr, Address(obj, hdr_offset));
-
   if (LockingMode == LM_LIGHTWEIGHT) {
     lightweight_lock(obj, hdr, temp, t1, slow_case);
   } else if (LockingMode == LM_LEGACY) {
     Label done;
+    // Load object header
+    ld(hdr, Address(obj, hdr_offset));
     // and mark it as unlocked
     ori(hdr, hdr, markWord::unlocked_value);
     // save unlocked object header into the displaced header location on the stack
     sd(hdr, Address(disp_hdr, 0));
     // test if object header is still the same (i.e. unlocked), and if so, store the
@@ -132,13 +131,10 @@
   // load object
   ld(obj, Address(disp_hdr, BasicObjectLock::obj_offset()));
   verify_oop(obj);
 
   if (LockingMode == LM_LIGHTWEIGHT) {
-    ld(hdr, Address(obj, oopDesc::mark_offset_in_bytes()));
-    test_bit(temp, hdr, exact_log2(markWord::monitor_value));
-    bnez(temp, slow_case, /* is_far */ true);
     lightweight_unlock(obj, hdr, temp, t1, slow_case);
   } else if (LockingMode == LM_LEGACY) {
     // test if object header is pointing to the displaced header, and if so, restore
     // the displaced header in the object - if the object header is not pointing to
     // the displaced header, get the object header instead
@@ -179,10 +175,14 @@
     sd(klass, Address(obj, oopDesc::klass_offset_in_bytes()));
   }
 
   if (len->is_valid()) {
     sw(len, Address(obj, arrayOopDesc::length_offset_in_bytes()));
+    if (!is_aligned(arrayOopDesc::header_size_in_bytes(), BytesPerWord)) {
+      assert(is_aligned(arrayOopDesc::header_size_in_bytes(), BytesPerInt), "must be 4-byte aligned");
+      sw(zr, Address(obj, arrayOopDesc::header_size_in_bytes()));
+    }
   } else if (UseCompressedClassPointers) {
     store_klass_gap(obj, zr);
   }
 }
 
@@ -278,11 +278,11 @@
   }
 
   verify_oop(obj);
 }
 
-void C1_MacroAssembler::allocate_array(Register obj, Register len, Register tmp1, Register tmp2, int header_size, int f, Register klass, Label& slow_case) {
+void C1_MacroAssembler::allocate_array(Register obj, Register len, Register tmp1, Register tmp2, int base_offset_in_bytes, int f, Register klass, Label& slow_case) {
   assert_different_registers(obj, len, tmp1, tmp2, klass);
 
   // determine alignment mask
   assert(!(BytesPerWord & 1), "must be multiple of 2 for masking code to work");
 
@@ -290,21 +290,24 @@
   mv(t0, (int32_t)max_array_allocation_length);
   bgeu(len, t0, slow_case, /* is_far */ true);
 
   const Register arr_size = tmp2; // okay to be the same
   // align object end
-  mv(arr_size, (int32_t)header_size * BytesPerWord + MinObjAlignmentInBytesMask);
+  mv(arr_size, (int32_t)base_offset_in_bytes + MinObjAlignmentInBytesMask);
   shadd(arr_size, len, arr_size, t0, f);
   andi(arr_size, arr_size, ~(uint)MinObjAlignmentInBytesMask);
 
   try_allocate(obj, arr_size, 0, tmp1, tmp2, slow_case);
 
   initialize_header(obj, klass, len, tmp1, tmp2);
 
   // clear rest of allocated space
   const Register len_zero = len;
-  initialize_body(obj, arr_size, header_size * BytesPerWord, len_zero);
+  // We align-up the header size to word-size, because we clear the
+  // possible alignment gap in initialize_header().
+  int hdr_size = align_up(base_offset_in_bytes, BytesPerWord);
+  initialize_body(obj, arr_size, hdr_size, len_zero);
 
   membar(MacroAssembler::StoreStore);
 
   if (CURRENT_ENV->dtrace_alloc_probes()) {
     assert(obj == x10, "must be");
diff a/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.hpp
--- a/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.hpp
+++ b/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.hpp
@@ -99,11 +99,11 @@
   // len        : array length in number of elements
   // t          : temp register - contents destroyed
   // header_size: size of object header in words
   // f          : element scale factor
   // slow_case  : exit to slow case implementation if fast allocation fails
-  void allocate_array(Register obj, Register len, Register tmp1, Register tmp2, int header_size, int f, Register klass, Label& slow_case);
+  void allocate_array(Register obj, Register len, Register tmp1, Register tmp2, int base_offset_in_bytes, int f, Register klass, Label& slow_case);
 
   int  rsp_offset() const { return _rsp_offset; }
 
   void invalidate_registers(bool inv_r0, bool inv_r19, bool inv_r2, bool inv_r3, bool inv_r4, bool inv_r5) PRODUCT_RETURN;
 
diff a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp
--- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp
+++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp
@@ -30,10 +30,11 @@
 #include "opto/compile.hpp"
 #include "opto/intrinsicnode.hpp"
 #include "opto/output.hpp"
 #include "opto/subnode.hpp"
 #include "runtime/stubRoutines.hpp"
+#include "utilities/globalDefinitions.hpp"
 
 #ifdef PRODUCT
 #define BLOCK_COMMENT(str) /* nothing */
 #define STOP(error) stop(error)
 #else
@@ -49,217 +50,475 @@
   Register flag = t1;
   Register oop = objectReg;
   Register box = boxReg;
   Register disp_hdr = tmp1Reg;
   Register tmp = tmp2Reg;
-  Label cont;
   Label object_has_monitor;
-  Label count, no_count;
+  // Finish fast lock successfully. MUST branch to with flag == 0
+  Label locked;
+  // Finish fast lock unsuccessfully. slow_path MUST branch to with flag != 0
+  Label slow_path;
 
+  assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking should use fast_lock_lightweight");
   assert_different_registers(oop, box, tmp, disp_hdr, flag, tmp3Reg, t0);
 
+  mv(flag, 1);
+
   // Load markWord from object into displaced_header.
   ld(disp_hdr, Address(oop, oopDesc::mark_offset_in_bytes()));
 
   if (DiagnoseSyncOnValueBasedClasses != 0) {
-    load_klass(flag, oop);
-    lwu(flag, Address(flag, Klass::access_flags_offset()));
-    test_bit(flag, flag, exact_log2(JVM_ACC_IS_VALUE_BASED_CLASS));
-    bnez(flag, cont, true /* is_far */);
+    load_klass(tmp, oop);
+    lwu(tmp, Address(tmp, Klass::access_flags_offset()));
+    test_bit(tmp, tmp, exact_log2(JVM_ACC_IS_VALUE_BASED_CLASS));
+    bnez(tmp, slow_path);
   }
 
   // Check for existing monitor
-  test_bit(t0, disp_hdr, exact_log2(markWord::monitor_value));
-  bnez(t0, object_has_monitor);
+  test_bit(tmp, disp_hdr, exact_log2(markWord::monitor_value));
+  bnez(tmp, object_has_monitor);
 
   if (LockingMode == LM_MONITOR) {
-    mv(flag, 1); // Set non-zero flag to indicate 'failure' -> take slow-path
-    j(cont);
-  } else if (LockingMode == LM_LEGACY) {
+    j(slow_path);
+  } else {
+    assert(LockingMode == LM_LEGACY, "must be");
     // Set tmp to be (markWord of object | UNLOCK_VALUE).
     ori(tmp, disp_hdr, markWord::unlocked_value);
 
     // Initialize the box. (Must happen before we update the object mark!)
     sd(tmp, Address(box, BasicLock::displaced_header_offset_in_bytes()));
 
     // Compare object markWord with an unlocked value (tmp) and if
     // equal exchange the stack address of our box with object markWord.
     // On failure disp_hdr contains the possibly locked markWord.
-    cmpxchg(/*memory address*/oop, /*expected value*/tmp, /*new value*/box, Assembler::int64, Assembler::aq,
-            Assembler::rl, /*result*/disp_hdr);
-    mv(flag, zr);
-    beq(disp_hdr, tmp, cont); // prepare zero flag and goto cont if we won the cas
+    cmpxchg(/*memory address*/oop, /*expected value*/tmp, /*new value*/box, Assembler::int64,
+            Assembler::aq, Assembler::rl, /*result*/disp_hdr);
+    beq(disp_hdr, tmp, locked);
 
     assert(oopDesc::mark_offset_in_bytes() == 0, "offset of _mark is not 0");
 
     // If the compare-and-exchange succeeded, then we found an unlocked
-    // object, will have now locked it will continue at label cont
+    // object, will have now locked it will continue at label locked
     // We did not see an unlocked object so try the fast recursive case.
 
     // Check if the owner is self by comparing the value in the
     // markWord of object (disp_hdr) with the stack pointer.
     sub(disp_hdr, disp_hdr, sp);
     mv(tmp, (intptr_t) (~(os::vm_page_size()-1) | (uintptr_t)markWord::lock_mask_in_place));
-    // If (mark & lock_mask) == 0 and mark - sp < page_size, we are stack-locking and goto cont,
+    // If (mark & lock_mask) == 0 and mark - sp < page_size, we are stack-locking and goto label locked,
     // hence we can store 0 as the displaced header in the box, which indicates that it is a
     // recursive lock.
     andr(tmp/*==0?*/, disp_hdr, tmp);
     sd(tmp/*==0, perhaps*/, Address(box, BasicLock::displaced_header_offset_in_bytes()));
-    mv(flag, tmp); // we can use the value of tmp as the result here
-    j(cont);
-  } else {
-    assert(LockingMode == LM_LIGHTWEIGHT, "");
-    Label slow;
-    lightweight_lock(oop, disp_hdr, tmp, tmp3Reg, slow);
-
-    // Indicate success on completion.
-    mv(flag, zr);
-    j(count);
-    bind(slow);
-    mv(flag, 1); // Set non-zero flag to indicate 'failure' -> take slow-path
-    j(no_count);
+    beqz(tmp, locked);
+    j(slow_path);
   }
 
   // Handle existing monitor.
   bind(object_has_monitor);
   // The object's monitor m is unlocked iff m->owner == NULL,
   // otherwise m->owner may contain a thread or a stack address.
   //
   // Try to CAS m->owner from NULL to current thread.
   add(tmp, disp_hdr, (in_bytes(ObjectMonitor::owner_offset()) - markWord::monitor_value));
-  cmpxchg(/*memory address*/tmp, /*expected value*/zr, /*new value*/xthread, Assembler::int64, Assembler::aq,
-          Assembler::rl, /*result*/flag); // cas succeeds if flag == zr(expected)
-
-  if (LockingMode != LM_LIGHTWEIGHT) {
-    // Store a non-null value into the box to avoid looking like a re-entrant
-    // lock. The fast-path monitor unlock code checks for
-    // markWord::monitor_value so use markWord::unused_mark which has the
-    // relevant bit set, and also matches ObjectSynchronizer::slow_enter.
-    mv(tmp, (address)markWord::unused_mark().value());
-    sd(tmp, Address(box, BasicLock::displaced_header_offset_in_bytes()));
-  }
+  cmpxchg(/*memory address*/tmp, /*expected value*/zr, /*new value*/xthread, Assembler::int64,
+          Assembler::aq, Assembler::rl, /*result*/tmp3Reg); // cas succeeds if tmp3Reg == zr(expected)
 
-  beqz(flag, cont); // CAS success means locking succeeded
+  // Store a non-null value into the box to avoid looking like a re-entrant
+  // lock. The fast-path monitor unlock code checks for
+  // markWord::monitor_value so use markWord::unused_mark which has the
+  // relevant bit set, and also matches ObjectSynchronizer::slow_enter.
+  mv(tmp, (address)markWord::unused_mark().value());
+  sd(tmp, Address(box, BasicLock::displaced_header_offset_in_bytes()));
 
-  bne(flag, xthread, cont); // Check for recursive locking
+  beqz(tmp3Reg, locked); // CAS success means locking succeeded
+
+  bne(tmp3Reg, xthread, slow_path); // Check for recursive locking
 
   // Recursive lock case
-  mv(flag, zr);
-  increment(Address(disp_hdr, in_bytes(ObjectMonitor::recursions_offset()) - markWord::monitor_value), 1, t0, tmp);
+  increment(Address(disp_hdr, in_bytes(ObjectMonitor::recursions_offset()) - markWord::monitor_value), 1, tmp2Reg, tmp3Reg);
 
-  bind(cont);
-  // zero flag indicates success
-  // non-zero flag indicates failure
-  bnez(flag, no_count);
+  bind(locked);
+  mv(flag, zr);
+  increment(Address(xthread, JavaThread::held_monitor_count_offset()), 1, tmp2Reg, tmp3Reg);
 
-  bind(count);
-  increment(Address(xthread, JavaThread::held_monitor_count_offset()), 1, t0, tmp);
+#ifdef ASSERT
+  // Check that locked label is reached with flag == 0.
+  Label flag_correct;
+  beqz(flag, flag_correct);
+  stop("Fast Lock Flag != 0");
+#endif
 
-  bind(no_count);
+  bind(slow_path);
+#ifdef ASSERT
+  // Check that slow_path label is reached with flag != 0.
+  bnez(flag, flag_correct);
+  stop("Fast Lock Flag == 0");
+  bind(flag_correct);
+#endif
+  // C2 uses the value of flag (0 vs !0) to determine the continuation.
 }
 
 void C2_MacroAssembler::fast_unlock(Register objectReg, Register boxReg,
                                     Register tmp1Reg, Register tmp2Reg) {
   // Use cr register to indicate the fast_unlock result: zero for success; non-zero for failure.
   Register flag = t1;
   Register oop = objectReg;
   Register box = boxReg;
   Register disp_hdr = tmp1Reg;
   Register tmp = tmp2Reg;
-  Label cont;
   Label object_has_monitor;
-  Label count, no_count;
+  // Finish fast lock successfully. MUST branch to with flag == 0
+  Label unlocked;
+  // Finish fast lock unsuccessfully. slow_path MUST branch to with flag != 0
+  Label slow_path;
 
+  assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking should use fast_unlock_lightweight");
   assert_different_registers(oop, box, tmp, disp_hdr, flag, t0);
 
+  mv(flag, 1);
+
   if (LockingMode == LM_LEGACY) {
     // Find the lock address and load the displaced header from the stack.
     ld(disp_hdr, Address(box, BasicLock::displaced_header_offset_in_bytes()));
 
     // If the displaced header is 0, we have a recursive unlock.
-    mv(flag, disp_hdr);
-    beqz(disp_hdr, cont);
+    beqz(disp_hdr, unlocked);
   }
 
   // Handle existing monitor.
   ld(tmp, Address(oop, oopDesc::mark_offset_in_bytes()));
   test_bit(t0, tmp, exact_log2(markWord::monitor_value));
   bnez(t0, object_has_monitor);
 
   if (LockingMode == LM_MONITOR) {
-    mv(flag, 1); // Set non-zero flag to indicate 'failure' -> take slow path
-    j(cont);
-  } else if (LockingMode == LM_LEGACY) {
+    j(slow_path);
+  } else {
+    assert(LockingMode == LM_LEGACY, "must be");
     // Check if it is still a light weight lock, this is true if we
     // see the stack address of the basicLock in the markWord of the
     // object.
 
-    cmpxchg(/*memory address*/oop, /*expected value*/box, /*new value*/disp_hdr, Assembler::int64, Assembler::relaxed,
-            Assembler::rl, /*result*/tmp);
-    xorr(flag, box, tmp); // box == tmp if cas succeeds
-    j(cont);
-  } else {
-    assert(LockingMode == LM_LIGHTWEIGHT, "");
-    Label slow;
-    lightweight_unlock(oop, tmp, box, disp_hdr, slow);
-
-    // Indicate success on completion.
-    mv(flag, zr);
-    j(count);
-    bind(slow);
-    mv(flag, 1); // Set non-zero flag to indicate 'failure' -> take slow path
-    j(no_count);
+    cmpxchg(/*memory address*/oop, /*expected value*/box, /*new value*/disp_hdr, Assembler::int64,
+            Assembler::relaxed, Assembler::rl, /*result*/tmp);
+    beq(box, tmp, unlocked); // box == tmp if cas succeeds
+    j(slow_path);
   }
 
   assert(oopDesc::mark_offset_in_bytes() == 0, "offset of _mark is not 0");
 
   // Handle existing monitor.
   bind(object_has_monitor);
   STATIC_ASSERT(markWord::monitor_value <= INT_MAX);
   add(tmp, tmp, -(int)markWord::monitor_value); // monitor
 
-  if (LockingMode == LM_LIGHTWEIGHT) {
-    // If the owner is anonymous, we need to fix it -- in an outline stub.
-    Register tmp2 = disp_hdr;
-    ld(tmp2, Address(tmp, ObjectMonitor::owner_offset()));
-    test_bit(t0, tmp2, exact_log2(ObjectMonitor::ANONYMOUS_OWNER));
-    C2HandleAnonOMOwnerStub* stub = new (Compile::current()->comp_arena()) C2HandleAnonOMOwnerStub(tmp, tmp2);
-    Compile::current()->output()->add_stub(stub);
-    bnez(t0, stub->entry(), /* is_far */ true);
-    bind(stub->continuation());
-  }
-
   ld(disp_hdr, Address(tmp, ObjectMonitor::recursions_offset()));
 
   Label notRecursive;
   beqz(disp_hdr, notRecursive); // Will be 0 if not recursive.
 
   // Recursive lock
   addi(disp_hdr, disp_hdr, -1);
   sd(disp_hdr, Address(tmp, ObjectMonitor::recursions_offset()));
-  mv(flag, zr);
-  j(cont);
+  j(unlocked);
 
   bind(notRecursive);
-  ld(flag, Address(tmp, ObjectMonitor::EntryList_offset()));
+  ld(t0, Address(tmp, ObjectMonitor::EntryList_offset()));
   ld(disp_hdr, Address(tmp, ObjectMonitor::cxq_offset()));
-  orr(flag, flag, disp_hdr); // Will be 0 if both are 0.
-  bnez(flag, cont);
+  orr(t0, t0, disp_hdr); // Will be 0 if both are 0.
+  bnez(t0, slow_path);
+
   // need a release store here
   la(tmp, Address(tmp, ObjectMonitor::owner_offset()));
   membar(MacroAssembler::LoadStore | MacroAssembler::StoreStore);
   sd(zr, Address(tmp)); // set unowned
 
-  bind(cont);
-  // zero flag indicates success
-  // non-zero flag indicates failure
-  bnez(flag, no_count);
+  bind(unlocked);
+  mv(flag, zr);
+  decrement(Address(xthread, JavaThread::held_monitor_count_offset()), 1, tmp1Reg, tmp2Reg);
 
-  bind(count);
-  decrement(Address(xthread, JavaThread::held_monitor_count_offset()), 1, t0, tmp);
+#ifdef ASSERT
+  // Check that unlocked label is reached with flag == 0.
+  Label flag_correct;
+  beqz(flag, flag_correct);
+  stop("Fast Lock Flag != 0");
+#endif
 
-  bind(no_count);
+  bind(slow_path);
+#ifdef ASSERT
+  // Check that slow_path label is reached with flag != 0.
+  bnez(flag, flag_correct);
+  stop("Fast Lock Flag == 0");
+  bind(flag_correct);
+#endif
+  // C2 uses the value of flag (0 vs !0) to determine the continuation.
+}
+
+void C2_MacroAssembler::fast_lock_lightweight(Register obj, Register tmp1, Register tmp2, Register tmp3) {
+  // Flag register, zero for success; non-zero for failure.
+  Register flag = t1;
+
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  assert_different_registers(obj, tmp1, tmp2, tmp3, flag, t0);
+
+  mv(flag, 1);
+
+  // Handle inflated monitor.
+  Label inflated;
+  // Finish fast lock successfully. MUST branch to with flag == 0
+  Label locked;
+  // Finish fast lock unsuccessfully. slow_path MUST branch to with flag != 0
+  Label slow_path;
+
+  if (DiagnoseSyncOnValueBasedClasses != 0) {
+    load_klass(tmp1, obj);
+    lwu(tmp1, Address(tmp1, Klass::access_flags_offset()));
+    test_bit(tmp1, tmp1, exact_log2(JVM_ACC_IS_VALUE_BASED_CLASS));
+    bnez(tmp1, slow_path);
+  }
+
+  const Register tmp1_mark = tmp1;
+
+  { // Lightweight locking
+
+    // Push lock to the lock stack and finish successfully. MUST branch to with flag == 0
+    Label push;
+
+    const Register tmp2_top = tmp2;
+    const Register tmp3_t = tmp3;
+
+    // Check if lock-stack is full.
+    lwu(tmp2_top, Address(xthread, JavaThread::lock_stack_top_offset()));
+    mv(tmp3_t, (unsigned)LockStack::end_offset());
+    bge(tmp2_top, tmp3_t, slow_path);
+
+    // Check if recursive.
+    add(tmp3_t, xthread, tmp2_top);
+    ld(tmp3_t, Address(tmp3_t, -oopSize));
+    beq(obj, tmp3_t, push);
+
+    // Relaxed normal load to check for monitor. Optimization for monitor case.
+    ld(tmp1_mark, Address(obj, oopDesc::mark_offset_in_bytes()));
+    test_bit(tmp3_t, tmp1_mark, exact_log2(markWord::monitor_value));
+    bnez(tmp3_t, inflated);
+
+    // Not inflated
+    assert(oopDesc::mark_offset_in_bytes() == 0, "required to avoid a la");
+
+    // Try to lock. Transition lock-bits 0b01 => 0b00
+    ori(tmp1_mark, tmp1_mark, markWord::unlocked_value);
+    xori(tmp3_t, tmp1_mark, markWord::unlocked_value);
+    cmpxchg(/*addr*/ obj, /*expected*/ tmp1_mark, /*new*/ tmp3_t, Assembler::int64,
+            /*acquire*/ Assembler::aq, /*release*/ Assembler::relaxed, /*result*/ tmp3_t);
+    bne(tmp1_mark, tmp3_t, slow_path);
+
+    bind(push);
+    // After successful lock, push object on lock-stack.
+    add(tmp3_t, xthread, tmp2_top);
+    sd(obj, Address(tmp3_t));
+    addw(tmp2_top, tmp2_top, oopSize);
+    sw(tmp2_top, Address(xthread, JavaThread::lock_stack_top_offset()));
+    j(locked);
+  }
+
+  { // Handle inflated monitor.
+    bind(inflated);
+
+    // mark contains the tagged ObjectMonitor*.
+    const Register tmp1_tagged_monitor = tmp1_mark;
+    const uintptr_t monitor_tag = markWord::monitor_value;
+    const Register tmp2_owner_addr = tmp2;
+    const Register tmp3_owner = tmp3;
+
+    // Compute owner address.
+    la(tmp2_owner_addr, Address(tmp1_tagged_monitor, (in_bytes(ObjectMonitor::owner_offset()) - monitor_tag)));
+
+    // CAS owner (null => current thread).
+    cmpxchg(/*addr*/ tmp2_owner_addr, /*expected*/ zr, /*new*/ xthread, Assembler::int64,
+            /*acquire*/ Assembler::aq, /*release*/ Assembler::relaxed, /*result*/ tmp3_owner);
+    beqz(tmp3_owner, locked);
+
+    // Check if recursive.
+    bne(tmp3_owner, xthread, slow_path);
+
+    // Recursive.
+    increment(Address(tmp1_tagged_monitor, in_bytes(ObjectMonitor::recursions_offset()) - monitor_tag), 1, tmp2, tmp3);
+  }
+
+  bind(locked);
+  mv(flag, zr);
+  increment(Address(xthread, JavaThread::held_monitor_count_offset()), 1, tmp2, tmp3);
+
+#ifdef ASSERT
+  // Check that locked label is reached with flag == 0.
+  Label flag_correct;
+  beqz(flag, flag_correct);
+  stop("Fast Lock Flag != 0");
+#endif
+
+  bind(slow_path);
+#ifdef ASSERT
+  // Check that slow_path label is reached with flag != 0.
+  bnez(flag, flag_correct);
+  stop("Fast Lock Flag == 0");
+  bind(flag_correct);
+#endif
+  // C2 uses the value of flag (0 vs !0) to determine the continuation.
+}
+
+void C2_MacroAssembler::fast_unlock_lightweight(Register obj, Register tmp1, Register tmp2,
+                                                Register tmp3) {
+  // Flag register, zero for success; non-zero for failure.
+  Register flag = t1;
+
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  assert_different_registers(obj, tmp1, tmp2, tmp3, flag, t0);
+
+  mv(flag, 1);
+
+  // Handle inflated monitor.
+  Label inflated, inflated_load_monitor;
+  // Finish fast unlock successfully. unlocked MUST branch to with flag == 0
+  Label unlocked;
+  // Finish fast unlock unsuccessfully. MUST branch to with flag != 0
+  Label slow_path;
+
+  const Register tmp1_mark = tmp1;
+  const Register tmp2_top = tmp2;
+  const Register tmp3_t = tmp3;
+
+  { // Lightweight unlock
+
+    // Check if obj is top of lock-stack.
+    lwu(tmp2_top, Address(xthread, JavaThread::lock_stack_top_offset()));
+    subw(tmp2_top, tmp2_top, oopSize);
+    add(tmp3_t, xthread, tmp2_top);
+    ld(tmp3_t, Address(tmp3_t));
+    // Top of lock stack was not obj. Must be monitor.
+    bne(obj, tmp3_t, inflated_load_monitor);
+
+    // Pop lock-stack.
+    DEBUG_ONLY(add(tmp3_t, xthread, tmp2_top);)
+    DEBUG_ONLY(sd(zr, Address(tmp3_t));)
+    sw(tmp2_top, Address(xthread, JavaThread::lock_stack_top_offset()));
+
+    // Check if recursive.
+    add(tmp3_t, xthread, tmp2_top);
+    ld(tmp3_t, Address(tmp3_t, -oopSize));
+    beq(obj, tmp3_t, unlocked);
+
+    // Not recursive.
+    // Load Mark.
+    ld(tmp1_mark, Address(obj, oopDesc::mark_offset_in_bytes()));
+
+    // Check header for monitor (0b10).
+    test_bit(tmp3_t, tmp1_mark, exact_log2(markWord::monitor_value));
+    bnez(tmp3_t, inflated);
+
+    // Try to unlock. Transition lock bits 0b00 => 0b01
+    assert(oopDesc::mark_offset_in_bytes() == 0, "required to avoid lea");
+    ori(tmp3_t, tmp1_mark, markWord::unlocked_value);
+    cmpxchg(/*addr*/ obj, /*expected*/ tmp1_mark, /*new*/ tmp3_t, Assembler::int64,
+            /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, /*result*/ tmp3_t);
+    beq(tmp1_mark, tmp3_t, unlocked);
+
+    // Compare and exchange failed.
+    // Restore lock-stack and handle the unlock in runtime.
+    DEBUG_ONLY(add(tmp3_t, xthread, tmp2_top);)
+    DEBUG_ONLY(sd(obj, Address(tmp3_t));)
+    addw(tmp2_top, tmp2_top, oopSize);
+    sd(tmp2_top, Address(xthread, JavaThread::lock_stack_top_offset()));
+    j(slow_path);
+  }
+
+  { // Handle inflated monitor.
+    bind(inflated_load_monitor);
+    ld(tmp1_mark, Address(obj, oopDesc::mark_offset_in_bytes()));
+#ifdef ASSERT
+    test_bit(tmp3_t, tmp1_mark, exact_log2(markWord::monitor_value));
+    bnez(tmp3_t, inflated);
+    stop("Fast Unlock not monitor");
+#endif
+
+    bind(inflated);
+
+#ifdef ASSERT
+    Label check_done;
+    subw(tmp2_top, tmp2_top, oopSize);
+    mv(tmp3_t, in_bytes(JavaThread::lock_stack_base_offset()));
+    blt(tmp2_top, tmp3_t, check_done);
+    add(tmp3_t, xthread, tmp2_top);
+    ld(tmp3_t, Address(tmp3_t));
+    bne(obj, tmp3_t, inflated);
+    stop("Fast Unlock lock on stack");
+    bind(check_done);
+#endif
+
+    // mark contains the tagged ObjectMonitor*.
+    const Register tmp1_monitor = tmp1_mark;
+    const uintptr_t monitor_tag = markWord::monitor_value;
+
+    // Untag the monitor.
+    sub(tmp1_monitor, tmp1_mark, monitor_tag);
+
+    const Register tmp2_recursions = tmp2;
+    Label not_recursive;
+
+    // Check if recursive.
+    ld(tmp2_recursions, Address(tmp1_monitor, ObjectMonitor::recursions_offset()));
+    beqz(tmp2_recursions, not_recursive);
+
+    // Recursive unlock.
+    addi(tmp2_recursions, tmp2_recursions, -1);
+    sd(tmp2_recursions, Address(tmp1_monitor, ObjectMonitor::recursions_offset()));
+    j(unlocked);
+
+    bind(not_recursive);
+
+    Label release;
+    const Register tmp2_owner_addr = tmp2;
+
+    // Compute owner address.
+    la(tmp2_owner_addr, Address(tmp1_monitor, ObjectMonitor::owner_offset()));
+
+    // Check if the entry lists are empty.
+    ld(t0, Address(tmp1_monitor, ObjectMonitor::EntryList_offset()));
+    ld(tmp3_t, Address(tmp1_monitor, ObjectMonitor::cxq_offset()));
+    orr(t0, t0, tmp3_t);
+    beqz(t0, 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.
+    sd(xthread, Address(tmp2_owner_addr));
+    j(slow_path);
+
+    bind(release);
+    // Set owner to null.
+    membar(MacroAssembler::LoadStore | MacroAssembler::StoreStore);
+    sd(zr, Address(tmp2_owner_addr));
+  }
+
+  bind(unlocked);
+  mv(flag, zr);
+  decrement(Address(xthread, JavaThread::held_monitor_count_offset()), 1, tmp2, tmp3);
+
+#ifdef ASSERT
+  // Check that unlocked label is reached with flag == 0.
+  Label flag_correct;
+  beqz(flag, flag_correct);
+  stop("Fast Lock Flag != 0");
+#endif
+
+  bind(slow_path);
+#ifdef ASSERT
+  // Check that slow_path label is reached with flag != 0.
+  bnez(flag, flag_correct);
+  stop("Fast Lock Flag == 0");
+  bind(flag_correct);
+#endif
+  // C2 uses the value of flag (0 vs !0) to determine the continuation.
 }
 
 // short string
 // StringUTF16.indexOfChar
 // StringLatin1.indexOfChar
diff a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp
--- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp
+++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp
@@ -38,13 +38,15 @@
                        VectorRegister vr1, VectorRegister vr2,
                        VectorRegister vrs,
                        bool is_latin, Label& DONE);
  public:
   // Code used by cmpFastLock and cmpFastUnlock mach instructions in .ad file.
-  // See full description in macroAssembler_riscv.cpp.
   void fast_lock(Register object, Register box, Register tmp1, Register tmp2, Register tmp3);
   void fast_unlock(Register object, Register box, Register tmp1, Register tmp2);
+  // Code used by cmpFastLockLightweight and cmpFastUnlockLightweight mach instructions in .ad file.
+  void fast_lock_lightweight(Register object, Register tmp1, Register tmp2, Register tmp3);
+  void fast_unlock_lightweight(Register object, Register tmp1, Register tmp2, Register tmp3);
 
   void string_compare(Register str1, Register str2,
                       Register cnt1, Register cnt2, Register result,
                       Register tmp1, Register tmp2, Register tmp3,
                       int ae);
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
@@ -834,11 +834,10 @@
       test_bit(tmp, tmp, exact_log2(JVM_ACC_IS_VALUE_BASED_CLASS));
       bnez(tmp, slow_case);
     }
 
     if (LockingMode == LM_LIGHTWEIGHT) {
-      ld(tmp, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
       lightweight_lock(obj_reg, tmp, tmp2, tmp3, slow_case);
       j(count);
     } else if (LockingMode == LM_LEGACY) {
       // Load (object->mark() | 1) into swap_reg
       ld(t0, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
@@ -931,28 +930,10 @@
     // Free entry
     sd(zr, Address(lock_reg, BasicObjectLock::obj_offset()));
 
     if (LockingMode == LM_LIGHTWEIGHT) {
       Label slow_case;
-
-      // Check for non-symmetric locking. This is allowed by the spec and the interpreter
-      // must handle it.
-      Register tmp1 = t0;
-      Register tmp2 = header_reg;
-      // First check for lock-stack underflow.
-      lwu(tmp1, Address(xthread, JavaThread::lock_stack_top_offset()));
-      mv(tmp2, (unsigned)LockStack::start_offset());
-      ble(tmp1, tmp2, slow_case);
-      // Then check if the top of the lock-stack matches the unlocked object.
-      subw(tmp1, tmp1, oopSize);
-      add(tmp1, xthread, tmp1);
-      ld(tmp1, Address(tmp1, 0));
-      bne(tmp1, obj_reg, slow_case);
-
-      ld(header_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
-      test_bit(t0, header_reg, exact_log2(markWord::monitor_value));
-      bnez(t0, slow_case);
       lightweight_unlock(obj_reg, header_reg, swap_reg, tmp_reg, slow_case);
       j(count);
 
       bind(slow_case);
     } else if (LockingMode == LM_LEGACY) {
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
@@ -45,10 +45,11 @@
 #include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/javaThread.hpp"
 #include "runtime/jniHandles.inline.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "runtime/stubRoutines.hpp"
+#include "utilities/globalDefinitions.hpp"
 #include "utilities/powerOfTwo.hpp"
 #ifdef COMPILER2
 #include "opto/compile.hpp"
 #include "opto/node.hpp"
 #include "opto/output.hpp"
@@ -4701,100 +4702,126 @@
     and_imm12(Rd, Rd, 1);
   }
 }
 
 // Implements lightweight-locking.
-// Branches to slow upon failure to lock the object.
-// Falls through upon success.
 //
 //  - obj: the object to be locked
-//  - hdr: the header, already loaded from obj, will be destroyed
-//  - tmp1, tmp2: temporary registers, will be destroyed
-void MacroAssembler::lightweight_lock(Register obj, Register hdr, Register tmp1, Register tmp2, Label& slow) {
+//  - tmp1, tmp2, tmp3: temporary registers, will be destroyed
+//  - slow: branched to if locking fails
+void MacroAssembler::lightweight_lock(Register obj, Register tmp1, Register tmp2, Register tmp3, Label& slow) {
   assert(LockingMode == LM_LIGHTWEIGHT, "only used with new lightweight locking");
-  assert_different_registers(obj, hdr, tmp1, tmp2, t0);
-
-  // Check if we would have space on lock-stack for the object.
-  lwu(tmp1, Address(xthread, JavaThread::lock_stack_top_offset()));
-  mv(tmp2, (unsigned)LockStack::end_offset());
-  bge(tmp1, tmp2, slow, /* is_far */ true);
-
-  // Load (object->mark() | 1) into hdr
-  ori(hdr, hdr, markWord::unlocked_value);
-  // Clear lock-bits, into tmp2
-  xori(tmp2, hdr, markWord::unlocked_value);
-
-  // Try to swing header from unlocked to locked
-  Label success;
-  cmpxchgptr(hdr, tmp2, obj, tmp1, success, &slow);
-  bind(success);
-
-  // After successful lock, push object on lock-stack
-  lwu(tmp1, Address(xthread, JavaThread::lock_stack_top_offset()));
-  add(tmp2, xthread, tmp1);
-  sd(obj, Address(tmp2, 0));
-  addw(tmp1, tmp1, oopSize);
-  sw(tmp1, Address(xthread, JavaThread::lock_stack_top_offset()));
+  assert_different_registers(obj, tmp1, tmp2, tmp3, t0);
+
+  Label push;
+  const Register top = tmp1;
+  const Register mark = tmp2;
+  const Register t = tmp3;
+
+  // Preload the markWord. It is important that this is the first
+  // instruction emitted as it is part of C1's null check semantics.
+  ld(mark, Address(obj, oopDesc::mark_offset_in_bytes()));
+
+  // Check if the lock-stack is full.
+  lwu(top, Address(xthread, JavaThread::lock_stack_top_offset()));
+  mv(t, (unsigned)LockStack::end_offset());
+  bge(top, t, slow, /* is_far */ true);
+
+  // Check for recursion.
+  add(t, xthread, top);
+  ld(t, Address(t, -oopSize));
+  beq(obj, t, push);
+
+  // Check header for monitor (0b10).
+  test_bit(t, mark, exact_log2(markWord::monitor_value));
+  bnez(t, slow, /* is_far */ true);
+
+  // Try to lock. Transition lock-bits 0b01 => 0b00
+  assert(oopDesc::mark_offset_in_bytes() == 0, "required to avoid a la");
+  ori(mark, mark, markWord::unlocked_value);
+  xori(t, mark, markWord::unlocked_value);
+  cmpxchg(/*addr*/ obj, /*expected*/ mark, /*new*/ t, Assembler::int64,
+          /*acquire*/ Assembler::aq, /*release*/ Assembler::relaxed, /*result*/ t);
+  bne(mark, t, slow, /* is_far */ true);
+
+  bind(push);
+  // After successful lock, push object on lock-stack.
+  add(t, xthread, top);
+  sd(obj, Address(t));
+  addw(top, top, oopSize);
+  sw(top, Address(xthread, JavaThread::lock_stack_top_offset()));
 }
 
 // Implements ligthweight-unlocking.
-// Branches to slow upon failure.
-// Falls through upon success.
 //
 // - obj: the object to be unlocked
-// - hdr: the (pre-loaded) header of the object
-// - tmp1, tmp2: temporary registers
-void MacroAssembler::lightweight_unlock(Register obj, Register hdr, Register tmp1, Register tmp2, Label& slow) {
+// - tmp1, tmp2, tmp3: temporary registers
+// - slow: branched to if unlocking fails
+void MacroAssembler::lightweight_unlock(Register obj, Register tmp1, Register tmp2, Register tmp3, Label& slow) {
   assert(LockingMode == LM_LIGHTWEIGHT, "only used with new lightweight locking");
-  assert_different_registers(obj, hdr, tmp1, tmp2, t0);
+  assert_different_registers(obj, tmp1, tmp2, tmp3, t0);
 
 #ifdef ASSERT
   {
-    // The following checks rely on the fact that LockStack is only ever modified by
-    // its owning thread, even if the lock got inflated concurrently; removal of LockStack
-    // entries after inflation will happen delayed in that case.
-
     // Check for lock-stack underflow.
     Label stack_ok;
     lwu(tmp1, Address(xthread, JavaThread::lock_stack_top_offset()));
     mv(tmp2, (unsigned)LockStack::start_offset());
-    bgt(tmp1, tmp2, stack_ok);
+    bge(tmp1, tmp2, stack_ok);
     STOP("Lock-stack underflow");
     bind(stack_ok);
   }
-  {
-    // Check if the top of the lock-stack matches the unlocked object.
-    Label tos_ok;
-    subw(tmp1, tmp1, oopSize);
-    add(tmp1, xthread, tmp1);
-    ld(tmp1, Address(tmp1, 0));
-    beq(tmp1, obj, tos_ok);
-    STOP("Top of lock-stack does not match the unlocked object");
-    bind(tos_ok);
-  }
-  {
-    // Check that hdr is fast-locked.
-   Label hdr_ok;
-    andi(tmp1, hdr, markWord::lock_mask_in_place);
-    beqz(tmp1, hdr_ok);
-    STOP("Header is not fast-locked");
-    bind(hdr_ok);
-  }
 #endif
 
-  // Load the new header (unlocked) into tmp1
-  ori(tmp1, hdr, markWord::unlocked_value);
+  Label unlocked, push_and_slow;
+  const Register top = tmp1;
+  const Register mark = tmp2;
+  const Register t = tmp3;
+
+  // Check if obj is top of lock-stack.
+  lwu(top, Address(xthread, JavaThread::lock_stack_top_offset()));
+  subw(top, top, oopSize);
+  add(t, xthread, top);
+  ld(t, Address(t));
+  bne(obj, t, slow, /* is_far */ true);
+
+  // Pop lock-stack.
+  DEBUG_ONLY(add(t, xthread, top);)
+  DEBUG_ONLY(sd(zr, Address(t));)
+  sw(top, Address(xthread, JavaThread::lock_stack_top_offset()));
+
+  // Check if recursive.
+  add(t, xthread, top);
+  ld(t, Address(t, -oopSize));
+  beq(obj, t, unlocked);
+
+  // Not recursive. Check header for monitor (0b10).
+  ld(mark, Address(obj, oopDesc::mark_offset_in_bytes()));
+  test_bit(t, mark, exact_log2(markWord::monitor_value));
+  bnez(t, push_and_slow);
 
-  // Try to swing header from locked to unlocked
-  Label success;
-  cmpxchgptr(hdr, tmp1, obj, tmp2, success, &slow);
-  bind(success);
-
-  // After successful unlock, pop object from lock-stack
-  lwu(tmp1, Address(xthread, JavaThread::lock_stack_top_offset()));
-  subw(tmp1, tmp1, oopSize);
 #ifdef ASSERT
-  add(tmp2, xthread, tmp1);
-  sd(zr, Address(tmp2, 0));
+  // Check header not unlocked (0b01).
+  Label not_unlocked;
+  test_bit(t, mark, exact_log2(markWord::unlocked_value));
+  beqz(t, not_unlocked);
+  stop("lightweight_unlock already unlocked");
+  bind(not_unlocked);
 #endif
-  sw(tmp1, Address(xthread, JavaThread::lock_stack_top_offset()));
+
+  // Try to unlock. Transition lock bits 0b00 => 0b01
+  assert(oopDesc::mark_offset_in_bytes() == 0, "required to avoid lea");
+  ori(t, mark, markWord::unlocked_value);
+  cmpxchg(/*addr*/ obj, /*expected*/ mark, /*new*/ t, Assembler::int64,
+          /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, /*result*/ t);
+  beq(mark, t, unlocked);
+
+  bind(push_and_slow);
+  // Restore lock-stack and handle the unlock in runtime.
+  DEBUG_ONLY(add(t, xthread, top);)
+  DEBUG_ONLY(sd(obj, Address(t));)
+  addw(top, top, oopSize);
+  sw(top, Address(xthread, JavaThread::lock_stack_top_offset()));
+  j(slow);
+
+  bind(unlocked);
 }
diff a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp
--- a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp
+++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp
@@ -1450,12 +1450,12 @@
 
   void load_reserved(Register addr, enum operand_size size, Assembler::Aqrl acquire);
   void store_conditional(Register addr, Register new_val, enum operand_size size, Assembler::Aqrl release);
 
 public:
-  void lightweight_lock(Register obj, Register hdr, Register tmp1, Register tmp2, Label& slow);
-  void lightweight_unlock(Register obj, Register hdr, Register tmp1, Register tmp2, Label& slow);
+  void lightweight_lock(Register obj, Register tmp1, Register tmp2, Register tmp3, Label& slow);
+  void lightweight_unlock(Register obj, Register tmp1, Register tmp2, Register tmp3, Label& slow);
 };
 
 #ifdef ASSERT
 inline bool AbstractAssembler::pd_check_instruction_mark() { return false; }
 #endif
diff a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad
--- a/src/hotspot/cpu/riscv/riscv.ad
+++ b/src/hotspot/cpu/riscv/riscv.ad
@@ -10231,14 +10231,15 @@
 
 // inlined locking and unlocking
 // using t1 as the 'flag' register to bridge the BoolNode producers and consumers
 instruct cmpFastLock(rFlagsReg cr, iRegP object, iRegP box, iRegPNoSp tmp1, iRegPNoSp tmp2, iRegPNoSp tmp3)
 %{
+  predicate(LockingMode != LM_LIGHTWEIGHT);
   match(Set cr (FastLock object box));
   effect(TEMP tmp1, TEMP tmp2, TEMP tmp3);
 
-  ins_cost(LOAD_COST * 2 + STORE_COST * 3 + ALU_COST * 6 + BRANCH_COST * 3);
+  ins_cost(10 * DEFAULT_COST);
   format %{ "fastlock $object,$box\t! kills $tmp1,$tmp2,$tmp3, #@cmpFastLock" %}
 
   ins_encode %{
     __ fast_lock($object$$Register, $box$$Register, $tmp1$$Register, $tmp2$$Register, $tmp3$$Register);
   %}
@@ -10247,23 +10248,56 @@
 %}
 
 // using t1 as the 'flag' register to bridge the BoolNode producers and consumers
 instruct cmpFastUnlock(rFlagsReg cr, iRegP object, iRegP box, iRegPNoSp tmp1, iRegPNoSp tmp2)
 %{
+  predicate(LockingMode != LM_LIGHTWEIGHT);
   match(Set cr (FastUnlock object box));
   effect(TEMP tmp1, TEMP tmp2);
 
-  ins_cost(LOAD_COST * 2 + STORE_COST + ALU_COST * 2 + BRANCH_COST * 4);
+  ins_cost(10 * DEFAULT_COST);
   format %{ "fastunlock $object,$box\t! kills $tmp1, $tmp2, #@cmpFastUnlock" %}
 
   ins_encode %{
     __ fast_unlock($object$$Register, $box$$Register, $tmp1$$Register, $tmp2$$Register);
   %}
 
   ins_pipe(pipe_serial);
 %}
 
+instruct cmpFastLockLightweight(rFlagsReg cr, iRegP object, iRegP_R10 box, iRegPNoSp tmp1, iRegPNoSp tmp2)
+%{
+  predicate(LockingMode == LM_LIGHTWEIGHT);
+  match(Set cr (FastLock object box));
+  effect(TEMP tmp1, TEMP tmp2, USE_KILL box);
+
+  ins_cost(10 * DEFAULT_COST);
+  format %{ "fastlock $object,$box\t! kills $box,$tmp1,$tmp2 #@cmpFastLockLightweight" %}
+
+  ins_encode %{
+    __ fast_lock_lightweight($object$$Register, $box$$Register, $tmp1$$Register, $tmp2$$Register);
+  %}
+
+  ins_pipe(pipe_serial);
+%}
+
+instruct cmpFastUnlockLightweight(rFlagsReg cr, iRegP object, iRegP_R10 box, iRegPNoSp tmp1, iRegPNoSp tmp2)
+%{
+  predicate(LockingMode == LM_LIGHTWEIGHT);
+  match(Set cr (FastUnlock object box));
+  effect(TEMP tmp1, TEMP tmp2, USE_KILL box);
+
+  ins_cost(10 * DEFAULT_COST);
+  format %{ "fastunlock $object,$box\t! kills $box,$tmp1,$tmp2, #@cmpFastUnlockLightweight" %}
+
+  ins_encode %{
+    __ fast_unlock_lightweight($object$$Register, $box$$Register, $tmp1$$Register, $tmp2$$Register);
+  %}
+
+  ins_pipe(pipe_serial);
+%}
+
 // Tail Call; Jump from runtime stub to Java code.
 // Also known as an 'interprocedural jump'.
 // Target of jump will eventually return to caller.
 // TailJump below removes the return address.
 // Don't use fp for 'jump_target' because a MachEpilogNode has already been
diff a/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp b/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp
--- a/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp
+++ b/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp
@@ -1702,12 +1702,11 @@
 
       // Save the test result, for recursive case, the result is zero
       __ sd(swap_reg, Address(lock_reg, mark_word_offset));
       __ bnez(swap_reg, slow_path_lock);
     } else {
-      assert(LockingMode == LM_LIGHTWEIGHT, "");
-      __ ld(swap_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
+      assert(LockingMode == LM_LIGHTWEIGHT, "must be");
       __ lightweight_lock(obj_reg, swap_reg, tmp, lock_tmp, slow_path_lock);
     }
 
     __ bind(count);
     __ increment(Address(xthread, JavaThread::held_monitor_count_offset()));
@@ -1829,13 +1828,10 @@
       __ cmpxchg_obj_header(x10, old_hdr, obj_reg, t0, count, &slow_path_unlock);
       __ bind(count);
       __ decrement(Address(xthread, JavaThread::held_monitor_count_offset()));
     } else {
       assert(LockingMode == LM_LIGHTWEIGHT, "");
-      __ ld(old_hdr, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
-      __ test_bit(t0, old_hdr, exact_log2(markWord::monitor_value));
-      __ bnez(t0, slow_path_unlock);
       __ lightweight_unlock(obj_reg, old_hdr, swap_reg, lock_tmp, slow_path_unlock);
       __ decrement(Address(xthread, JavaThread::held_monitor_count_offset()));
     }
 
     // slow path re-enters here
diff a/src/hotspot/cpu/riscv/vm_version_riscv.hpp b/src/hotspot/cpu/riscv/vm_version_riscv.hpp
--- a/src/hotspot/cpu/riscv/vm_version_riscv.hpp
+++ b/src/hotspot/cpu/riscv/vm_version_riscv.hpp
@@ -207,10 +207,12 @@
   static void initialize();
   static void initialize_cpu_information();
 
   constexpr static bool supports_stack_watermark_barrier() { return true; }
 
+  constexpr static bool supports_recursive_lightweight_locking() { return true; }
+
   static bool supports_on_spin_wait() { return UseZihintpause; }
 
   // RISCV64 supports fast class initialization checks
   static bool supports_fast_class_init_checks() { return true; }
 };
diff a/src/hotspot/cpu/s390/c1_CodeStubs_s390.cpp b/src/hotspot/cpu/s390/c1_CodeStubs_s390.cpp
--- a/src/hotspot/cpu/s390/c1_CodeStubs_s390.cpp
+++ b/src/hotspot/cpu/s390/c1_CodeStubs_s390.cpp
@@ -251,10 +251,15 @@
   ce->emit_call_c(Runtime1::entry_for (exit_id));
   CHECK_BAILOUT();
   __ branch_optimized(Assembler::bcondAlways, _continuation);
 }
 
+void LoadKlassStub::emit_code(LIR_Assembler* ce) {
+  // Currently not needed.
+  Unimplemented();
+}
+
 // 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/s390/c1_LIRAssembler_s390.cpp b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp
--- a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp
+++ b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp
@@ -2383,11 +2383,11 @@
   } else {
     __ allocate_array(op->obj()->as_register(),
                       op->len()->as_register(),
                       op->tmp1()->as_register(),
                       op->tmp2()->as_register(),
-                      arrayOopDesc::header_size(op->type()),
+                      arrayOopDesc::base_offset_in_bytes(op->type()),
                       type2aelembytes(op->type()),
                       op->klass()->as_register(),
                       *op->stub()->entry());
   }
   __ bind(*op->stub()->continuation());
diff a/src/hotspot/cpu/s390/c1_MacroAssembler_s390.cpp b/src/hotspot/cpu/s390/c1_MacroAssembler_s390.cpp
--- a/src/hotspot/cpu/s390/c1_MacroAssembler_s390.cpp
+++ b/src/hotspot/cpu/s390/c1_MacroAssembler_s390.cpp
@@ -294,11 +294,11 @@
 void C1_MacroAssembler::allocate_array(
   Register obj,                        // result: Pointer to array after successful allocation.
   Register len,                        // array length
   Register t1,                         // temp register
   Register t2,                         // temp register
-  int      hdr_size,                   // object header size in words
+  int      base_offset_in_bytes,       // elements offset in bytes
   int      elt_size,                   // element size in bytes
   Register klass,                      // object klass
   Label&   slow_case                   // Continuation point if fast allocation fails.
 ) {
   assert_different_registers(obj, len, t1, t2, klass);
@@ -320,24 +320,24 @@
     case  2: z_sllg(arr_size, len, 1); break;
     case  4: z_sllg(arr_size, len, 2); break;
     case  8: z_sllg(arr_size, len, 3); break;
     default: ShouldNotReachHere();
   }
-  add2reg(arr_size, hdr_size * wordSize + MinObjAlignmentInBytesMask); // Add space for header & alignment.
-  z_nill(arr_size, (~MinObjAlignmentInBytesMask) & 0xffff);            // Align array size.
+  add2reg(arr_size, base_offset_in_bytes + MinObjAlignmentInBytesMask); // Add space for header & alignment.
+  z_nill(arr_size, (~MinObjAlignmentInBytesMask) & 0xffff);             // Align array size.
 
   try_allocate(obj, arr_size, 0, t1, slow_case);
 
   initialize_header(obj, klass, len, noreg, t1);
 
   // Clear rest of allocated space.
   Label done;
   Register object_fields = t1;
   Register Rzero = Z_R1_scratch;
-  z_aghi(arr_size, -(hdr_size * BytesPerWord));
+  z_aghi(arr_size, -base_offset_in_bytes);
   z_bre(done); // Jump if size of fields is zero.
-  z_la(object_fields, hdr_size * BytesPerWord, obj);
+  z_la(object_fields, base_offset_in_bytes, obj);
   z_xgr(Rzero, Rzero);
   initialize_body(object_fields, arr_size, Rzero);
   bind(done);
 
   // Dtrace support is unimplemented.
diff a/src/hotspot/cpu/s390/c1_MacroAssembler_s390.hpp b/src/hotspot/cpu/s390/c1_MacroAssembler_s390.hpp
--- a/src/hotspot/cpu/s390/c1_MacroAssembler_s390.hpp
+++ b/src/hotspot/cpu/s390/c1_MacroAssembler_s390.hpp
@@ -68,11 +68,11 @@
   // hdr_size correctly and storing the array length afterwards.
   void allocate_object(
     Register obj,                      // result: Pointer to object after successful allocation.
     Register t1,                       // temp register
     Register t2,                       // temp register
-    int      hdr_size,                 // object header size in words
+    int      base_offset_in_bytes,     // elements offset in bytes
     int      obj_size,                 // object size in words
     Register klass,                    // object klass
     Label&   slow_case                 // Continuation point if fast allocation fails.
   );
 
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,10 +277,21 @@
   }
   __ call(RuntimeAddress(Runtime1::entry_for(exit_id)));
   __ jmp(_continuation);
 }
 
+void LoadKlassStub::emit_code(LIR_Assembler* ce) {
+  assert(UseCompactObjectHeaders, "only with compact headers");
+  __ bind(_entry);
+#ifdef _LP64
+  Register d = _result->as_register();
+  __ movq(d, Address(d, OM_OFFSET_NO_MONITOR_VALUE_TAG(header)));
+  __ jmp(_continuation);
+#else
+  __ should_not_reach_here();
+#endif
+}
 
 // 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:
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
@@ -1633,11 +1633,11 @@
     }
     __ allocate_array(op->obj()->as_register(),
                       len,
                       tmp1,
                       tmp2,
-                      arrayOopDesc::header_size(op->type()),
+                      arrayOopDesc::base_offset_in_bytes(op->type()),
                       array_element_size(op->type()),
                       op->klass()->as_register(),
                       *op->stub()->entry());
   }
   __ bind(*op->stub()->continuation());
@@ -3073,10 +3073,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;
@@ -3197,12 +3198,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) {
@@ -3264,17 +3263,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;
@@ -3329,10 +3322,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);
@@ -3432,20 +3426,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);
@@ -3537,16 +3527,31 @@
   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);
+
+    // Check if we can take the (common) fast path, if obj is unlocked.
+    __ movq(result, Address(obj, oopDesc::mark_offset_in_bytes()));
+    __ testb(result, markWord::monitor_value);
+    __ jcc(Assembler::notZero, *op->stub()->entry());
+    __ bind(*op->stub()->continuation());
+    // Fast-path: shift and decode Klass*.
+    __ 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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 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.
@@ -32,13 +32,15 @@
 #include "gc/shared/tlab_globals.hpp"
 #include "interpreter/interpreter.hpp"
 #include "oops/arrayOop.hpp"
 #include "oops/markWord.hpp"
 #include "runtime/basicLock.hpp"
+#include "runtime/globals.hpp"
 #include "runtime/os.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "runtime/stubRoutines.hpp"
+#include "utilities/globalDefinitions.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");
@@ -57,23 +59,22 @@
     movl(hdr, Address(hdr, Klass::access_flags_offset()));
     testl(hdr, JVM_ACC_IS_VALUE_BASED_CLASS);
     jcc(Assembler::notZero, slow_case);
   }
 
-  // Load object header
-  movptr(hdr, Address(obj, hdr_offset));
-
   if (LockingMode == LM_LIGHTWEIGHT) {
 #ifdef _LP64
     const Register thread = r15_thread;
 #else
     const Register thread = disp_hdr;
     get_thread(thread);
 #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
     orptr(hdr, markWord::unlocked_value);
     // save unlocked object header into the displaced header location on the stack
     movptr(Address(disp_hdr, 0), hdr);
     // test if object header is still the same (i.e. unlocked), and if so, store the
@@ -131,13 +132,18 @@
   // load object
   movptr(obj, Address(disp_hdr, BasicObjectLock::obj_offset()));
   verify_oop(obj);
 
   if (LockingMode == LM_LIGHTWEIGHT) {
-    movptr(disp_hdr, Address(obj, hdr_offset));
-    andptr(disp_hdr, ~(int32_t)markWord::lock_mask_in_place);
-    lightweight_unlock(obj, disp_hdr, hdr, slow_case);
+#ifdef _LP64
+    lightweight_unlock(obj, disp_hdr, r15_thread, hdr, slow_case);
+#else
+    // This relies on the implementation of lightweight_unlock being able to handle
+    // that the reg_rax and thread Register parameters may alias each other.
+    get_thread(disp_hdr);
+    lightweight_unlock(obj, disp_hdr, disp_hdr, hdr, slow_case);
+#endif
   } else if (LockingMode == LM_LEGACY) {
     // test if object header is pointing to the displaced header, and if so, restore
     // the displaced header in the object - if the object header is not pointing to
     // the displaced header, get the object header instead
     MacroAssembler::lock(); // must be immediately before cmpxchg!
@@ -161,28 +167,32 @@
   }
 }
 
 
 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);
   }
 #ifdef _LP64
-  else if (UseCompressedClassPointers) {
+  else if (UseCompressedClassPointers && !UseCompactObjectHeaders) {
     xorptr(t1, t1);
     store_klass_gap(obj, t1);
   }
 #endif
 }
@@ -212,10 +222,11 @@
 
 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;
+  assert(!UseCompactObjectHeaders || 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
@@ -259,11 +270,11 @@
   }
 
   verify_oop(obj);
 }
 
-void C1_MacroAssembler::allocate_array(Register obj, Register len, Register t1, Register t2, int header_size, Address::ScaleFactor f, Register klass, Label& slow_case) {
+void C1_MacroAssembler::allocate_array(Register obj, Register len, Register t1, Register t2, int base_offset_in_bytes, Address::ScaleFactor f, Register klass, Label& slow_case) {
   assert(obj == rax, "obj must be in rax, for cmpxchg");
   assert_different_registers(obj, len, t1, t2, klass);
 
   // determine alignment mask
   assert(!(BytesPerWord & 1), "must be a multiple of 2 for masking code to work");
@@ -272,21 +283,34 @@
   cmpptr(len, checked_cast<int32_t>(max_array_allocation_length));
   jcc(Assembler::above, slow_case);
 
   const Register arr_size = t2; // okay to be the same
   // align object end
-  movptr(arr_size, header_size * BytesPerWord + MinObjAlignmentInBytesMask);
+  movptr(arr_size, base_offset_in_bytes + MinObjAlignmentInBytesMask);
   lea(arr_size, Address(arr_size, len, f));
   andptr(arr_size, ~MinObjAlignmentInBytesMask);
 
   try_allocate(obj, arr_size, 0, t1, t2, slow_case);
 
   initialize_header(obj, klass, len, t1, t2);
 
+  // Clear leading 4 bytes, if necessary.
+  // TODO: This could perhaps go into initialize_body() and also clear the leading 4 bytes
+  // for non-array objects, thereby replacing the klass-gap clearing code in initialize_header().
+  int base_offset = base_offset_in_bytes;
+#ifdef _LP64
+  if (!is_aligned(base_offset, BytesPerWord)) {
+    assert(is_aligned(base_offset, BytesPerInt), "must be 4-byte aligned");
+    movl(Address(obj, base_offset), 0);
+    base_offset += BytesPerInt;
+  }
+#endif
+  assert(is_aligned(base_offset, BytesPerWord), "must be word aligned");
+
   // clear rest of allocated space
   const Register len_zero = len;
-  initialize_body(obj, arr_size, header_size * BytesPerWord, len_zero);
+  initialize_body(obj, arr_size, base_offset, len_zero);
 
   if (CURRENT_ENV->dtrace_alloc_probes()) {
     assert(obj == rax, "must be");
     call(RuntimeAddress(Runtime1::entry_for(Runtime1::dtrace_object_alloc_id)));
   }
@@ -297,12 +321,11 @@
 
 
 void C1_MacroAssembler::inline_cache_check(Register receiver, Register iCache) {
   verify_oop(receiver);
   // explicit null check not needed since load from [klass_offset] causes a trap
-  // check against inline cache
-  assert(!MacroAssembler::needs_explicit_null_check(oopDesc::klass_offset_in_bytes()), "must add explicit null check");
+  // check against inline cache. This is checked in Universe::genesis().
   int start_offset = offset();
 
   if (UseCompressedClassPointers) {
     load_klass(rscratch1, receiver, rscratch2);
     cmpptr(rscratch1, iCache);
diff a/src/hotspot/cpu/x86/c1_MacroAssembler_x86.hpp b/src/hotspot/cpu/x86/c1_MacroAssembler_x86.hpp
--- a/src/hotspot/cpu/x86/c1_MacroAssembler_x86.hpp
+++ b/src/hotspot/cpu/x86/c1_MacroAssembler_x86.hpp
@@ -74,11 +74,11 @@
   // obj        : must be rax, will contain pointer to allocated object
   // t1, t2     : scratch registers - contents destroyed
   // header_size: size of object header in words
   // object_size: total size of object in words
   // slow_case  : exit to slow case implementation if fast allocation fails
-  void allocate_object(Register obj, Register t1, Register t2, int header_size, int object_size, Register klass, Label& slow_case);
+  void allocate_object(Register obj, Register t1, Register t2, int base_offset_in_bytes, int object_size, Register klass, Label& slow_case);
 
   enum {
     max_array_allocation_length = 0x00FFFFFF
   };
 
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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 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.
@@ -71,28 +71,89 @@
   __ bind(entry());
   __ call(RuntimeAddress(StubRoutines::x86::method_entry_barrier()));
   __ jmp(continuation(), false /* maybe_short */);
 }
 
-#ifdef _LP64
-int C2HandleAnonOMOwnerStub::max_size() const {
-  // Max size of stub has been determined by testing with 0, in which case
-  // C2CodeStubList::emit() will throw an assertion and report the actual size that
-  // is needed.
-  return DEBUG_ONLY(36) NOT_DEBUG(21);
+int C2FastUnlockLightweightStub::max_size() const {
+  return 128;
 }
 
-void C2HandleAnonOMOwnerStub::emit(C2_MacroAssembler& masm) {
-  __ bind(entry());
-  Register mon = monitor();
-  Register t = tmp();
-  __ movptr(Address(mon, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)), r15_thread);
-  __ subl(Address(r15_thread, JavaThread::lock_stack_top_offset()), oopSize);
+void C2FastUnlockLightweightStub::emit(C2_MacroAssembler& masm) {
+  assert(_t == rax, "must be");
+
+  Label restore_held_monitor_count_and_slow_path;
+
+  { // Restore lock-stack and handle the unlock in runtime.
+
+    __ bind(_push_and_slow_path);
 #ifdef ASSERT
-  __ movl(t, Address(r15_thread, JavaThread::lock_stack_top_offset()));
-  __ movptr(Address(r15_thread, t), 0);
+    // The obj was only cleared in debug.
+    __ movl(_t, Address(_thread, JavaThread::lock_stack_top_offset()));
+    __ movptr(Address(_thread, _t), _obj);
+#endif
+    __ addl(Address(_thread, JavaThread::lock_stack_top_offset()), oopSize);
+  }
+
+  { // Restore held monitor count and slow path.
+
+    __ bind(restore_held_monitor_count_and_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());
+  }
+
+  { // Handle monitor medium path.
+
+    __ bind(_check_successor);
+
+    Label fix_zf_and_unlocked;
+    const Register monitor = _mark;
+
+#ifndef _LP64
+    __ jmpb(restore_held_monitor_count_and_slow_path);
+#else // _LP64
+    // successor null check.
+    __ cmpptr(Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(succ)), NULL_WORD);
+    __ jccb(Assembler::equal, restore_held_monitor_count_and_slow_path);
+
+    // Release lock.
+    __ movptr(Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)), NULL_WORD);
+
+    // Fence.
+    // Instead of MFENCE we use a dummy locked add of 0 to the top-of-stack.
+    __ lock(); __ addl(Address(rsp, 0), 0);
+
+    // Recheck successor.
+    __ cmpptr(Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(succ)), NULL_WORD);
+    // Observed a successor after the release -> fence we have handed off the monitor
+    __ jccb(Assembler::notEqual, fix_zf_and_unlocked);
+
+    // Try to relock, if it fails the monitor has been handed over
+    // TODO: Caveat, this may fail due to deflation, which does
+    //       not handle the monitor handoff. Currently only works
+    //       due to the responsible thread.
+    __ xorptr(rax, rax);
+    __ lock(); __ cmpxchgptr(_thread, Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)));
+    __ jccb  (Assembler::equal, restore_held_monitor_count_and_slow_path);
 #endif
+
+    __ bind(fix_zf_and_unlocked);
+    __ xorl(rax, rax);
+    __ jmp(unlocked_continuation());
+  }
+}
+
+#ifdef _LP64
+int C2LoadNKlassStub::max_size() const {
+  return 10;
+}
+
+void C2LoadNKlassStub::emit(C2_MacroAssembler& masm) {
+  __ bind(entry());
+  Register d = dst();
+  __ movq(d, Address(d, OM_OFFSET_NO_MONITOR_VALUE_TAG(header)));
   __ jmp(continuation());
 }
 #endif
 
 #undef __
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
@@ -31,12 +31,16 @@
 #include "opto/c2_MacroAssembler.hpp"
 #include "opto/intrinsicnode.hpp"
 #include "opto/output.hpp"
 #include "opto/opcodes.hpp"
 #include "opto/subnode.hpp"
+#include "runtime/globals.hpp"
 #include "runtime/objectMonitor.hpp"
 #include "runtime/stubRoutines.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "utilities/powerOfTwo.hpp"
+#include "utilities/sizes.hpp"
 
 #ifdef PRODUCT
 #define BLOCK_COMMENT(str) /* nothing */
 #define STOP(error) stop(error)
 #else
@@ -551,10 +555,11 @@
                                  Register scrReg, Register cx1Reg, Register cx2Reg, Register thread,
                                  RTMLockingCounters* rtm_counters,
                                  RTMLockingCounters* stack_rtm_counters,
                                  Metadata* method_data,
                                  bool use_rtm, bool profile_rtm) {
+  assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking should use fast_lock_lightweight");
   // Ensure the register assignments are disjoint
   assert(tmpReg == rax, "");
 
   if (use_rtm) {
     assert_different_registers(objReg, boxReg, tmpReg, scrReg, cx1Reg, cx2Reg);
@@ -602,11 +607,12 @@
   jcc(Assembler::notZero, IsInflated);
 
   if (LockingMode == LM_MONITOR) {
     // Clear ZF so that we take the slow path at the DONE label. objReg is known to be not 0.
     testptr(objReg, objReg);
-  } else if (LockingMode == LM_LEGACY) {
+  } else {
+    assert(LockingMode == LM_LEGACY, "must be");
     // Attempt stack-locking ...
     orptr (tmpReg, markWord::unlocked_value);
     movptr(Address(boxReg, 0), tmpReg);          // Anticipate successful CAS
     lock();
     cmpxchgptr(boxReg, Address(objReg, oopDesc::mark_offset_in_bytes()));      // Updates tmpReg
@@ -617,14 +623,10 @@
     // Locked by current thread if difference with current SP is less than one page.
     subptr(tmpReg, rsp);
     // Next instruction set ZFlag == 1 (Success) if difference is less then one page.
     andptr(tmpReg, (int32_t) (NOT_LP64(0xFFFFF003) LP64_ONLY(7 - (int)os::vm_page_size())) );
     movptr(Address(boxReg, 0), tmpReg);
-  } else {
-    assert(LockingMode == LM_LIGHTWEIGHT, "");
-    lightweight_lock(objReg, tmpReg, thread, scrReg, NO_COUNT);
-    jmp(COUNT);
   }
   jmp(DONE_LABEL);
 
   bind(IsInflated);
   // The object is inflated. tmpReg contains pointer to ObjectMonitor* + markWord::monitor_value
@@ -751,10 +753,11 @@
 // In the interest of performance we elide m->Owner==Self check in unlock.
 // A perfectly viable alternative is to elide the owner check except when
 // Xcheck:jni is enabled.
 
 void C2_MacroAssembler::fast_unlock(Register objReg, Register boxReg, Register tmpReg, bool use_rtm) {
+  assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking should use fast_unlock_lightweight");
   assert(boxReg == rax, "");
   assert_different_registers(objReg, boxReg, tmpReg);
 
   Label DONE_LABEL, Stacked, COUNT, NO_COUNT;
 
@@ -781,27 +784,10 @@
     testptr(tmpReg, markWord::monitor_value);                         // Inflated?
     jcc(Assembler::zero, Stacked);
   }
 
   // It's inflated.
-  if (LockingMode == LM_LIGHTWEIGHT) {
-    // If the owner is ANONYMOUS, we need to fix it -  in an outline stub.
-    testb(Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)), (int32_t) ObjectMonitor::ANONYMOUS_OWNER);
-#ifdef _LP64
-    if (!Compile::current()->output()->in_scratch_emit_size()) {
-      C2HandleAnonOMOwnerStub* stub = new (Compile::current()->comp_arena()) C2HandleAnonOMOwnerStub(tmpReg, boxReg);
-      Compile::current()->output()->add_stub(stub);
-      jcc(Assembler::notEqual, stub->entry());
-      bind(stub->continuation());
-    } else
-#endif
-    {
-      // We can't easily implement this optimization on 32 bit because we don't have a thread register.
-      // Call the slow-path instead.
-      jcc(Assembler::notEqual, NO_COUNT);
-    }
-  }
 
 #if INCLUDE_RTM_OPT
   if (use_rtm) {
     Label L_regular_inflated_unlock;
     int owner_offset = OM_OFFSET_NO_MONITOR_VALUE_TAG(owner);
@@ -919,23 +905,18 @@
   bind  (LSuccess);
   testl (boxReg, 0);                      // set ICC.ZF=1 to indicate success
   jmpb  (DONE_LABEL);
 
 #endif
-  if (LockingMode != LM_MONITOR) {
+  if (LockingMode == LM_LEGACY) {
     bind  (Stacked);
-    if (LockingMode == LM_LIGHTWEIGHT) {
-      mov(boxReg, tmpReg);
-      lightweight_unlock(objReg, boxReg, tmpReg, NO_COUNT);
-      jmp(COUNT);
-    } else if (LockingMode == LM_LEGACY) {
-      movptr(tmpReg, Address (boxReg, 0));      // re-fetch
-      lock();
-      cmpxchgptr(tmpReg, Address(objReg, oopDesc::mark_offset_in_bytes())); // Uses RAX which is box
-    }
+    movptr(tmpReg, Address (boxReg, 0));      // re-fetch
+    lock();
+    cmpxchgptr(tmpReg, Address(objReg, oopDesc::mark_offset_in_bytes())); // Uses RAX which is box
     // Intentional fall-thru into DONE_LABEL
   }
+
   bind(DONE_LABEL);
 
   // ZFlag == 1 count in fast path
   // ZFlag == 0 count in slow path
   jccb(Assembler::notZero, NO_COUNT);
@@ -952,10 +933,251 @@
   xorl(tmpReg, tmpReg); // Set ZF == 1
 
   bind(NO_COUNT);
 }
 
+void C2_MacroAssembler::fast_lock_lightweight(Register obj, Register box, Register rax_reg,
+                                              Register t, Register thread) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  assert(rax_reg == rax, "Used for CAS");
+  assert_different_registers(obj, box, rax_reg, t, thread);
+
+  // Handle inflated monitor.
+  Label inflated;
+  // Finish fast lock successfully. ZF value is irrelevant.
+  Label locked;
+  // Finish fast lock unsuccessfully. MUST jump with ZF == 0
+  Label slow_path;
+
+  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);
+  }
+
+  const Register mark = t;
+
+  { // Lightweight Lock
+
+    Label push;
+
+    const Register top = box;
+
+    // Load the mark.
+    movptr(mark, Address(obj, oopDesc::mark_offset_in_bytes()));
+
+    // Prefetch top.
+    movl(top, Address(thread, JavaThread::lock_stack_top_offset()));
+
+    // Check for monitor (0b10).
+    testptr(mark, markWord::monitor_value);
+    jcc(Assembler::notZero, inflated);
+
+    // Check if lock-stack is full.
+    cmpl(top, LockStack::end_offset() - 1);
+    jcc(Assembler::greater, slow_path);
+
+    // Check if recursive.
+    cmpptr(obj, Address(thread, top, Address::times_1, -oopSize));
+    jccb(Assembler::equal, push);
+
+    // Try to lock. Transition lock bits 0b01 => 0b00
+    movptr(rax_reg, mark);
+    orptr(rax_reg, markWord::unlocked_value);
+    andptr(mark, ~(int32_t)markWord::unlocked_value);
+    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.
+    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;
+
+    // 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);
+
+    // Check if recursive.
+    cmpptr(thread, rax_reg);
+    jccb(Assembler::notEqual, slow_path);
+
+    // Recursive.
+    increment(Address(tagged_monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(recursions)));
+  }
+
+  bind(locked);
+  increment(Address(thread, JavaThread::held_monitor_count_offset()));
+  // Set ZF = 1
+  xorl(rax_reg, rax_reg);
+
+#ifdef ASSERT
+  // Check that locked label is reached with ZF set.
+  Label zf_correct;
+  jccb(Assembler::zero, zf_correct);
+  stop("Fast Lock ZF != 1");
+#endif
+
+  bind(slow_path);
+#ifdef ASSERT
+  // Check that slow_path label is reached with ZF not set.
+  jccb(Assembler::notZero, zf_correct);
+  stop("Fast Lock ZF != 0");
+  bind(zf_correct);
+#endif
+  // C2 uses the value of ZF to determine the continuation.
+}
+
+void C2_MacroAssembler::fast_unlock_lightweight(Register obj, Register reg_rax, Register t, Register thread) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+  assert(reg_rax == rax, "Used for CAS");
+  assert_different_registers(obj, reg_rax, t);
+
+  // Handle inflated monitor.
+  Label inflated, inflated_check_lock_stack;
+  // Finish fast unlock successfully.  MUST jump with ZF == 1
+  Label unlocked;
+
+  // Assume success.
+  decrement(Address(thread, JavaThread::held_monitor_count_offset()));
+
+  const Register mark = t;
+  const Register top = reg_rax;
+
+  Label dummy;
+  C2FastUnlockLightweightStub* stub = nullptr;
+
+  if (!Compile::current()->output()->in_scratch_emit_size()) {
+    stub = new (Compile::current()->comp_arena()) C2FastUnlockLightweightStub(obj, mark, reg_rax, thread);
+    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();
+
+  { // 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);
+
+    // Pop lock-stack.
+    DEBUG_ONLY(movptr(Address(thread, top, Address::times_1, -oopSize), 0);)
+    subl(Address(thread, JavaThread::lock_stack_top_offset()), oopSize);
+
+    // Check if recursive.
+    cmpptr(obj, Address(thread, top, Address::times_1, -2 * oopSize));
+    jcc(Assembler::equal, unlocked);
+
+    // We elide the monitor check, let the CAS fail instead.
+
+    // 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()));
+    jcc(Assembler::notEqual, push_and_slow_path);
+    jmp(unlocked);
+  }
+
+
+  { // Handle inflated monitor.
+    bind(inflated_check_lock_stack);
+#ifdef ASSERT
+    Label check_done;
+    subl(top, oopSize);
+    cmpl(top, in_bytes(JavaThread::lock_stack_base_offset()));
+    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);
+    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
+  }
+
+  bind(unlocked);
+  if (stub != nullptr) {
+    bind(stub->unlocked_continuation());
+  }
+
+#ifdef ASSERT
+  // Check that unlocked label is reached with ZF set.
+  Label zf_correct;
+  jccb(Assembler::zero, zf_correct);
+  stop("Fast Unlock ZF != 1");
+#endif
+
+  if (stub != nullptr) {
+    bind(stub->slow_path_continuation());
+  }
+#ifdef ASSERT
+  // Check that stub->continuation() label is reached with ZF not set.
+  jccb(Assembler::notZero, zf_correct);
+  stop("Fast Unlock ZF != 0");
+  bind(zf_correct);
+#endif
+  // C2 uses the value of ZF to determine the continuation.
+}
+
 //-------------------------------------------------------------------------------------------
 // Generic instructions support for use in .ad files C2 code generation
 
 void C2_MacroAssembler::vabsnegd(int opcode, XMMRegister dst, XMMRegister src) {
   if (dst != src) {
@@ -6177,5 +6399,27 @@
   } 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) {
+  C2LoadNKlassStub* stub = new (Compile::current()->comp_arena()) C2LoadNKlassStub(dst);
+  Compile::current()->output()->add_stub(stub);
+
+  // 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));
+  testb(dst, markWord::monitor_value);
+  jcc(Assembler::notZero, stub->entry());
+  bind(stub->continuation());
+  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
@@ -41,10 +41,14 @@
                  RTMLockingCounters* stack_rtm_counters,
                  Metadata* method_data,
                  bool use_rtm, bool profile_rtm);
   void fast_unlock(Register obj, Register box, Register tmp, bool use_rtm);
 
+  void fast_lock_lightweight(Register obj, Register box, Register rax_reg,
+                             Register t, Register thread);
+  void fast_unlock_lightweight(Register obj, Register reg_rax, Register t, Register thread);
+
 #if INCLUDE_RTM_OPT
   void rtm_counters_update(Register abort_status, Register rtm_counters);
   void branch_on_random_using_rdtsc(Register tmp, Register scr, int count, Label& brLabel);
   void rtm_abort_ratio_calculation(Register tmp, Register rtm_counters_reg,
                                    RTMLockingCounters* rtm_counters,
@@ -490,6 +494,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/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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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.
@@ -1250,12 +1250,10 @@
       const Register thread = r15_thread;
 #else
       const Register thread = lock_reg;
       get_thread(thread);
 #endif
-      // Load object header, prepare for CAS from unlocked to locked.
-      movptr(swap_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
       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);
 
@@ -1369,24 +1367,17 @@
     // Free entry
     movptr(Address(lock_reg, BasicObjectLock::obj_offset()), NULL_WORD);
 
     if (LockingMode == LM_LIGHTWEIGHT) {
 #ifdef _LP64
-      const Register thread = r15_thread;
+      lightweight_unlock(obj_reg, swap_reg, r15_thread, header_reg, slow_case);
 #else
-      const Register thread = header_reg;
-      get_thread(thread);
+      // This relies on the implementation of lightweight_unlock being able to handle
+      // that the reg_rax and thread Register parameters may alias each other.
+      get_thread(swap_reg);
+      lightweight_unlock(obj_reg, swap_reg, swap_reg, header_reg, slow_case);
 #endif
-      // Handle unstructured locking.
-      Register tmp = swap_reg;
-      movl(tmp, Address(thread, JavaThread::lock_stack_top_offset()));
-      cmpptr(obj_reg, Address(thread, tmp, Address::times_1,  -oopSize));
-      jcc(Assembler::notEqual, slow_case);
-      // Try to swing header from locked to unlocked.
-      movptr(swap_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
-      andptr(swap_reg, ~(int32_t)markWord::lock_mask_in_place);
-      lightweight_unlock(obj_reg, swap_reg, header_reg, slow_case);
     } else if (LockingMode == LM_LEGACY) {
       // Load the old header from BasicLock structure
       movptr(header_reg, Address(swap_reg,
                                  BasicLock::displaced_header_offset_in_bytes()));
 
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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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.
@@ -5249,23 +5249,46 @@
   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");
+
+  Label fast;
+  movq(dst, Address(src, oopDesc::mark_offset_in_bytes()));
+  testb(dst, markWord::monitor_value);
+  jccb(Assembler::zero, fast);
+
+  // Fetch displaced header
+  movq(dst, Address(dst, OM_OFFSET_NO_MONITOR_VALUE_TAG(header)));
+
+  bind(fast);
+  shrq(dst, markWord::klass_shift);
+}
+#endif
+
 void MacroAssembler::load_klass(Register dst, Register src, Register tmp) {
   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);
@@ -5273,10 +5296,43 @@
   } else
 #endif
     movptr(Address(dst, oopDesc::klass_offset_in_bytes()), src);
 }
 
+void MacroAssembler::cmp_klass(Register klass, Register obj, Register tmp) {
+#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) {
+#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;
@@ -5320,10 +5376,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);
   }
 }
@@ -9813,70 +9870,118 @@
   stop(msg);
   bind(L_stack_ok);
 }
 
 // Implements lightweight-locking.
-// Branches to slow upon failure to lock the object, with ZF cleared.
-// Falls through upon success with unspecified ZF.
 //
 // obj: the object to be locked
-// hdr: the (pre-loaded) header of the object, must be rax
+// reg_rax: rax
 // thread: the thread which attempts to lock obj
 // tmp: a temporary register
-void MacroAssembler::lightweight_lock(Register obj, Register hdr, Register thread, Register tmp, Label& slow) {
-  assert(hdr == rax, "header must be in rax for cmpxchg");
-  assert_different_registers(obj, hdr, thread, tmp);
-
-  // First we need to check if the lock-stack has room for pushing the object reference.
-  // Note: we subtract 1 from the end-offset so that we can do a 'greater' comparison, instead
-  // of 'greaterEqual' below, which readily clears the ZF. This makes C2 code a little simpler and
-  // avoids one branch.
-  cmpl(Address(thread, JavaThread::lock_stack_top_offset()), LockStack::end_offset() - 1);
-  jcc(Assembler::greater, slow);
-
-  // Now we attempt to take the fast-lock.
-  // Clear lock_mask bits (locked state).
-  andptr(hdr, ~(int32_t)markWord::lock_mask_in_place);
-  movptr(tmp, hdr);
-  // Set unlocked_value bit.
-  orptr(hdr, markWord::unlocked_value);
-  lock();
-  cmpxchgptr(tmp, Address(obj, oopDesc::mark_offset_in_bytes()));
+void MacroAssembler::lightweight_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()));
+
+  // Load top.
+  movl(top, Address(thread, JavaThread::lock_stack_top_offset()));
+
+  // Check if the lock-stack is full.
+  cmpl(top, LockStack::end_offset());
+  jcc(Assembler::greaterEqual, slow);
+
+  // Check for recursion.
+  cmpptr(obj, Address(thread, top, Address::times_1, -oopSize));
+  jcc(Assembler::equal, push);
+
+  // Check header for monitor (0b10).
+  testptr(reg_rax, markWord::monitor_value);
+  jcc(Assembler::notZero, slow);
+
+  // Try to lock. Transition lock bits 0b01 => 0b00
+  movptr(tmp, reg_rax);
+  andptr(tmp, ~(int32_t)markWord::unlocked_value);
+  orptr(reg_rax, markWord::unlocked_value);
+  lock(); cmpxchgptr(tmp, Address(obj, oopDesc::mark_offset_in_bytes()));
   jcc(Assembler::notEqual, slow);
 
-  // If successful, push object to lock-stack.
-  movl(tmp, Address(thread, JavaThread::lock_stack_top_offset()));
-  movptr(Address(thread, tmp), obj);
-  incrementl(tmp, oopSize);
-  movl(Address(thread, JavaThread::lock_stack_top_offset()), tmp);
+  // Restore top, CAS clobbers register.
+  movl(top, Address(thread, JavaThread::lock_stack_top_offset()));
+
+  bind(push);
+  // After successful lock, push object on lock-stack.
+  movptr(Address(thread, top), obj);
+  incrementl(top, oopSize);
+  movl(Address(thread, JavaThread::lock_stack_top_offset()), top);
 }
 
 // Implements lightweight-unlocking.
-// Branches to slow upon failure, with ZF cleared.
-// Falls through upon success, with unspecified ZF.
 //
 // obj: the object to be unlocked
-// hdr: the (pre-loaded) header of the object, must be rax
+// reg_rax: rax
+// thread: the thread
 // tmp: a temporary register
-void MacroAssembler::lightweight_unlock(Register obj, Register hdr, Register tmp, Label& slow) {
-  assert(hdr == rax, "header must be in rax for cmpxchg");
-  assert_different_registers(obj, hdr, tmp);
-
-  // Mark-word must be lock_mask now, try to swing it back to unlocked_value.
-  movptr(tmp, hdr); // The expected old value
-  orptr(tmp, markWord::unlocked_value);
-  lock();
-  cmpxchgptr(tmp, Address(obj, oopDesc::mark_offset_in_bytes()));
+//
+// x86_32 Note: reg_rax and thread may alias each other due to limited register
+//              availiability.
+void MacroAssembler::lightweight_unlock(Register obj, Register reg_rax, Register thread, Register tmp, Label& slow) {
+  assert(reg_rax == rax, "");
+  assert_different_registers(obj, reg_rax, tmp);
+  LP64_ONLY(assert_different_registers(obj, reg_rax, thread, tmp);)
+
+  Label unlocked, push_and_slow;
+  const Register top = tmp;
+
+  // Check if obj is top of lock-stack.
+  movl(top, Address(thread, JavaThread::lock_stack_top_offset()));
+  cmpptr(obj, Address(thread, top, Address::times_1, -oopSize));
   jcc(Assembler::notEqual, slow);
-  // Pop the lock object from the lock-stack.
-#ifdef _LP64
-  const Register thread = r15_thread;
-#else
-  const Register thread = rax;
-  get_thread(thread);
-#endif
+
+  // Pop lock-stack.
+  DEBUG_ONLY(movptr(Address(thread, top, Address::times_1, -oopSize), 0);)
   subl(Address(thread, JavaThread::lock_stack_top_offset()), oopSize);
+
+  // Check if recursive.
+  cmpptr(obj, Address(thread, top, Address::times_1, -2 * oopSize));
+  jcc(Assembler::equal, unlocked);
+
+  // Not recursive. Check header for monitor (0b10).
+  movptr(reg_rax, Address(obj, oopDesc::mark_offset_in_bytes()));
+  testptr(reg_rax, markWord::monitor_value);
+  jcc(Assembler::notZero, push_and_slow);
+
 #ifdef ASSERT
-  movl(tmp, Address(thread, JavaThread::lock_stack_top_offset()));
-  movptr(Address(thread, tmp), 0);
+  // Check header not unlocked (0b01).
+  Label not_unlocked;
+  testptr(reg_rax, markWord::unlocked_value);
+  jcc(Assembler::zero, not_unlocked);
+  stop("lightweight_unlock already unlocked");
+  bind(not_unlocked);
 #endif
+
+  // Try to unlock. Transition lock bits 0b00 => 0b01
+  movptr(tmp, reg_rax);
+  orptr(tmp, markWord::unlocked_value);
+  lock(); cmpxchgptr(tmp, Address(obj, oopDesc::mark_offset_in_bytes()));
+  jcc(Assembler::equal, unlocked);
+
+  bind(push_and_slow);
+  // Restore lock-stack and handle the unlock in runtime.
+  if (thread == reg_rax) {
+    // On x86_32 we may lose the thread.
+    get_thread(thread);
+  }
+#ifdef ASSERT
+  movl(top, Address(thread, JavaThread::lock_stack_top_offset()));
+  movptr(Address(thread, top), obj);
+#endif
+  addl(Address(thread, JavaThread::lock_stack_top_offset()), oopSize);
+  jmp(slow);
+
+  bind(unlocked);
 }
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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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.
@@ -361,13 +361,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);
 
@@ -2021,12 +2032,12 @@
 
   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 hdr, Register thread, Register tmp, Label& slow);
-  void lightweight_unlock(Register obj, Register hdr, Register tmp, Label& slow);
+  void lightweight_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/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,17 @@
   // 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) {
+    __ 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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 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.
@@ -1712,12 +1712,10 @@
       // 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");
-      // Load object header
-      __ movptr(swap_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
       __ lightweight_lock(obj_reg, swap_reg, thread, lock_reg, slow_path_lock);
     }
     __ bind(count_mon);
     __ inc_held_monitor_count();
 
@@ -1871,13 +1869,11 @@
       __ cmpxchgptr(rbx, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
       __ jcc(Assembler::notEqual, slow_path_unlock);
       __ dec_held_monitor_count();
     } else {
       assert(LockingMode == LM_LIGHTWEIGHT, "must be");
-      __ movptr(swap_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
-      __ andptr(swap_reg, ~(int32_t)markWord::lock_mask_in_place);
-      __ lightweight_unlock(obj_reg, swap_reg, lock_reg, slow_path_unlock);
+      __ lightweight_unlock(obj_reg, swap_reg, thread, lock_reg, slow_path_unlock);
       __ dec_held_monitor_count();
     }
 
     // slow path re-enters here
     __ bind(unlock_done);
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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 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.
@@ -2187,12 +2187,10 @@
       // 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");
-      // Load object header
-      __ movptr(swap_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
       __ lightweight_lock(obj_reg, swap_reg, r15_thread, rscratch1, slow_path_lock);
     }
     __ bind(count_mon);
     __ inc_held_monitor_count();
 
@@ -2331,13 +2329,11 @@
       __ cmpxchgptr(old_hdr, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
       __ jcc(Assembler::notEqual, slow_path_unlock);
       __ dec_held_monitor_count();
     } else {
       assert(LockingMode == LM_LIGHTWEIGHT, "must be");
-      __ movptr(swap_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
-      __ andptr(swap_reg, ~(int32_t)markWord::lock_mask_in_place);
-      __ lightweight_unlock(obj_reg, swap_reg, lock_reg, slow_path_unlock);
+      __ lightweight_unlock(obj_reg, swap_reg, r15_thread, lock_reg, slow_path_unlock);
       __ dec_held_monitor_count();
     }
 
     // slow path re-enters here
     __ bind(unlock_done);
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
@@ -4030,11 +4030,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)
@@ -4052,26 +4057,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/vm_version_x86.hpp b/src/hotspot/cpu/x86/vm_version_x86.hpp
--- a/src/hotspot/cpu/x86/vm_version_x86.hpp
+++ b/src/hotspot/cpu/x86/vm_version_x86.hpp
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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.
@@ -764,10 +764,14 @@
 
   constexpr static bool supports_stack_watermark_barrier() {
     return true;
   }
 
+  constexpr static bool supports_recursive_lightweight_locking() {
+    return true;
+  }
+
   // For AVX CPUs only. f16c support is disabled if UseAVX == 0.
   static bool supports_float16() {
     return supports_f16c() || supports_avx512vl();
   }
 
diff a/src/hotspot/cpu/x86/x86_32.ad b/src/hotspot/cpu/x86/x86_32.ad
--- a/src/hotspot/cpu/x86/x86_32.ad
+++ b/src/hotspot/cpu/x86/x86_32.ad
@@ -13778,11 +13778,11 @@
   %}
   ins_pipe(pipe_slow);
 %}
 
 instruct cmpFastLock(eFlagsReg cr, eRegP object, eBXRegP box, eAXRegI tmp, eRegP scr, eRegP thread) %{
-  predicate(!Compile::current()->use_rtm());
+  predicate(LockingMode != LM_LIGHTWEIGHT && !Compile::current()->use_rtm());
   match(Set cr (FastLock object box));
   effect(TEMP tmp, TEMP scr, USE_KILL box, TEMP thread);
   ins_cost(300);
   format %{ "FASTLOCK $object,$box\t! kills $box,$tmp,$scr" %}
   ins_encode %{
@@ -13792,20 +13792,47 @@
   %}
   ins_pipe(pipe_slow);
 %}
 
 instruct cmpFastUnlock(eFlagsReg cr, eRegP object, eAXRegP box, eRegP tmp ) %{
+  predicate(LockingMode != LM_LIGHTWEIGHT);
   match(Set cr (FastUnlock object box));
   effect(TEMP tmp, USE_KILL box);
   ins_cost(300);
   format %{ "FASTUNLOCK $object,$box\t! kills $box,$tmp" %}
   ins_encode %{
     __ fast_unlock($object$$Register, $box$$Register, $tmp$$Register, ra_->C->use_rtm());
   %}
   ins_pipe(pipe_slow);
 %}
 
+instruct cmpFastLockLightweight(eFlagsReg cr, eRegP object, eBXRegP box, eAXRegI eax_reg, eRegP tmp, eRegP thread) %{
+  predicate(LockingMode == LM_LIGHTWEIGHT);
+  match(Set cr (FastLock object box));
+  effect(TEMP eax_reg, TEMP tmp, USE_KILL box, TEMP thread);
+  ins_cost(300);
+  format %{ "FASTLOCK $object,$box\t! kills $box,$eax_reg,$tmp" %}
+  ins_encode %{
+    __ get_thread($thread$$Register);
+    __ fast_lock_lightweight($object$$Register, $box$$Register, $eax_reg$$Register, $tmp$$Register, $thread$$Register);
+  %}
+  ins_pipe(pipe_slow);
+%}
+
+instruct cmpFastUnlockLightweight(eFlagsReg cr, eRegP object, eAXRegP eax_reg, eRegP tmp, eRegP thread) %{
+  predicate(LockingMode == LM_LIGHTWEIGHT);
+  match(Set cr (FastUnlock object eax_reg));
+  effect(TEMP tmp, USE_KILL eax_reg, TEMP thread);
+  ins_cost(300);
+  format %{ "FASTUNLOCK $object,$eax_reg\t! kills $eax_reg,$tmp" %}
+  ins_encode %{
+    __ get_thread($thread$$Register);
+    __ fast_unlock_lightweight($object$$Register, $eax_reg$$Register, $tmp$$Register, $thread$$Register);
+  %}
+  ins_pipe(pipe_slow);
+%}
+
 instruct mask_all_evexL_LT32(kReg dst, eRegL src) %{
   predicate(Matcher::vector_length(n) <= 32);
   match(Set dst (MaskAll src));
   format %{ "mask_all_evexL_LE32 $dst, $src \t" %}
   ins_encode %{
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
@@ -5312,20 +5312,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));
 
@@ -12640,10 +12656,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);
@@ -13286,11 +13303,11 @@
   %}
   ins_pipe(pipe_slow);
 %}
 
 instruct cmpFastLock(rFlagsReg cr, rRegP object, rbx_RegP box, rax_RegI tmp, rRegP scr) %{
-  predicate(!Compile::current()->use_rtm());
+  predicate(LockingMode != LM_LIGHTWEIGHT && !Compile::current()->use_rtm());
   match(Set cr (FastLock object box));
   effect(TEMP tmp, TEMP scr, USE_KILL box);
   ins_cost(300);
   format %{ "fastlock $object,$box\t! kills $box,$tmp,$scr" %}
   ins_encode %{
@@ -13299,20 +13316,45 @@
   %}
   ins_pipe(pipe_slow);
 %}
 
 instruct cmpFastUnlock(rFlagsReg cr, rRegP object, rax_RegP box, rRegP tmp) %{
+  predicate(LockingMode != LM_LIGHTWEIGHT);
   match(Set cr (FastUnlock object box));
   effect(TEMP tmp, USE_KILL box);
   ins_cost(300);
   format %{ "fastunlock $object,$box\t! kills $box,$tmp" %}
   ins_encode %{
     __ fast_unlock($object$$Register, $box$$Register, $tmp$$Register, ra_->C->use_rtm());
   %}
   ins_pipe(pipe_slow);
 %}
 
+instruct cmpFastLockLightweight(rFlagsReg cr, rRegP object, rbx_RegP box, rax_RegI rax_reg, rRegP tmp) %{
+  predicate(LockingMode == LM_LIGHTWEIGHT);
+  match(Set cr (FastLock object box));
+  effect(TEMP rax_reg, TEMP tmp, USE_KILL box);
+  ins_cost(300);
+  format %{ "fastlock $object,$box\t! kills $box,$rax_reg,$tmp" %}
+  ins_encode %{
+    __ fast_lock_lightweight($object$$Register, $box$$Register, $rax_reg$$Register, $tmp$$Register, r15_thread);
+  %}
+  ins_pipe(pipe_slow);
+%}
+
+instruct cmpFastUnlockLightweight(rFlagsReg cr, rRegP object, rax_RegP rax_reg, rRegP tmp) %{
+  predicate(LockingMode == LM_LIGHTWEIGHT);
+  match(Set cr (FastUnlock object rax_reg));
+  effect(TEMP tmp, USE_KILL rax_reg);
+  ins_cost(300);
+  format %{ "fastunlock $object,$rax_reg\t! kills $rax_reg,$tmp" %}
+  ins_encode %{
+    __ fast_unlock_lightweight($object$$Register, $rax_reg$$Register, $tmp$$Register, r15_thread);
+  %}
+  ins_pipe(pipe_slow);
+%}
+
 
 // ============================================================================
 // Safepoint Instructions
 instruct safePoint_poll_tls(rFlagsReg cr, rRegP poll)
 %{
diff a/src/hotspot/share/c1/c1_CodeStubs.hpp b/src/hotspot/share/c1/c1_CodeStubs.hpp
--- a/src/hotspot/share/c1/c1_CodeStubs.hpp
+++ b/src/hotspot/share/c1/c1_CodeStubs.hpp
@@ -579,6 +579,24 @@
 #ifndef PRODUCT
   virtual void print_name(outputStream* out) const { out->print("ArrayCopyStub"); }
 #endif // PRODUCT
 };
 
+class LoadKlassStub: public CodeStub {
+private:
+  LIR_Opr          _result;
+
+public:
+  LoadKlassStub(LIR_Opr result) :
+    CodeStub(), _result(result) {};
+
+  virtual void emit_code(LIR_Assembler* e);
+  virtual void visit(LIR_OpVisitState* visitor) {
+    visitor->do_temp(_result);
+    visitor->do_output(_result);
+  }
+#ifndef PRODUCT
+  virtual void print_name(outputStream* out) const { out->print("LoadKlassStub"); }
+#endif // PRODUCT
+};
+
 #endif // SHARE_C1_C1_CODESTUBS_HPP
diff a/src/hotspot/share/c1/c1_LIR.cpp b/src/hotspot/share/c1/c1_LIR.cpp
--- a/src/hotspot/share/c1/c1_LIR.cpp
+++ b/src/hotspot/share/c1/c1_LIR.cpp
@@ -888,10 +888,11 @@
       LIR_OpLoadKlass* opLoadKlass = op->as_OpLoadKlass();
       assert(opLoadKlass != nullptr, "must be");
 
       do_input(opLoadKlass->_obj);
       do_output(opLoadKlass->_result);
+      if (opLoadKlass->_stub) do_stub(opLoadKlass->_stub);
       if (opLoadKlass->_info) do_info(opLoadKlass->_info);
       break;
     }
 
 
@@ -1068,10 +1069,13 @@
   }
 }
 
 void LIR_OpLoadKlass::emit_code(LIR_Assembler* masm) {
   masm->emit_load_klass(this);
+  if (stub()) {
+    masm->append_code_stub(stub());
+  }
 }
 
 #ifdef ASSERT
 void LIR_OpAssert::emit_code(LIR_Assembler* masm) {
   masm->emit_assert(this);
@@ -2044,10 +2048,13 @@
 }
 
 void LIR_OpLoadKlass::print_instr(outputStream* out) const {
   obj()->print(out);        out->print(" ");
   result_opr()->print(out); out->print(" ");
+  if (stub()) {
+    out->print("[lbl:" INTPTR_FORMAT "]", p2i(stub()->entry()));
+  }
 }
 
 #ifdef ASSERT
 void LIR_OpAssert::print_instr(outputStream* out) const {
   print_condition(out, condition()); out->print(" ");
diff a/src/hotspot/share/c1/c1_LIR.hpp b/src/hotspot/share/c1/c1_LIR.hpp
--- a/src/hotspot/share/c1/c1_LIR.hpp
+++ b/src/hotspot/share/c1/c1_LIR.hpp
@@ -1901,17 +1901,20 @@
 class LIR_OpLoadKlass: public LIR_Op {
   friend class LIR_OpVisitState;
 
  private:
   LIR_Opr _obj;
+  CodeStub* _stub;
  public:
-  LIR_OpLoadKlass(LIR_Opr obj, LIR_Opr result, CodeEmitInfo* info)
+  LIR_OpLoadKlass(LIR_Opr obj, LIR_Opr result, CodeEmitInfo* info, CodeStub* stub)
     : LIR_Op(lir_load_klass, result, info)
     , _obj(obj)
+    , _stub(stub)
     {}
 
   LIR_Opr obj()        const { return _obj;  }
+  CodeStub* stub()     const { return _stub; }
 
   virtual LIR_OpLoadKlass* as_OpLoadKlass() { return this; }
   virtual void emit_code(LIR_Assembler* masm);
   void print_instr(outputStream* out) const PRODUCT_RETURN;
 };
@@ -2373,11 +2376,11 @@
   }
 
   void xadd(LIR_Opr src, LIR_Opr add, LIR_Opr res, LIR_Opr tmp) { append(new LIR_Op2(lir_xadd, src, add, res, tmp)); }
   void xchg(LIR_Opr src, LIR_Opr set, LIR_Opr res, LIR_Opr tmp) { append(new LIR_Op2(lir_xchg, src, set, res, tmp)); }
 
-  void load_klass(LIR_Opr obj, LIR_Opr result, CodeEmitInfo* info) { append(new LIR_OpLoadKlass(obj, result, info)); }
+  void load_klass(LIR_Opr obj, LIR_Opr result, CodeEmitInfo* info, CodeStub* stub) { append(new LIR_OpLoadKlass(obj, result, info, stub)); }
 
 #ifdef ASSERT
   void lir_assert(LIR_Condition condition, LIR_Opr opr1, LIR_Opr opr2, const char* msg, bool halt) { append(new LIR_OpAssert(condition, opr1, opr2, msg, halt)); }
 #endif
 };
diff a/src/hotspot/share/c1/c1_LIRGenerator.cpp b/src/hotspot/share/c1/c1_LIRGenerator.cpp
--- a/src/hotspot/share/c1/c1_LIRGenerator.cpp
+++ b/src/hotspot/share/c1/c1_LIRGenerator.cpp
@@ -1241,11 +1241,12 @@
                                      nullptr); // null CodeEmitInfo results in a leaf call
   __ move(call_result, result);
 }
 
 void LIRGenerator::load_klass(LIR_Opr obj, LIR_Opr klass, CodeEmitInfo* null_check_info) {
-  __ load_klass(obj, klass, null_check_info);
+  CodeStub* slow_path = UseCompactObjectHeaders ? new LoadKlassStub(klass) : nullptr;
+  __ load_klass(obj, klass, null_check_info, slow_path);
 }
 
 // Example: object.getClass ()
 void LIRGenerator::do_getClass(Intrinsic* x) {
   assert(x->number_of_arguments() == 1, "wrong type");
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
@@ -684,10 +684,17 @@
     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);
+      narrowKlass nk = CompressedKlassPointers::encode_not_null(requested_k, _requested_static_archive_bottom);
+      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";
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
@@ -191,12 +191,16 @@
 
   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) {
+      oopDesc::release_set_mark(mem, k->prototype_header());
+    } else {
+      oopDesc::set_mark(mem, markWord::prototype());
+      oopDesc::release_set_klass(mem, k);
+    }
   }
   {
     // This is copied from ObjArrayAllocator::initialize
     arrayOopDesc::set_length(mem, length);
   }
@@ -258,13 +262,17 @@
 void 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::objectArrayKlassObj(); // 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);
 }
 
 void ArchiveHeapWriter::maybe_fill_gc_region_gap(size_t required_byte_size) {
   // We fill only with arrays (so we don't need to use a single HeapWord filler if the
@@ -420,17 +428,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_mark(fake_oop->mark().set_narrow_klass(nk));
+  } else {
+    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.
   if (src_obj != nullptr && !src_obj->fast_no_hash_check()) {
     int 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(int archived_hash = fake_oop->identity_hash());
     assert(src_hash == archived_hash, "Different hash codes: original %x, archived %x", src_hash, archived_hash);
   }
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
@@ -201,10 +201,11 @@
     copy_base_archive_name(Arguments::GetSharedArchivePath());
   }
   _core_region_alignment = core_region_alignment;
   _obj_alignment = ObjectAlignmentInBytes;
   _compact_strings = CompactStrings;
+  _compact_headers = UseCompactObjectHeaders;
   if (DumpSharedSpaces && HeapShared::can_write()) {
     _narrow_oop_mode = CompressedOops::mode();
     _narrow_oop_base = CompressedOops::base();
     _narrow_oop_shift = CompressedOops::shift();
     if (UseCompressedOops) {
@@ -279,10 +280,11 @@
   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("- narrow_klass_shift:             %d", _narrow_klass_shift);
   st->print_cr("- compressed_oops:                %d", _compressed_oops);
   st->print_cr("- compressed_class_ptrs:          %d", _compressed_class_ptrs);
@@ -2406,10 +2408,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) {
     MetaspaceShared::disable_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,10 +185,11 @@
   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
   int     _narrow_klass_shift;                    // save narrow klass base and shift
   bool    _compressed_oops;                       // save the flag UseCompressedOops
   bool    _compressed_class_ptrs;                 // save the flag UseCompressedClassPointers
@@ -253,10 +254,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; }
   int narrow_klass_shift()                 const { return _narrow_klass_shift; }
   address narrow_klass_base()              const { return (address)mapped_base_address(); }
   char* cloned_vtables()                   const { return from_mapped_offset(_cloned_vtables_offset); }
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/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
@@ -86,10 +86,11 @@
 #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"
@@ -1519,10 +1520,12 @@
 
   evac_failure_injector()->reset();
 
   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"
@@ -207,10 +208,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();
 
@@ -219,10 +222,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();
 }
 
@@ -387,11 +392,12 @@
     }
   }
   return lowest_current;
 }
 
-void G1FullCollector::phase2c_prepare_serial_compaction() {
+template <bool ALT_FWD>
+void G1FullCollector::phase2c_prepare_serial_compaction_impl() {
   GCTraceTime(Debug, gc, phases) debug("Phase 2: Prepare serial compaction", scope()->timer());
   // At this point, we know that after parallel compaction there will be regions that
   // are partially compacted into. Thus, the last compaction region of all
   // compaction queues still have space in them. We try to re-compact these regions
   // in serial to avoid a premature OOM when the mutator wants to allocate the first
@@ -412,11 +418,11 @@
   HeapRegion* start_hr = _heap->region_at(start_serial);
   serial_cp->add(start_hr);
   serial_cp->initialize(start_hr);
 
   HeapWord* dense_prefix_top = compaction_top(start_hr);
-  G1SerialRePrepareClosure re_prepare(serial_cp, dense_prefix_top);
+  G1SerialRePrepareClosure<ALT_FWD> re_prepare(serial_cp, dense_prefix_top);
 
   for (uint i = start_serial + 1; i < _heap->max_reserved_regions(); i++) {
     if (is_compaction_target(i)) {
       HeapRegion* current = _heap->region_at(i);
       set_compaction_top(current, current->bottom());
@@ -425,11 +431,20 @@
     }
   }
   serial_cp->update();
 }
 
-void G1FullCollector::phase2d_prepare_humongous_compaction() {
+void G1FullCollector::phase2c_prepare_serial_compaction() {
+  if (UseAltGCForwarding) {
+    phase2c_prepare_serial_compaction_impl<true>();
+  } else {
+    phase2c_prepare_serial_compaction_impl<false>();
+  }
+}
+
+template <bool ALT_FWD>
+void G1FullCollector::phase2d_prepare_humongous_compaction_impl() {
   GCTraceTime(Debug, gc, phases) debug("Phase 2: Prepare humongous compaction", scope()->timer());
   G1FullGCCompactionPoint* serial_cp = serial_compaction_point();
   assert(serial_cp->has_regions(), "Sanity!" );
 
   uint last_serial_target = serial_cp->current_region()->hrm_index();
@@ -443,21 +458,29 @@
 
     if (hr == nullptr) {
       region_index++;
       continue;
     } else if (hr->is_starts_humongous()) {
-      uint num_regions = humongous_cp->forward_humongous(hr);
+      uint num_regions = humongous_cp->forward_humongous<ALT_FWD>(hr);
       region_index += num_regions; // Skip over the continues humongous regions.
       continue;
     } else if (is_compaction_target(region_index)) {
       // Add the region to the humongous compaction point.
       humongous_cp->add(hr);
     }
     region_index++;
   }
 }
 
+void G1FullCollector::phase2d_prepare_humongous_compaction() {
+  if (UseAltGCForwarding) {
+    phase2d_prepare_humongous_compaction_impl<true>();
+  } else {
+    phase2d_prepare_humongous_compaction_impl<false>();
+  }
+}
+
 void G1FullCollector::phase3_adjust_pointers() {
   // Adjust the pointers to reflect the new locations
   GCTraceTime(Info, gc, phases) info("Phase 3: Adjust pointers", scope()->timer());
 
   G1FullGCAdjustTask task(this);
diff a/src/hotspot/share/gc/g1/g1FullCollector.hpp b/src/hotspot/share/gc/g1/g1FullCollector.hpp
--- a/src/hotspot/share/gc/g1/g1FullCollector.hpp
+++ b/src/hotspot/share/gc/g1/g1FullCollector.hpp
@@ -156,11 +156,15 @@
   void phase1_mark_live_objects();
   void phase2_prepare_compaction();
 
   void phase2a_determine_worklists();
   bool phase2b_forward_oops();
+  template <bool ALT_FWD>
+  void phase2c_prepare_serial_compaction_impl();
   void phase2c_prepare_serial_compaction();
+  template <bool ALT_FWD>
+  void phase2d_prepare_humongous_compaction_impl();
   void phase2d_prepare_humongous_compaction();
 
   void phase3_adjust_pointers();
   void phase4_do_compaction();
   void phase5_reset_metadata();
diff a/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp b/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp
--- a/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp
+++ b/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp
@@ -38,14 +38,15 @@
 #include "gc/shared/weakProcessor.inline.hpp"
 #include "logging/log.hpp"
 #include "memory/iterator.inline.hpp"
 #include "runtime/atomic.hpp"
 
+template <bool ALT_FWD>
 class G1AdjustLiveClosure : public StackObj {
-  G1AdjustClosure* _adjust_closure;
+  G1AdjustClosure<ALT_FWD>* _adjust_closure;
 public:
-  G1AdjustLiveClosure(G1AdjustClosure* cl) :
+  G1AdjustLiveClosure(G1AdjustClosure<ALT_FWD>* cl) :
     _adjust_closure(cl) { }
 
   size_t apply(oop object) {
     return object->oop_iterate_size(_adjust_closure);
   }
@@ -60,52 +61,71 @@
     _collector(collector),
     _bitmap(collector->mark_bitmap()),
     _worker_id(worker_id) { }
 
   bool do_heap_region(HeapRegion* r) {
-    G1AdjustClosure cl(_collector);
+    if (UseAltGCForwarding) {
+      return do_heap_region_impl<true>(r);
+    } else {
+      return do_heap_region_impl<false>(r);
+    }
+  }
+
+private:
+  template <bool ALT_FWD>
+  bool do_heap_region_impl(HeapRegion* r) {
+    G1AdjustClosure<ALT_FWD> cl(_collector);
     if (r->is_humongous()) {
       // Special handling for humongous regions to get somewhat better
       // work distribution.
       oop obj = cast_to_oop(r->humongous_start_region()->bottom());
       obj->oop_iterate(&cl, MemRegion(r->bottom(), r->top()));
     } else if (!r->is_free()) {
       // Free regions do not contain objects to iterate. So skip them.
-      G1AdjustLiveClosure adjust(&cl);
+      G1AdjustLiveClosure<ALT_FWD> adjust(&cl);
       r->apply_to_marked_objects(_bitmap, &adjust);
     }
     return false;
   }
 };
 
 G1FullGCAdjustTask::G1FullGCAdjustTask(G1FullCollector* collector) :
     G1FullGCTask("G1 Adjust", collector),
     _root_processor(G1CollectedHeap::heap(), collector->workers()),
     _weak_proc_task(collector->workers()),
-    _hrclaimer(collector->workers()),
-    _adjust(collector) {
+    _hrclaimer(collector->workers()) {
   ClassLoaderDataGraph::verify_claimed_marks_cleared(ClassLoaderData::_claim_stw_fullgc_adjust);
 }
 
-void G1FullGCAdjustTask::work(uint worker_id) {
+template <bool ALT_FWD>
+void G1FullGCAdjustTask::work_impl(uint worker_id) {
   Ticks start = Ticks::now();
   ResourceMark rm;
 
   // Adjust preserved marks first since they are not balanced.
   G1FullGCMarker* marker = collector()->marker(worker_id);
   marker->preserved_stack()->adjust_during_full_gc();
 
+  G1AdjustClosure<ALT_FWD> adjust(collector());
   {
     // Adjust the weak roots.
     AlwaysTrueClosure always_alive;
-    _weak_proc_task.work(worker_id, &always_alive, &_adjust);
+    _weak_proc_task.work(worker_id, &always_alive, &adjust);
   }
 
-  CLDToOopClosure adjust_cld(&_adjust, ClassLoaderData::_claim_stw_fullgc_adjust);
-  CodeBlobToOopClosure adjust_code(&_adjust, CodeBlobToOopClosure::FixRelocations);
-  _root_processor.process_all_roots(&_adjust, &adjust_cld, &adjust_code);
+  CLDToOopClosure adjust_cld(&adjust, ClassLoaderData::_claim_stw_fullgc_adjust);
+  CodeBlobToOopClosure adjust_code(&adjust, CodeBlobToOopClosure::FixRelocations);
+  _root_processor.process_all_roots(&adjust, &adjust_cld, &adjust_code);
 
   // Now adjust pointers region by region
   G1AdjustRegionClosure blk(collector(), worker_id);
   G1CollectedHeap::heap()->heap_region_par_iterate_from_worker_offset(&blk, &_hrclaimer, worker_id);
   log_task("Adjust task", worker_id, start);
 }
+
+void G1FullGCAdjustTask::work(uint worker_id) {
+  if (UseAltGCForwarding) {
+    work_impl<true>(worker_id);
+  } else {
+    work_impl<false>(worker_id);
+  }
+}
diff a/src/hotspot/share/gc/g1/g1FullGCAdjustTask.hpp b/src/hotspot/share/gc/g1/g1FullGCAdjustTask.hpp
--- a/src/hotspot/share/gc/g1/g1FullGCAdjustTask.hpp
+++ b/src/hotspot/share/gc/g1/g1FullGCAdjustTask.hpp
@@ -36,12 +36,13 @@
 
 class G1FullGCAdjustTask : public G1FullGCTask {
   G1RootProcessor          _root_processor;
   WeakProcessor::Task      _weak_proc_task;
   HeapRegionClaimer        _hrclaimer;
-  G1AdjustClosure          _adjust;
 
+  template <bool ALT_FWD>
+  void work_impl(uint worker_id);
 public:
   G1FullGCAdjustTask(G1FullCollector* collector);
   void work(uint worker_id);
 };
 
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,39 +28,43 @@
 #include "gc/g1/g1FullCollector.inline.hpp"
 #include "gc/g1/g1FullGCCompactionPoint.hpp"
 #include "gc/g1/g1FullGCCompactTask.hpp"
 #include "gc/g1/heapRegion.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) {
+template <bool ALT_FWD>
+void G1FullGCCompactTask::G1CompactRegionClosure<ALT_FWD>::clear_in_bitmap(oop obj) {
   assert(_bitmap->is_marked(obj), "Should only compact marked objects");
   _bitmap->clear(obj);
 }
 
-size_t G1FullGCCompactTask::G1CompactRegionClosure::apply(oop obj) {
+template <bool ALT_FWD>
+size_t G1FullGCCompactTask::G1CompactRegionClosure<ALT_FWD>::apply(oop obj) {
   size_t size = obj->size();
-  if (obj->is_forwarded()) {
-    G1FullGCCompactTask::copy_object_to_new_location(obj);
+  if (SlidingForwarding::is_forwarded(obj)) {
+    G1FullGCCompactTask::copy_object_to_new_location<ALT_FWD>(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;
 }
 
+template <bool ALT_FWD>
 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<ALT_FWD>(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<ALT_FWD>(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");
@@ -75,12 +79,17 @@
     // for bitmap verification and to be able to use the bitmap
     // for evacuation failures in the next young collection. Testing
     // showed that it was better overall to clear bit by bit, compared
     // to clearing the whole region at the end. This difference was
     // clearly seen for regions with few marks.
-    G1CompactRegionClosure compact(collector()->mark_bitmap());
-    hr->apply_to_marked_objects(collector()->mark_bitmap(), &compact);
+    if (UseAltGCForwarding) {
+      G1CompactRegionClosure<true> compact(collector()->mark_bitmap());
+      hr->apply_to_marked_objects(collector()->mark_bitmap(), &compact);
+    } else {
+      G1CompactRegionClosure<false> compact(collector()->mark_bitmap());
+      hr->apply_to_marked_objects(collector()->mark_bitmap(), &compact);
+    }
   }
 
   hr->reset_compacted_after_full_gc(_collector->compaction_top(hr));
 }
 
@@ -102,32 +111,41 @@
        ++it) {
     compact_region(*it);
   }
 }
 
-void G1FullGCCompactTask::humongous_compaction() {
-  GCTraceTime(Debug, gc, phases) tm("Phase 4: Humonguous Compaction", collector()->scope()->timer());
-
+template <bool ALT_FWD>
+void G1FullGCCompactTask::humongous_compaction_impl() {
   for (HeapRegion* hr : collector()->humongous_compaction_regions()) {
     assert(collector()->is_compaction_target(hr->hrm_index()), "Sanity");
-    compact_humongous_obj(hr);
+    compact_humongous_obj<ALT_FWD>(hr);
+  }
+}
+
+void G1FullGCCompactTask::humongous_compaction() {
+  GCTraceTime(Debug, gc, phases) tm("Phase 4: Humonguous Compaction", collector()->scope()->timer());
+  if (UseAltGCForwarding) {
+    humongous_compaction_impl<true>();
+  } else {
+    humongous_compaction_impl<false>();
   }
 }
 
+template <bool ALT_FWD>
 void G1FullGCCompactTask::compact_humongous_obj(HeapRegion* src_hr) {
   assert(src_hr->is_starts_humongous(), "Should be start region of the humongous object");
 
   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<ALT_FWD>(obj));
 
   assert(collector()->mark_bitmap()->is_marked(obj), "Should only compact marked objects");
   collector()->mark_bitmap()->clear(obj);
 
-  copy_object_to_new_location(obj);
+  copy_object_to_new_location<ALT_FWD>(obj);
 
   uint dest_start_idx = _g1h->addr_to_region(destination);
   // Update the metadata for the destination regions.
   _g1h->set_humongous_metadata(_g1h->region_at(dest_start_idx), num_regions, word_size, false);
 
diff a/src/hotspot/share/gc/g1/g1FullGCCompactTask.hpp b/src/hotspot/share/gc/g1/g1FullGCCompactTask.hpp
--- a/src/hotspot/share/gc/g1/g1FullGCCompactTask.hpp
+++ b/src/hotspot/share/gc/g1/g1FullGCCompactTask.hpp
@@ -39,15 +39,20 @@
   G1FullCollector* _collector;
   HeapRegionClaimer _claimer;
   G1CollectedHeap* _g1h;
 
   void compact_region(HeapRegion* hr);
+  template <bool ALT_FWD>
   void compact_humongous_obj(HeapRegion* hr);
   void free_non_overlapping_regions(uint src_start_idx, uint dest_start_idx, uint num_regions);
 
+  template <bool ALT_FWD>
   static void copy_object_to_new_location(oop obj);
 
+  template <bool ALT_FWD>
+  void humongous_compaction_impl();
+
 public:
   G1FullGCCompactTask(G1FullCollector* collector) :
     G1FullGCTask("G1 Compact Task", collector),
     _collector(collector),
     _claimer(collector->workers()),
@@ -55,10 +60,11 @@
 
   void work(uint worker_id);
   void serial_compaction();
   void humongous_compaction();
 
+  template <bool ALT_FWD>
   class G1CompactRegionClosure : public StackObj {
     G1CMBitMap* _bitmap;
     void clear_in_bitmap(oop object);
   public:
     G1CompactRegionClosure(G1CMBitMap* bitmap) : _bitmap(bitmap) { }
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/heapRegion.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) :
     _collector(collector),
@@ -90,31 +91,35 @@
   // Get the next region and re-initialize the values.
   _current_region = next_region();
   initialize_values();
 }
 
+template <bool ALT_FWD>
 void G1FullGCCompactionPoint::forward(oop object, size_t size) {
   assert(_current_region != nullptr, "Must have been initialized");
 
   // Ensure the object fit in the current region.
   while (!object_will_fit(size)) {
     switch_region();
   }
 
   // Store a forwarding pointer if the object should be moved.
   if (cast_from_oop<HeapWord*>(object) != _compaction_top) {
-    object->forward_to(cast_to_oop(_compaction_top));
-    assert(object->is_forwarded(), "must be forwarded");
+    SlidingForwarding::forward_to<ALT_FWD>(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);
 }
 
+template void G1FullGCCompactionPoint::forward<true>(oop object, size_t size);
+template void G1FullGCCompactionPoint::forward<false>(oop object, size_t size);
+
 void G1FullGCCompactionPoint::add(HeapRegion* hr) {
   _compaction_regions->append(hr);
 }
 
 void G1FullGCCompactionPoint::remove_at_or_above(uint bottom) {
@@ -143,10 +148,11 @@
                                        add(r);
                                        _collector->update_from_skip_compacting_to_compacting(r->hrm_index());
                                      });
 }
 
+template <bool ALT_FWD>
 uint G1FullGCCompactionPoint::forward_humongous(HeapRegion* hr) {
   assert(hr->is_starts_humongous(), "Sanity!");
 
   oop obj = cast_to_oop(hr->bottom());
   size_t obj_size = obj->size();
@@ -166,22 +172,25 @@
 
   // Preserve the mark for the humongous object as the region was initially not compacting.
   _collector->marker(0)->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<ALT_FWD>(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.
   _compaction_regions->remove_range(range_begin, (range_begin + num_regions));
 
   return num_regions;
 }
 
+template uint G1FullGCCompactionPoint::forward_humongous<true>(HeapRegion* hr);
+template uint G1FullGCCompactionPoint::forward_humongous<false>(HeapRegion* hr);
+
 uint G1FullGCCompactionPoint::find_contiguous_before(HeapRegion* hr, uint num_regions) {
   assert(num_regions > 0, "Sanity!");
   assert(has_regions(), "Sanity!");
 
   if (num_regions == 1) {
diff a/src/hotspot/share/gc/g1/g1FullGCCompactionPoint.hpp b/src/hotspot/share/gc/g1/g1FullGCCompactionPoint.hpp
--- a/src/hotspot/share/gc/g1/g1FullGCCompactionPoint.hpp
+++ b/src/hotspot/share/gc/g1/g1FullGCCompactionPoint.hpp
@@ -52,11 +52,13 @@
 
   bool has_regions();
   bool is_initialized();
   void initialize(HeapRegion* hr);
   void update();
+  template <bool ALT_FWD>
   void forward(oop object, size_t size);
+  template <bool ALT_FWD>
   uint forward_humongous(HeapRegion* hr);
   void add(HeapRegion* hr);
   void add_humongous(HeapRegion* hr);
 
   void remove_at_or_above(uint bottom);
diff a/src/hotspot/share/gc/g1/g1FullGCOopClosures.hpp b/src/hotspot/share/gc/g1/g1FullGCOopClosures.hpp
--- a/src/hotspot/share/gc/g1/g1FullGCOopClosures.hpp
+++ b/src/hotspot/share/gc/g1/g1FullGCOopClosures.hpp
@@ -71,10 +71,11 @@
   template <class T> inline void do_oop_work(T* p);
   virtual void do_oop(oop* p);
   virtual void do_oop(narrowOop* p);
 };
 
+template <bool ALT_FWD>
 class G1AdjustClosure : public BasicOopIterateClosure {
   G1FullCollector* _collector;
 
   template <class T> inline void adjust_pointer(T* p);
 public:
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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 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.
@@ -30,10 +30,11 @@
 #include "gc/g1/g1Allocator.inline.hpp"
 #include "gc/g1/g1FullCollector.inline.hpp"
 #include "gc/g1/g1ConcurrentMarkBitMap.inline.hpp"
 #include "gc/g1/g1FullGCMarker.inline.hpp"
 #include "gc/g1/heapRegionRemSet.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"
@@ -49,11 +50,12 @@
 
 inline void G1MarkAndPushClosure::do_oop(narrowOop* p) {
   do_oop_work(p);
 }
 
-template <class T> inline void G1AdjustClosure::adjust_pointer(T* p) {
+template <bool ALT_FWD>
+template <class T> inline void G1AdjustClosure<ALT_FWD>::adjust_pointer(T* p) {
   T heap_oop = RawAccess<>::oop_load(p);
   if (CompressedOops::is_null(heap_oop)) {
     return;
   }
 
@@ -63,21 +65,23 @@
     // 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<ALT_FWD>(obj);
     // Forwarded, just update.
     assert(G1CollectedHeap::heap()->is_in_reserved(forwardee), "should be in object space");
     RawAccess<IS_NOT_NULL>::oop_store(p, forwardee);
   }
 
 }
 
-inline void G1AdjustClosure::do_oop(oop* p)       { do_oop_work(p); }
-inline void G1AdjustClosure::do_oop(narrowOop* p) { do_oop_work(p); }
+template <bool ALT_FWD>
+inline void G1AdjustClosure<ALT_FWD>::do_oop(oop* p)       { do_oop_work(p); }
+template <bool ALT_FWD>
+inline void G1AdjustClosure<ALT_FWD>::do_oop(narrowOop* p) { do_oop_work(p); }
 
 inline bool G1IsAliveClosure::do_object_b(oop p) {
   return _bitmap->is_marked(p);
 }
 
diff a/src/hotspot/share/gc/g1/g1FullGCPrepareTask.cpp b/src/hotspot/share/gc/g1/g1FullGCPrepareTask.cpp
--- a/src/hotspot/share/gc/g1/g1FullGCPrepareTask.cpp
+++ b/src/hotspot/share/gc/g1/g1FullGCPrepareTask.cpp
@@ -102,20 +102,27 @@
   _collector(collector),
   _bitmap(collector->mark_bitmap()),
   _cp(cp) { }
 
 
-G1FullGCPrepareTask::G1PrepareCompactLiveClosure::G1PrepareCompactLiveClosure(G1FullGCCompactionPoint* cp) :
+template <bool ALT_FWD>
+G1FullGCPrepareTask::G1PrepareCompactLiveClosure<ALT_FWD>::G1PrepareCompactLiveClosure(G1FullGCCompactionPoint* cp) :
     _cp(cp) { }
 
-size_t G1FullGCPrepareTask::G1PrepareCompactLiveClosure::apply(oop object) {
+template <bool ALT_FWD>
+size_t G1FullGCPrepareTask::G1PrepareCompactLiveClosure<ALT_FWD>::apply(oop object) {
   size_t size = object->size();
-  _cp->forward(object, size);
+  _cp->forward<ALT_FWD>(object, size);
   return size;
 }
 
 void G1FullGCPrepareTask::G1CalculatePointersClosure::prepare_for_compaction(HeapRegion* hr) {
   if (!_collector->is_free(hr->hrm_index())) {
-    G1PrepareCompactLiveClosure prepare_compact(_cp);
-    hr->apply_to_marked_objects(_bitmap, &prepare_compact);
+    if (UseAltGCForwarding) {
+      G1PrepareCompactLiveClosure<true> prepare_compact(_cp);
+      hr->apply_to_marked_objects(_bitmap, &prepare_compact);
+    } else {
+      G1PrepareCompactLiveClosure<false> prepare_compact(_cp);
+      hr->apply_to_marked_objects(_bitmap, &prepare_compact);
+    }
   }
 }
diff a/src/hotspot/share/gc/g1/g1FullGCPrepareTask.hpp b/src/hotspot/share/gc/g1/g1FullGCPrepareTask.hpp
--- a/src/hotspot/share/gc/g1/g1FullGCPrepareTask.hpp
+++ b/src/hotspot/share/gc/g1/g1FullGCPrepareTask.hpp
@@ -87,10 +87,11 @@
                                G1FullGCCompactionPoint* cp);
 
     bool do_heap_region(HeapRegion* hr);
   };
 
+  template <bool ALT_FWD>
   class G1PrepareCompactLiveClosure : public StackObj {
     G1FullGCCompactionPoint* _cp;
 
   public:
     G1PrepareCompactLiveClosure(G1FullGCCompactionPoint* cp);
@@ -98,10 +99,11 @@
   };
 };
 
 // Closure to re-prepare objects in the serial compaction point queue regions for
 // serial compaction.
+template <bool ALT_FWD>
 class G1SerialRePrepareClosure : public StackObj {
   G1FullGCCompactionPoint* _cp;
   HeapWord* _dense_prefix_top;
 
 public:
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/heapRegion.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);
@@ -99,22 +100,23 @@
   }
 
   return false;
 }
 
-inline size_t G1SerialRePrepareClosure::apply(oop obj) {
-  if (obj->is_forwarded()) {
+template <bool ALT_FWD>
+inline size_t G1SerialRePrepareClosure<ALT_FWD>::apply(oop obj) {
+  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<ALT_FWD>(obj)) < _dense_prefix_top) {
       return obj->size();
     }
   }
 
   // Get size and forward.
   size_t size = obj->size();
-  _cp->forward(obj, size);
+  _cp->forward<ALT_FWD>(obj, size);
 
   return size;
 }
 
 #endif // SHARE_GC_G1_G1FULLGCPREPARETASK_INLINE_HPP
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
@@ -226,11 +226,11 @@
   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());
+      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
@@ -205,11 +205,11 @@
     return;
   }
 
   markWord m = obj->mark();
   if (m.is_marked()) {
-    obj = cast_to_oop(m.decode_pointer());
+    obj = obj->forwardee(m);
   } else {
     obj = do_copy_to_survivor_space(region_attr, obj, m);
   }
   RawAccess<IS_NOT_NULL>::oop_store(p, obj);
 
@@ -219,11 +219,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");
@@ -249,11 +249,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");
 
@@ -376,26 +376,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.
@@ -414,11 +414,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;
 }
 
@@ -451,11 +451,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);
 
   uint age = 0;
   G1HeapRegionAttr dest_attr = next_region_attr(region_attr, old_mark, age);
   HeapRegion* const from_region = _g1h->heap_region_containing(old);
@@ -464,11 +470,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);
     }
@@ -618,11 +624,11 @@
 
 NOINLINE
 oop G1ParScanThreadState::handle_evacuation_failure_par(oop old, markWord m, size_t word_sz) {
   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(r->hrm_index())) {
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
@@ -159,11 +159,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,
@@ -194,11 +194,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;
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
@@ -238,19 +238,20 @@
   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 {
+    if (obj->is_forwarded()) {
       assert(obj->forwardee() != obj, "must not be self-forwarded");
+      // 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 {
+      cl->do_object(obj);
+      p += obj->size();
     }
-#endif
-    p += cast_to_oop(p)->size();
   }
 }
 
 void MutableSpace::print_short() const { print_short_on(tty); }
 void MutableSpace::print_short_on( outputStream* st) const {
diff a/src/hotspot/share/gc/parallel/psOldGen.cpp b/src/hotspot/share/gc/parallel/psOldGen.cpp
--- a/src/hotspot/share/gc/parallel/psOldGen.cpp
+++ b/src/hotspot/share/gc/parallel/psOldGen.cpp
@@ -393,11 +393,13 @@
  public:
   VerifyObjectStartArrayClosure(ObjectStartArray* start_array) :
     _start_array(start_array) { }
 
   virtual void do_object(oop obj) {
-    HeapWord* test_addr = cast_from_oop<HeapWord*>(obj) + 1;
+    // With compact headers, the objects can be one-word sized.
+    size_t int_off = UseCompactObjectHeaders ? MIN2((size_t)1, obj->size() - 1) : 1;
+    HeapWord* test_addr = cast_from_oop<HeapWord*>(obj) + int_off;
     guarantee(_start_array->object_start(test_addr) == cast_from_oop<HeapWord*>(obj), "ObjectStartArray cannot find start of object");
     guarantee(_start_array->is_block_allocated(cast_from_oop<HeapWord*>(obj)), "ObjectStartArray missing block allocation");
   }
 };
 
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
@@ -293,11 +293,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();
@@ -331,11 +331,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
@@ -61,11 +61,11 @@
   oop obj = RawAccess<IS_NOT_NULL>::oop_load(p);
   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) {
@@ -74,18 +74,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);
       }
     }
   }
 }
@@ -150,11 +150,11 @@
     // 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
@@ -166,11 +166,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();
 
@@ -181,21 +188,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;
           }
         }
       }
@@ -217,21 +224,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.
@@ -252,11 +259,25 @@
   // 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_marked()) {
+      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
@@ -880,11 +880,11 @@
   // 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();
+        obj->forward_safe_init_mark();
       }
     }
   } cl;
   eden()->object_iterate(&cl);
   from()->object_iterate(&cl);
@@ -903,12 +903,11 @@
   _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/genMarkSweep.cpp b/src/hotspot/share/gc/serial/genMarkSweep.cpp
--- a/src/hotspot/share/gc/serial/genMarkSweep.cpp
+++ b/src/hotspot/share/gc/serial/genMarkSweep.cpp
@@ -44,10 +44,11 @@
 #include "gc/shared/genCollectedHeap.hpp"
 #include "gc/shared/generation.hpp"
 #include "gc/shared/modRefBarrierSet.hpp"
 #include "gc/shared/referencePolicy.hpp"
 #include "gc/shared/referenceProcessorPhaseTimes.hpp"
+#include "gc/shared/slidingForwarding.hpp"
 #include "gc/shared/space.hpp"
 #include "gc/shared/strongRootsScope.hpp"
 #include "gc/shared/weakProcessor.hpp"
 #include "memory/universe.hpp"
 #include "oops/instanceRefKlass.hpp"
@@ -86,10 +87,12 @@
 
   allocate_stacks();
 
   mark_sweep_phase1(clear_all_softrefs);
 
+  SlidingForwarding::begin();
+
   mark_sweep_phase2();
 
   // Don't add any more derived pointers during phase3
 #if COMPILER2_OR_JVMCI
   assert(DerivedPointerTable::is_active(), "Sanity");
@@ -104,10 +107,12 @@
 
   // Set saved marks for allocation profiler (and other things? -- dld)
   // (Should this be in general part?)
   gch->save_marks();
 
+  SlidingForwarding::end();
+
   deallocate_stacks();
 
   MarkSweep::_string_dedup_requests->flush();
 
   bool is_young_gen_empty = (gch->young_gen()->used() == 0);
@@ -258,19 +263,31 @@
   // Adjust the pointers to reflect the new locations
   GCTraceTime(Info, gc, phases) tm("Phase 3: Adjust pointers", gc_timer());
 
   ClassLoaderDataGraph::verify_claimed_marks_cleared(ClassLoaderData::_claim_stw_fullgc_adjust);
 
-  CodeBlobToOopClosure code_closure(&adjust_pointer_closure, CodeBlobToOopClosure::FixRelocations);
-  gch->process_roots(GenCollectedHeap::SO_AllCodeCache,
-                     &adjust_pointer_closure,
-                     &adjust_cld_closure,
-                     &adjust_cld_closure,
-                     &code_closure);
-
-  gch->gen_process_weak_roots(&adjust_pointer_closure);
-
+  if (UseAltGCForwarding) {
+    AdjustPointerClosure<true> adjust_pointer_closure;
+    CLDToOopClosure adjust_cld_closure(&adjust_pointer_closure, ClassLoaderData::_claim_stw_fullgc_adjust);
+    CodeBlobToOopClosure code_closure(&adjust_pointer_closure, CodeBlobToOopClosure::FixRelocations);
+    gch->process_roots(GenCollectedHeap::SO_AllCodeCache,
+                       &adjust_pointer_closure,
+                       &adjust_cld_closure,
+                       &adjust_cld_closure,
+                       &code_closure);
+    gch->gen_process_weak_roots(&adjust_pointer_closure);
+  } else {
+    AdjustPointerClosure<false> adjust_pointer_closure;
+    CLDToOopClosure adjust_cld_closure(&adjust_pointer_closure, ClassLoaderData::_claim_stw_fullgc_adjust);
+    CodeBlobToOopClosure code_closure(&adjust_pointer_closure, CodeBlobToOopClosure::FixRelocations);
+    gch->process_roots(GenCollectedHeap::SO_AllCodeCache,
+                       &adjust_pointer_closure,
+                       &adjust_cld_closure,
+                       &adjust_cld_closure,
+                       &code_closure);
+    gch->gen_process_weak_roots(&adjust_pointer_closure);
+  }
   adjust_marks();
   GenAdjustPointersClosure blk;
   gch->generation_iterate(&blk, true);
 }
 
diff a/src/hotspot/share/gc/serial/markSweep.cpp b/src/hotspot/share/gc/serial/markSweep.cpp
--- a/src/hotspot/share/gc/serial/markSweep.cpp
+++ b/src/hotspot/share/gc/serial/markSweep.cpp
@@ -58,11 +58,10 @@
 
 MarkSweep::FollowRootClosure  MarkSweep::follow_root_closure;
 
 MarkAndPushClosure MarkSweep::mark_and_push_closure(ClassLoaderData::_claim_stw_fullgc_mark);
 CLDToOopClosure    MarkSweep::follow_cld_closure(&mark_and_push_closure, ClassLoaderData::_claim_stw_fullgc_mark);
-CLDToOopClosure    MarkSweep::adjust_cld_closure(&adjust_pointer_closure, ClassLoaderData::_claim_stw_fullgc_adjust);
 
 template <class T> void MarkSweep::KeepAliveClosure::do_oop_work(T* p) {
   mark_and_push(p);
 }
 
@@ -140,12 +139,13 @@
 }
 
 void MarkSweep::FollowRootClosure::do_oop(oop* p)       { follow_root(p); }
 void MarkSweep::FollowRootClosure::do_oop(narrowOop* p) { follow_root(p); }
 
+template <bool ALT_FWD>
 void PreservedMark::adjust_pointer() {
-  MarkSweep::adjust_pointer(&_obj);
+  MarkSweep::adjust_pointer<ALT_FWD>(&_obj);
 }
 
 void PreservedMark::restore() {
   _obj->set_mark(_mark);
 }
@@ -170,16 +170,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 markSweep.
   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);
   }
 }
@@ -198,23 +200,30 @@
 template <typename T>
 void MarkAndPushClosure::do_oop_work(T* p)            { MarkSweep::mark_and_push(p); }
 void MarkAndPushClosure::do_oop(      oop* p)         { do_oop_work(p); }
 void MarkAndPushClosure::do_oop(narrowOop* p)         { do_oop_work(p); }
 
-AdjustPointerClosure MarkSweep::adjust_pointer_closure;
-
-void MarkSweep::adjust_marks() {
+template <bool ALT_FWD>
+void MarkSweep::adjust_marks_impl() {
   // adjust the oops we saved earlier
   for (size_t i = 0; i < _preserved_count; i++) {
-    _preserved_marks[i].adjust_pointer();
+    _preserved_marks[i].adjust_pointer<ALT_FWD>();
   }
 
   // deal with the overflow stack
   StackIterator<PreservedMark, mtGC> iter(_preserved_overflow_stack);
   while (!iter.is_empty()) {
     PreservedMark* p = iter.next_addr();
-    p->adjust_pointer();
+    p->adjust_pointer<ALT_FWD>();
+  }
+}
+
+void MarkSweep::adjust_marks() {
+  if (UseAltGCForwarding) {
+    adjust_marks_impl<true>();
+  } else {
+    adjust_marks_impl<false>();
   }
 }
 
 void MarkSweep::restore_marks() {
   log_trace(gc)("Restoring " SIZE_FORMAT " marks", _preserved_count + _preserved_overflow_stack.size());
diff a/src/hotspot/share/gc/serial/markSweep.hpp b/src/hotspot/share/gc/serial/markSweep.hpp
--- a/src/hotspot/share/gc/serial/markSweep.hpp
+++ b/src/hotspot/share/gc/serial/markSweep.hpp
@@ -48,11 +48,10 @@
 // Class unloading will only occur when a full gc is invoked.
 
 // declared at end
 class PreservedMark;
 class MarkAndPushClosure;
-class AdjustPointerClosure;
 
 class MarkSweep : AllStatic {
   //
   // Inline closure decls
   //
@@ -82,11 +81,10 @@
   };
 
   //
   // Friend decls
   //
-  friend class AdjustPointerClosure;
   friend class KeepAliveClosure;
 
   //
   // Vars
   //
@@ -122,12 +120,10 @@
   static IsAliveClosure       is_alive;
   static FollowRootClosure    follow_root_closure;
   static MarkAndPushClosure   mark_and_push_closure;
   static FollowStackClosure   follow_stack_closure;
   static CLDToOopClosure      follow_cld_closure;
-  static AdjustPointerClosure adjust_pointer_closure;
-  static CLDToOopClosure      adjust_cld_closure;
 
   // Accessors
   static uint total_invocations() { return _total_invocations; }
 
   // Reference Processing
@@ -139,20 +135,25 @@
   static void preserve_mark(oop p, markWord mark);
                                 // Save the mark word so it can be restored later
   static void adjust_marks();   // Adjust the pointers in the preserved marks table
   static void restore_marks();  // Restore the marks that we saved in preserve_mark
 
+  template <bool ALT_FWD>
   static size_t adjust_pointers(oop obj);
 
   static void follow_stack();   // Empty marking stack.
 
-  template <class T> static inline void adjust_pointer(T* p);
+  template <bool ALT_FWD, class T>
+  static void adjust_pointer(T* p);
 
   // Check mark and maybe push on marking stack
   template <class T> static void mark_and_push(T* p);
 
  private:
+  template <bool ALT_FWD>
+  static void adjust_marks_impl();
+
   // Call backs for marking
   static void mark_object(oop obj);
   // Mark pointer and follow contents.  Empty marking stack afterwards.
   template <class T> static inline void follow_root(T* p);
 
@@ -176,10 +177,11 @@
   void set_ref_discoverer(ReferenceDiscoverer* rd) {
     set_ref_discoverer_internal(rd);
   }
 };
 
+template <bool ALT_FWD>
 class AdjustPointerClosure: public BasicOopIterateClosure {
  public:
   template <typename T> void do_oop_work(T* p);
   virtual void do_oop(oop* p);
   virtual void do_oop(narrowOop* p);
@@ -191,10 +193,11 @@
   oop _obj;
   markWord _mark;
 
 public:
   PreservedMark(oop obj, markWord mark) : _obj(obj), _mark(mark) {}
+  template <bool ALT_FWD>
   void adjust_pointer();
   void restore();
 };
 
 #endif // SHARE_GC_SERIAL_MARKSWEEP_HPP
diff a/src/hotspot/share/gc/serial/markSweep.inline.hpp b/src/hotspot/share/gc/serial/markSweep.inline.hpp
--- a/src/hotspot/share/gc/serial/markSweep.inline.hpp
+++ b/src/hotspot/share/gc/serial/markSweep.inline.hpp
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2000, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 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.
@@ -29,37 +29,45 @@
 
 #include "classfile/classLoaderData.inline.hpp"
 #include "classfile/javaClasses.inline.hpp"
 #include "gc/shared/continuationGCSupport.inline.hpp"
 #include "gc/serial/serialStringDedup.hpp"
+#include "gc/shared/slidingForwarding.inline.hpp"
 #include "memory/universe.hpp"
 #include "oops/markWord.hpp"
 #include "oops/access.inline.hpp"
 #include "oops/compressedOops.inline.hpp"
 #include "oops/oop.inline.hpp"
 #include "utilities/align.hpp"
 #include "utilities/stack.inline.hpp"
 
-template <class T> inline void MarkSweep::adjust_pointer(T* p) {
+template <bool ALT_FWD, class T>
+inline void MarkSweep::adjust_pointer(T* p) {
   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<ALT_FWD>(obj);
       assert(is_object_aligned(new_obj), "oop must be aligned");
       RawAccess<IS_NOT_NULL>::oop_store(p, new_obj);
     }
   }
 }
 
+template <bool ALT_FWD>
 template <typename T>
-void AdjustPointerClosure::do_oop_work(T* p)           { MarkSweep::adjust_pointer(p); }
-inline void AdjustPointerClosure::do_oop(oop* p)       { do_oop_work(p); }
-inline void AdjustPointerClosure::do_oop(narrowOop* p) { do_oop_work(p); }
+void AdjustPointerClosure<ALT_FWD>::do_oop_work(T* p)           { MarkSweep::adjust_pointer<ALT_FWD>(p); }
+template <bool ALT_FWD>
+inline void AdjustPointerClosure<ALT_FWD>::do_oop(oop* p)       { do_oop_work(p); }
 
+template <bool ALT_FWD>
+inline void AdjustPointerClosure<ALT_FWD>::do_oop(narrowOop* p) { do_oop_work(p); }
+
+template <bool ALT_FWD>
 inline size_t MarkSweep::adjust_pointers(oop obj) {
-  return obj->oop_iterate_size(&MarkSweep::adjust_pointer_closure);
+  AdjustPointerClosure<ALT_FWD> adjust_pointer_closure;
+  return obj->oop_iterate_size(&adjust_pointer_closure);
 }
 
 #endif // SHARE_GC_SERIAL_MARKSWEEP_INLINE_HPP
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
@@ -226,11 +226,13 @@
 
   if (!is_in(object)) {
     return false;
   }
 
-  if (!Metaspace::contains(object->klass_raw())) {
+  // With compact headers, we can't safely access the class, due
+  // to possibly forwarded objects.
+  if (!UseCompactObjectHeaders && !Metaspace::contains(object->klass_raw())) {
     return false;
   }
 
   return true;
 }
@@ -398,27 +400,34 @@
     _perf_gc_cause->set_value(GCCause::to_string(v));
   }
   _gc_cause = v;
 }
 
+// Returns the header size in words aligned to the requirements of the
+// array object type.
+static int int_array_header_size() {
+  size_t typesize_in_bytes = arrayOopDesc::header_size_in_bytes();
+  return (int)align_up(typesize_in_bytes, HeapWordSize)/HeapWordSize;
+}
+
 size_t CollectedHeap::max_tlab_size() const {
   // TLABs can't be bigger than we can fill with a int[Integer.MAX_VALUE].
   // This restriction could be removed by enabling filling with multiple arrays.
   // If we compute that the reasonable way as
   //    header_size + ((sizeof(jint) * max_jint) / HeapWordSize)
   // we'll overflow on the multiply, so we do the divide first.
   // We actually lose a little by dividing first,
   // but that just makes the TLAB  somewhat smaller than the biggest array,
   // which is fine, since we'll be able to fill that.
-  size_t max_int_size = typeArrayOopDesc::header_size(T_INT) +
+  size_t max_int_size = int_array_header_size() +
               sizeof(jint) *
               ((juint) max_jint / (size_t) HeapWordSize);
   return align_down(max_int_size, MinObjAlignment);
 }
 
 size_t CollectedHeap::filler_array_hdr_size() {
-  return align_object_offset(arrayOopDesc::header_size(T_INT)); // align to Long
+  return align_object_offset(int_array_header_size()); // align to Long
 }
 
 size_t CollectedHeap::filler_array_min_size() {
   return align_object_size(filler_array_hdr_size()); // align to MinObjAlignment
 }
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
@@ -305,11 +305,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/gc_globals.hpp b/src/hotspot/share/gc/shared/gc_globals.hpp
--- a/src/hotspot/share/gc/shared/gc_globals.hpp
+++ b/src/hotspot/share/gc/shared/gc_globals.hpp
@@ -688,11 +688,15 @@
           range(0, max_juint)                                               \
                                                                             \
   product(uint, GCCardSizeInBytes, 512,                                     \
           "Card table entry size (in bytes) for card based collectors")     \
           range(128, NOT_LP64(512) LP64_ONLY(1024))                         \
-          constraint(GCCardSizeInBytesConstraintFunc,AtParse)
-  // end of GC_FLAGS
+          constraint(GCCardSizeInBytesConstraintFunc,AtParse)               \
+                                                                            \
+  product(bool, UseAltGCForwarding, false, EXPERIMENTAL,                    \
+          "Use alternative GC forwarding that preserves object headers")    \
+
+// end of GC_FLAGS
 
 DECLARE_FLAGS(GC_FLAGS)
 
 #endif // SHARE_GC_SHARED_GC_GLOBALS_HPP
diff a/src/hotspot/share/gc/shared/genCollectedHeap.cpp b/src/hotspot/share/gc/shared/genCollectedHeap.cpp
--- a/src/hotspot/share/gc/shared/genCollectedHeap.cpp
+++ b/src/hotspot/share/gc/shared/genCollectedHeap.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/weakProcessor.hpp"
 #include "gc/shared/workerThread.hpp"
 #include "memory/iterator.hpp"
@@ -131,10 +132,12 @@
   _young_gen = _young_gen_spec->init(young_rs, rem_set());
   _old_gen = _old_gen_spec->init(old_rs, rem_set());
 
   GCInitLogger::print();
 
+  SlidingForwarding::initialize(_reserved, SpaceAlignment / HeapWordSize);
+
   return JNI_OK;
 }
 
 CardTableRS* GenCollectedHeap::create_rem_set(const MemRegion& reserved_region) {
   return new CardTableRS(reserved_region);
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
@@ -375,22 +375,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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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.
@@ -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"
@@ -38,22 +39,31 @@
     elem.set_mark();
   }
   assert_empty();
 }
 
-void PreservedMarks::adjust_during_full_gc() {
+template <bool ALT_FWD>
+void PreservedMarks::adjust_during_full_gc_impl() {
   StackIterator<OopAndMarkWord, mtGC> iter(_stack);
   while (!iter.is_empty()) {
     OopAndMarkWord* elem = iter.next_addr();
 
     oop obj = elem->get_oop();
     if (obj->is_forwarded()) {
-      elem->set_oop(obj->forwardee());
+      elem->set_oop(SlidingForwarding::forwardee<ALT_FWD>(obj));
     }
   }
 }
 
+void PreservedMarks::adjust_during_full_gc() {
+  if (UseAltGCForwarding) {
+    adjust_during_full_gc_impl<true>();
+  } else {
+    adjust_during_full_gc_impl<false>();
+  }
+}
+
 void PreservedMarks::restore_and_increment(volatile size_t* const total_size_addr) {
   const size_t stack_size = size();
   restore();
   // Only do the atomic add if the size is > 0.
   if (stack_size > 0) {
diff a/src/hotspot/share/gc/shared/preservedMarks.hpp b/src/hotspot/share/gc/shared/preservedMarks.hpp
--- a/src/hotspot/share/gc/shared/preservedMarks.hpp
+++ b/src/hotspot/share/gc/shared/preservedMarks.hpp
@@ -52,10 +52,13 @@
 
   OopAndMarkWordStack _stack;
 
   inline bool should_preserve_mark(oop obj, markWord m) const;
 
+  template <bool ALT_FWD>
+  void adjust_during_full_gc_impl();
+
 public:
   size_t size() const { return _stack.size(); }
   inline void push_if_necessary(oop obj, markWord m);
   inline void push_always(oop obj, markWord m);
   // Iterate over the stack, restore all preserved marks, and
diff a/src/hotspot/share/gc/shared/preservedMarks.inline.hpp b/src/hotspot/share/gc/shared/preservedMarks.inline.hpp
--- a/src/hotspot/share/gc/shared/preservedMarks.inline.hpp
+++ b/src/hotspot/share/gc/shared/preservedMarks.inline.hpp
@@ -24,10 +24,11 @@
 
 #ifndef SHARE_GC_SHARED_PRESERVEDMARKS_INLINE_HPP
 #define SHARE_GC_SHARED_PRESERVEDMARKS_INLINE_HPP
 
 #include "gc/shared/preservedMarks.hpp"
+#include "gc/shared/slidingForwarding.inline.hpp"
 
 #include "logging/log.hpp"
 #include "oops/oop.inline.hpp"
 #include "utilities/stack.inline.hpp"
 
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,123 @@
+/*
+ * 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
+  if (UseAltGCForwarding) {
+    _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
+  if (UseAltGCForwarding) {
+    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
+  if (UseAltGCForwarding) {
+    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,181 @@
+/*
+ * 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);
+
+  template <bool ALT_FWD>
+  static inline void forward_to(oop from, oop to);
+  template <bool ALT_FWD>
+  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,171 @@
+/*
+ * 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");
+  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);
+  }
+}
+
+template <bool ALT_FWD>
+inline void SlidingForwarding::forward_to(oop obj, oop fwd) {
+#ifdef _LP64
+  if (ALT_FWD) {
+    assert(_bases_table != nullptr, "expect sliding forwarding initialized");
+    forward_to_impl(obj, fwd);
+    assert(forwardee<ALT_FWD>(obj) == fwd, "must be forwarded to correct forwardee");
+  } else
+#endif
+  {
+    obj->forward_to(fwd);
+  }
+}
+
+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);
+}
+
+template <bool ALT_FWD>
+inline oop SlidingForwarding::forwardee(oop obj) {
+#ifdef _LP64
+  if (ALT_FWD) {
+    assert(_bases_table != nullptr, "expect sliding forwarding initialized");
+    return forwardee_impl(obj);
+  } else
+#endif
+  {
+    return obj->forwardee();
+  }
+}
+
+#endif // SHARE_GC_SHARED_SLIDINGFORWARDING_INLINE_HPP
diff a/src/hotspot/share/gc/shared/space.cpp b/src/hotspot/share/gc/shared/space.cpp
--- a/src/hotspot/share/gc/shared/space.cpp
+++ b/src/hotspot/share/gc/shared/space.cpp
@@ -25,10 +25,11 @@
 #include "precompiled.hpp"
 #include "classfile/vmClasses.hpp"
 #include "classfile/vmSymbols.hpp"
 #include "gc/shared/collectedHeap.inline.hpp"
 #include "gc/shared/genCollectedHeap.hpp"
+#include "gc/shared/slidingForwarding.inline.hpp"
 #include "gc/shared/space.hpp"
 #include "gc/shared/space.inline.hpp"
 #include "gc/shared/spaceDecorator.inline.hpp"
 #include "memory/iterator.inline.hpp"
 #include "memory/universe.hpp"
@@ -242,11 +243,11 @@
 void ContiguousSpace::mangle_unused_area_complete() {
   mangler()->mangle_unused_area_complete();
 }
 #endif  // NOT_PRODUCT
 
-
+template <bool ALT_FWD>
 HeapWord* ContiguousSpace::forward(oop q, size_t size,
                                     CompactPoint* cp, HeapWord* compact_top) {
   // q is alive
   // First check if we should switch compaction space
   assert(this == cp->space, "'this' should be current compaction space.");
@@ -267,17 +268,17 @@
     compaction_max_size = pointer_delta(cp->space->end(), compact_top);
   }
 
   // store the forwarding pointer into the mark word
   if (cast_from_oop<HeapWord*>(q) != compact_top) {
-    q->forward_to(cast_to_oop(compact_top));
+    SlidingForwarding::forward_to<ALT_FWD>(q, cast_to_oop(compact_top));
     assert(q->is_gc_marked(), "encoding the pointer should preserve the mark");
   } else {
     // if the object isn't moving we can just set the mark to the default
     // mark and handle it specially later on.
     q->init_mark();
-    assert(!q->is_forwarded(), "should not be forwarded");
+    assert(SlidingForwarding::is_not_forwarded(q), "should not be forwarded");
   }
 
   compact_top += size;
 
   // We need to update the offset table so that the beginnings of objects can be
@@ -287,11 +288,12 @@
   return compact_top;
 }
 
 #if INCLUDE_SERIALGC
 
-void ContiguousSpace::prepare_for_compaction(CompactPoint* cp) {
+template <bool ALT_FWD>
+void ContiguousSpace::prepare_for_compaction_impl(CompactPoint* cp) {
   // Compute the new addresses for the live objects and store it in the mark
   // Used by universe::mark_sweep_phase2()
 
   // We're sure to be here before any objects are compacted into this
   // space, so this is a good time to initialize this:
@@ -320,11 +322,11 @@
   while (cur_obj < scan_limit) {
     if (cast_to_oop(cur_obj)->is_gc_marked()) {
       // prefetch beyond cur_obj
       Prefetch::write(cur_obj, interval);
       size_t size = cast_to_oop(cur_obj)->size();
-      compact_top = cp->space->forward(cast_to_oop(cur_obj), size, cp, compact_top);
+      compact_top = cp->space->forward<ALT_FWD>(cast_to_oop(cur_obj), size, cp, compact_top);
       cur_obj += size;
       end_of_live = cur_obj;
     } else {
       // run over all the contiguous dead objects
       HeapWord* end = cur_obj;
@@ -336,11 +338,11 @@
 
       // see if we might want to pretend this object is alive so that
       // we don't have to compact quite as often.
       if (cur_obj == compact_top && dead_spacer.insert_deadspace(cur_obj, end)) {
         oop obj = cast_to_oop(cur_obj);
-        compact_top = cp->space->forward(obj, obj->size(), cp, compact_top);
+        compact_top = cp->space->forward<ALT_FWD>(obj, obj->size(), cp, compact_top);
         end_of_live = end;
       } else {
         // otherwise, it really is a free region.
 
         // cur_obj is a pointer to a dead object. Use this dead memory to store a pointer to the next live object.
@@ -367,11 +369,20 @@
 
   // save the compaction_top of the compaction space.
   cp->space->set_compaction_top(compact_top);
 }
 
-void ContiguousSpace::adjust_pointers() {
+void ContiguousSpace::prepare_for_compaction(CompactPoint* cp) {
+  if (UseAltGCForwarding) {
+    prepare_for_compaction_impl<true>(cp);
+  } else {
+    prepare_for_compaction_impl<false>(cp);
+  }
+}
+
+template <bool ALT_FWD>
+void ContiguousSpace::adjust_pointers_impl() {
   // Check first is there is any work to do.
   if (used() == 0) {
     return;   // Nothing to do.
   }
 
@@ -390,11 +401,11 @@
   while (cur_obj < end_of_live) {
     Prefetch::write(cur_obj, interval);
     if (cur_obj < first_dead || cast_to_oop(cur_obj)->is_gc_marked()) {
       // cur_obj is alive
       // point all the oops to the new location
-      size_t size = MarkSweep::adjust_pointers(cast_to_oop(cur_obj));
+      size_t size = MarkSweep::adjust_pointers<ALT_FWD>(cast_to_oop(cur_obj));
       debug_only(prev_obj = cur_obj);
       cur_obj += size;
     } else {
       debug_only(prev_obj = cur_obj);
       // cur_obj is not a live object, instead it points at the next live object
@@ -404,11 +415,20 @@
   }
 
   assert(cur_obj == end_of_live, "just checking");
 }
 
-void ContiguousSpace::compact() {
+void ContiguousSpace::adjust_pointers() {
+  if (UseAltGCForwarding) {
+    adjust_pointers_impl<true>();
+  } else {
+    adjust_pointers_impl<false>();
+  }
+}
+
+template <bool ALT_FWD>
+void ContiguousSpace::compact_impl() {
   // Copy all live objects to their new location
   // Used by MarkSweep::mark_sweep_phase4()
 
   verify_up_to_first_dead(this);
 
@@ -433,22 +453,22 @@
     cur_obj = *(HeapWord**)(_first_dead);
   }
 
   debug_only(HeapWord* prev_obj = nullptr);
   while (cur_obj < end_of_live) {
-    if (!cast_to_oop(cur_obj)->is_forwarded()) {
+    if (SlidingForwarding::is_not_forwarded(cast_to_oop(cur_obj))) {
       debug_only(prev_obj = cur_obj);
       // The first word of the dead object contains a pointer to the next live object or end of space.
       cur_obj = *(HeapWord**)cur_obj;
       assert(cur_obj > prev_obj, "we should be moving forward through memory");
     } else {
       // prefetch beyond q
       Prefetch::read(cur_obj, scan_interval);
 
       // size and destination
       size_t size = cast_to_oop(cur_obj)->size();
-      HeapWord* compaction_top = cast_from_oop<HeapWord*>(cast_to_oop(cur_obj)->forwardee());
+      HeapWord* compaction_top = cast_from_oop<HeapWord*>(SlidingForwarding::forwardee<ALT_FWD>(cast_to_oop(cur_obj)));
 
       // prefetch beyond compaction_top
       Prefetch::write(compaction_top, copy_interval);
 
       // copy object and reinit its mark
@@ -467,10 +487,18 @@
   }
 
   clear_empty_region(this);
 }
 
+void ContiguousSpace::compact() {
+  if (UseAltGCForwarding) {
+    compact_impl<true>();
+  } else {
+    compact_impl<false>();
+  }
+}
+
 #endif // INCLUDE_SERIALGC
 
 void Space::print_short() const { print_short_on(tty); }
 
 void Space::print_short_on(outputStream* st) const {
diff a/src/hotspot/share/gc/shared/space.hpp b/src/hotspot/share/gc/shared/space.hpp
--- a/src/hotspot/share/gc/shared/space.hpp
+++ b/src/hotspot/share/gc/shared/space.hpp
@@ -309,11 +309,22 @@
 
   static inline void verify_up_to_first_dead(ContiguousSpace* space) NOT_DEBUG_RETURN;
 
   static inline void clear_empty_region(ContiguousSpace* space);
 
- protected:
+#if INCLUDE_SERIALGC
+  template <bool ALT_FWD>
+  void prepare_for_compaction_impl(CompactPoint* cp);
+
+  template <bool ALT_FWD>
+  void adjust_pointers_impl();
+
+  template <bool ALT_FWD>
+  void compact_impl();
+#endif
+
+protected:
   HeapWord* _top;
   // A helper for mangling the unused area of the space in debug builds.
   GenSpaceMangler* _mangler;
 
   // Used during compaction.
@@ -396,11 +407,12 @@
   // be one, since compaction must succeed -- we go to the first space of
   // the previous generation if necessary, updating "cp"), reset compact_top
   // and then forward.  In either case, returns the new value of "compact_top".
   // Invokes the "alloc_block" function of the then-current compaction
   // space.
-  virtual HeapWord* forward(oop q, size_t size, CompactPoint* cp,
+  template <bool ALT_FWD>
+  HeapWord* forward(oop q, size_t size, CompactPoint* cp,
                     HeapWord* compact_top);
 
   // Accessors
   HeapWord* top() const            { return _top;    }
   void set_top(HeapWord* value)    { _top = value; }
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/shenandoahConcurrentGC.hpp"
 #include "gc/shenandoah/shenandoahCollectionSet.hpp"
@@ -219,10 +220,12 @@
   {
     // 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();
@@ -232,10 +235,11 @@
 
   {
     // Epilogue
     _preserved_marks->restore(heap->workers());
     _preserved_marks->reclaim();
+    SlidingForwarding::end();
   }
 
   // Resize metaspace
   MetaspaceGC::compute_new_size();
 
@@ -293,10 +297,11 @@
   ShenandoahSTWMark mark(true /*full_gc*/);
   mark.mark();
   heap->parallel_cleaning(true /* full_gc */);
 }
 
+template <bool ALT_FWD>
 class ShenandoahPrepareForCompactionObjectClosure : public ObjectClosure {
 private:
   PreservedMarks*          const _preserved_marks;
   ShenandoahHeap*          const _heap;
   GrowableArray<ShenandoahHeapRegion*>& _empty_regions;
@@ -362,11 +367,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<ALT_FWD>(p, cast_to_oop(_compact_point));
     }
     _compact_point += obj_size;
   }
 };
 
@@ -394,10 +399,20 @@
     // moves are special cased here, because their moves are handled separately.
     return r->is_stw_move_allowed() && !r->is_humongous();
   }
 
   void work(uint worker_id) {
+    if (UseAltGCForwarding) {
+      work_impl<true>(worker_id);
+    } else {
+      work_impl<false>(worker_id);
+    }
+  }
+
+private:
+  template <bool ALT_FWD>
+  void work_impl(uint worker_id) {
     ShenandoahParallelWorkerSession worker_session(worker_id);
     ShenandoahHeapRegionSet* slice = _worker_slices[worker_id];
     ShenandoahHeapRegionSetIterator it(slice);
     ShenandoahHeapRegion* from_region = it.next();
     // No work?
@@ -409,11 +424,11 @@
     // Remember empty regions and reuse them as needed.
     ResourceMark rm;
 
     GrowableArray<ShenandoahHeapRegion*> empty_regions((int)_heap->num_regions());
 
-    ShenandoahPrepareForCompactionObjectClosure cl(_preserved_marks->get(worker_id), empty_regions, from_region);
+    ShenandoahPrepareForCompactionObjectClosure<ALT_FWD> cl(_preserved_marks->get(worker_id), empty_regions, from_region);
 
     while (from_region != nullptr) {
       assert(is_candidate_region(from_region), "Sanity");
 
       cl.set_from_region(from_region);
@@ -435,11 +450,12 @@
       r->set_new_top(r->bottom());
     }
   }
 };
 
-void ShenandoahFullGC::calculate_target_humongous_objects() {
+template <bool ALT_FWD>
+void ShenandoahFullGC::calculate_target_humongous_objects_impl() {
   ShenandoahHeap* heap = ShenandoahHeap::heap();
 
   // Compute the new addresses for humongous objects. We need to do this after addresses
   // for regular objects are calculated, and we know what regions in heap suffix are
   // available for humongous moves.
@@ -471,11 +487,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<ALT_FWD>(old_obj, cast_to_oop(heap->get_region(start)->bottom()));
         to_end = start;
         continue;
       }
     }
 
@@ -483,10 +499,18 @@
     to_begin = r->index();
     to_end = r->index();
   }
 }
 
+void ShenandoahFullGC::calculate_target_humongous_objects() {
+  if (UseAltGCForwarding) {
+    calculate_target_humongous_objects_impl<true>();
+  } else {
+    calculate_target_humongous_objects_impl<false>();
+  }
+}
+
 class ShenandoahEnsureHeapActiveClosure: public ShenandoahHeapRegionClosure {
 private:
   ShenandoahHeap* const _heap;
 
 public:
@@ -720,10 +744,11 @@
     ShenandoahGCPhase phase(ShenandoahPhaseTimings::full_gc_calculate_addresses_humong);
     calculate_target_humongous_objects();
   }
 }
 
+template <bool ALT_FWD>
 class ShenandoahAdjustPointersClosure : public MetadataVisitingOopIterateClosure {
 private:
   ShenandoahHeap* const _heap;
   ShenandoahMarkingContext* const _ctx;
 
@@ -731,12 +756,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<ALT_FWD>(obj);
         RawAccess<IS_NOT_NULL>::oop_store(p, forw);
       }
     }
   }
 
@@ -749,14 +774,15 @@
   void do_oop(narrowOop* p) { do_oop_work(p); }
   void do_method(Method* m) {}
   void do_nmethod(nmethod* nm) {}
 };
 
+template <bool ALT_FWD>
 class ShenandoahAdjustPointersObjectClosure : public ObjectClosure {
 private:
   ShenandoahHeap* const _heap;
-  ShenandoahAdjustPointersClosure _cl;
+  ShenandoahAdjustPointersClosure<ALT_FWD> _cl;
 
 public:
   ShenandoahAdjustPointersObjectClosure() :
     _heap(ShenandoahHeap::heap()) {
   }
@@ -775,39 +801,62 @@
   ShenandoahAdjustPointersTask() :
     WorkerTask("Shenandoah Adjust Pointers"),
     _heap(ShenandoahHeap::heap()) {
   }
 
-  void work(uint worker_id) {
+private:
+  template <bool ALT_FWD>
+  void work_impl(uint worker_id) {
     ShenandoahParallelWorkerSession worker_session(worker_id);
-    ShenandoahAdjustPointersObjectClosure obj_cl;
+    ShenandoahAdjustPointersObjectClosure<ALT_FWD> obj_cl;
     ShenandoahHeapRegion* r = _regions.next();
     while (r != nullptr) {
       if (!r->is_humongous_continuation() && r->has_live()) {
         _heap->marked_object_iterate(r, &obj_cl);
       }
       r = _regions.next();
     }
   }
+
+public:
+  void work(uint worker_id) {
+    if (UseAltGCForwarding) {
+      work_impl<true>(worker_id);
+    } else {
+      work_impl<false>(worker_id);
+    }
+  }
 };
 
 class ShenandoahAdjustRootPointersTask : public WorkerTask {
 private:
   ShenandoahRootAdjuster* _rp;
   PreservedMarksSet* _preserved_marks;
+
 public:
   ShenandoahAdjustRootPointersTask(ShenandoahRootAdjuster* rp, PreservedMarksSet* preserved_marks) :
     WorkerTask("Shenandoah Adjust Root Pointers"),
     _rp(rp),
     _preserved_marks(preserved_marks) {}
 
-  void work(uint worker_id) {
+private:
+  template <bool ALT_FWD>
+  void work_impl(uint worker_id) {
     ShenandoahParallelWorkerSession worker_session(worker_id);
-    ShenandoahAdjustPointersClosure cl;
+    ShenandoahAdjustPointersClosure<ALT_FWD> cl;
     _rp->roots_do(worker_id, &cl);
     _preserved_marks->get(worker_id)->adjust_during_full_gc();
   }
+
+public:
+  void work(uint worker_id) {
+    if (UseAltGCForwarding) {
+      work_impl<true>(worker_id);
+    } else {
+      work_impl<false>(worker_id);
+    }
+  }
 };
 
 void ShenandoahFullGC::phase3_update_references() {
   GCTraceTime(Info, gc, phases) time("Phase 3: Adjust pointers", _gc_timer);
   ShenandoahGCPhase adjust_pointer_phase(ShenandoahPhaseTimings::full_gc_adjust_pointers);
@@ -830,10 +879,11 @@
 
   ShenandoahAdjustPointersTask adjust_pointers_task;
   workers->run_task(&adjust_pointers_task);
 }
 
+template <bool ALT_FWD>
 class ShenandoahCompactObjectsClosure : public ObjectClosure {
 private:
   ShenandoahHeap* const _heap;
   uint            const _worker_id;
 
@@ -842,13 +892,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<ALT_FWD>(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);
@@ -867,25 +917,36 @@
     WorkerTask("Shenandoah Compact Objects"),
     _heap(ShenandoahHeap::heap()),
     _worker_slices(worker_slices) {
   }
 
-  void work(uint worker_id) {
+private:
+  template <bool ALT_FWD>
+  void work_impl(uint worker_id) {
     ShenandoahParallelWorkerSession worker_session(worker_id);
     ShenandoahHeapRegionSetIterator slice(_worker_slices[worker_id]);
 
-    ShenandoahCompactObjectsClosure cl(worker_id);
+    ShenandoahCompactObjectsClosure<ALT_FWD> cl(worker_id);
     ShenandoahHeapRegion* r = slice.next();
     while (r != nullptr) {
       assert(!r->is_humongous(), "must not get humongous regions here");
       if (r->has_live()) {
         _heap->marked_object_iterate(r, &cl);
       }
       r->set_top(r->new_top());
       r = slice.next();
     }
   }
+
+public:
+  void work(uint worker_id) {
+    if (UseAltGCForwarding) {
+      work_impl<true>(worker_id);
+    } else {
+      work_impl<false>(worker_id);
+    }
+  }
 };
 
 class ShenandoahPostCompactClosure : public ShenandoahHeapRegionClosure {
 private:
   ShenandoahHeap* const _heap;
@@ -937,11 +998,12 @@
   size_t get_live() {
     return _live;
   }
 };
 
-void ShenandoahFullGC::compact_humongous_objects() {
+template <bool ALT_FWD>
+void ShenandoahFullGC::compact_humongous_objects_impl() {
   // Compact humongous regions, based on their fwdptr objects.
   //
   // This code is serial, because doing the in-slice parallel sliding is tricky. In most cases,
   // humongous regions are already compacted, and do not require further moves, which alleviates
   // sliding costs. We may consider doing this in parallel in future.
@@ -950,20 +1012,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<ALT_FWD>(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);
@@ -1000,10 +1062,18 @@
       }
     }
   }
 }
 
+void ShenandoahFullGC::compact_humongous_objects() {
+  if (UseAltGCForwarding) {
+    compact_humongous_objects_impl<true>();
+  } else {
+    compact_humongous_objects_impl<false>();
+  }
+}
+
 // This is slightly different to ShHeap::reset_next_mark_bitmap:
 // we need to remain able to walk pinned regions.
 // Since pinned region do not move and don't get compacted, we will get holes with
 // unreachable objects in them (which may have pointers to unloaded Klasses and thus
 // cannot be iterated over using oop->size(). The only way to safely iterate over those is using
diff a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.hpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.hpp
@@ -53,10 +53,11 @@
 class PreservedMarksSet;
 class VM_ShenandoahFullGC;
 class ShenandoahDegenGC;
 
 class ShenandoahFullGC : public ShenandoahGC {
+  template <bool ALT_FWD>
   friend class ShenandoahPrepareForCompactionObjectClosure;
   friend class VM_ShenandoahFullGC;
   friend class ShenandoahDegenGC;
 
 private:
@@ -81,10 +82,14 @@
   void phase2_calculate_target_addresses(ShenandoahHeapRegionSet** worker_slices);
   void phase3_update_references();
   void phase4_compact_objects(ShenandoahHeapRegionSet** worker_slices);
 
   void distribute_slices(ShenandoahHeapRegionSet** worker_slices);
+  template <bool ALT_FWD>
+  void calculate_target_humongous_objects_impl();
   void calculate_target_humongous_objects();
+  template <bool ALT_FWD>
+  void compact_humongous_objects_impl();
   void compact_humongous_objects();
 };
 
 #endif // SHARE_GC_SHENANDOAH_SHENANDOAHFULLGC_HPP
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"
@@ -402,10 +403,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
@@ -282,11 +282,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;
@@ -317,15 +317,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;
@@ -497,11 +511,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
@@ -48,11 +48,21 @@
   // A max segment size of 64K was chosen because microbenchmarking
   // suggested that it offered a good trade-off between allocation
   // time and time-to-safepoint
   const size_t segment_max = XUtils::bytes_to_words(64 * K);
   const BasicType element_type = ArrayKlass::cast(_klass)->element_type();
-  const size_t header = arrayOopDesc::header_size(element_type);
+
+  // Clear leading 32 bits, if necessary.
+  int base_offset = arrayOopDesc::base_offset_in_bytes(element_type);
+  if (!is_aligned(base_offset, HeapWordSize)) {
+    assert(is_aligned(base_offset, BytesPerInt), "array base must be 32 bit aligned");
+    *reinterpret_cast<jint*>(reinterpret_cast<char*>(mem) + base_offset) = 0;
+    base_offset += BytesPerInt;
+  }
+  assert(is_aligned(base_offset, HeapWordSize), "remaining array base must be 64 bit aligned");
+
+  const size_t header = heap_word_size(base_offset);
   const size_t payload_size = _word_size - header;
 
   if (payload_size <= segment_max) {
     // To small to use segmented clearing
     return ObjArrayAllocator::initialize(mem);
@@ -61,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
@@ -481,11 +481,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
@@ -48,11 +48,21 @@
   // A max segment size of 64K was chosen because microbenchmarking
   // suggested that it offered a good trade-off between allocation
   // time and time-to-safepoint
   const size_t segment_max = ZUtils::bytes_to_words(64 * K);
   const BasicType element_type = ArrayKlass::cast(_klass)->element_type();
-  const size_t header = arrayOopDesc::header_size(element_type);
+
+  // Clear leading 32 bits, if necessary.
+  int base_offset = arrayOopDesc::base_offset_in_bytes(element_type);
+  if (!is_aligned(base_offset, HeapWordSize)) {
+    assert(is_aligned(base_offset, BytesPerInt), "array base must be 32 bit aligned");
+    *reinterpret_cast<jint*>(reinterpret_cast<char*>(mem) + base_offset) = 0;
+    base_offset += BytesPerInt;
+  }
+  assert(is_aligned(base_offset, HeapWordSize), "remaining array base must be 64 bit aligned");
+
+  const size_t header = heap_word_size(base_offset);
   const size_t payload_size = _word_size - header;
 
   if (payload_size <= segment_max) {
     // To small to use segmented clearing
     return ObjArrayAllocator::initialize(mem);
@@ -64,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) {
+    arrayOopDesc::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
@@ -133,9 +147,13 @@
   }
 
   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
@@ -619,24 +619,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(_forwarding, 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/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
@@ -1995,14 +1995,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();
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
@@ -2384,11 +2384,11 @@
 C2V_END
 
 
 C2V_VMENTRY_0(jint, arrayBaseOffset, (JNIEnv* env, jobject, jchar type_char))
   BasicType type = JVMCIENV->typeCharToBasicType(type_char, JVMCI_CHECK_0);
-  return arrayOopDesc::header_size(type) * HeapWordSize;
+  return arrayOopDesc::base_offset_in_bytes(type);
 C2V_END
 
 C2V_VMENTRY_0(jint, arrayIndexScale, (JNIEnv* env, jobject, jchar type_char))
   BasicType type = JVMCIENV->typeCharToBasicType(type_char, JVMCI_CHECK_0);
   return type2aelembytes(type);
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
@@ -273,11 +273,10 @@
   do_bool_flag(ForceUnreachable)                                           \
   do_intx_flag(HugeMethodLimit)                                            \
   do_bool_flag(Inline)                                                     \
   do_intx_flag(JVMCICounterSize)                                           \
   do_bool_flag(JVMCIPrintProperties)                                       \
-  do_bool_flag(JVMCIUseFastLocking)                                        \
   do_int_flag(ObjectAlignmentInBytes)                                      \
   do_bool_flag(PrintInlining)                                              \
   do_bool_flag(ReduceInitialCardMarks)                                     \
   do_bool_flag(RestrictContended)                                          \
   do_intx_flag(StackReservedPages)                                         \
diff a/src/hotspot/share/jvmci/jvmci_globals.cpp b/src/hotspot/share/jvmci/jvmci_globals.cpp
--- a/src/hotspot/share/jvmci/jvmci_globals.cpp
+++ b/src/hotspot/share/jvmci/jvmci_globals.cpp
@@ -115,11 +115,10 @@
 
   CHECK_NOT_SET(JVMCIEventLogLevel,                  EnableJVMCI)
   CHECK_NOT_SET(JVMCITraceLevel,                     EnableJVMCI)
   CHECK_NOT_SET(JVMCICounterSize,                    EnableJVMCI)
   CHECK_NOT_SET(JVMCICountersExcludeCompiler,        EnableJVMCI)
-  CHECK_NOT_SET(JVMCIUseFastLocking,                 EnableJVMCI)
   CHECK_NOT_SET(JVMCINMethodSizeLimit,               EnableJVMCI)
   CHECK_NOT_SET(JVMCIPrintProperties,                EnableJVMCI)
   CHECK_NOT_SET(JVMCIThreadsPerNativeLibraryRuntime, EnableJVMCI)
   CHECK_NOT_SET(JVMCICompilerIdleDelay,              EnableJVMCI)
   CHECK_NOT_SET(UseJVMCINativeLibrary,               EnableJVMCI)
diff a/src/hotspot/share/jvmci/jvmci_globals.hpp b/src/hotspot/share/jvmci/jvmci_globals.hpp
--- a/src/hotspot/share/jvmci/jvmci_globals.hpp
+++ b/src/hotspot/share/jvmci/jvmci_globals.hpp
@@ -125,13 +125,10 @@
           range(0, 1000000)                                                 \
                                                                             \
   product(bool, JVMCICountersExcludeCompiler, true, EXPERIMENTAL,           \
           "Exclude JVMCI compiler threads from benchmark counters")         \
                                                                             \
-  develop(bool, JVMCIUseFastLocking, true,                                  \
-          "Use fast inlined locking code")                                  \
-                                                                            \
   product(intx, JVMCINMethodSizeLimit, (80*K)*wordSize, EXPERIMENTAL,       \
           "Maximum size of a compiled method.")                             \
           range(0, max_jint)                                                \
                                                                             \
   product(ccstr, JVMCILibPath, nullptr, EXPERIMENTAL,                       \
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
@@ -214,13 +214,16 @@
   nonstatic_field(JavaThread,                  _should_post_on_exceptions_flag,               int)                                   \
   nonstatic_field(JavaThread,                  _jni_environment,                              JNIEnv)                                \
   nonstatic_field(JavaThread,                  _poll_data,                                    SafepointMechanism::ThreadData)        \
   nonstatic_field(JavaThread,                  _stack_overflow_state._reserved_stack_activation, address)                            \
   nonstatic_field(JavaThread,                  _held_monitor_count,                           int64_t)                               \
+  nonstatic_field(JavaThread,                  _lock_stack,                                   LockStack)                             \
   JVMTI_ONLY(nonstatic_field(JavaThread,       _is_in_VTMS_transition,                        bool))                                 \
   JVMTI_ONLY(nonstatic_field(JavaThread,       _is_in_tmp_VTMS_transition,                    bool))                                 \
                                                                                                                                      \
+  nonstatic_field(LockStack,                   _top,                                          uint32_t)                              \
+                                                                                                                                     \
   JVMTI_ONLY(static_field(JvmtiVTMSTransitionDisabler, _VTMS_notify_jvmti_events,             bool))                                 \
                                                                                                                                      \
   static_field(java_lang_Class,                _klass_offset,                                 int)                                   \
   static_field(java_lang_Class,                _array_klass_offset,                           int)                                   \
                                                                                                                                      \
@@ -491,10 +494,11 @@
   declare_constant(BitData::exception_seen_flag)                          \
   declare_constant(BitData::null_seen_flag)                               \
   declare_constant(BranchData::not_taken_off_set)                         \
                                                                           \
   declare_constant_with_value("CardTable::dirty_card", CardTable::dirty_card_val()) \
+  declare_constant_with_value("LockStack::_end_offset", LockStack::end_offset()) \
                                                                           \
   declare_constant(CodeInstaller::VERIFIED_ENTRY)                         \
   declare_constant(CodeInstaller::UNVERIFIED_ENTRY)                       \
   declare_constant(CodeInstaller::OSR_ENTRY)                              \
   declare_constant(CodeInstaller::EXCEPTION_HANDLER_ENTRY)                \
@@ -675,10 +679,14 @@
                                                                           \
   declare_constant(InstanceKlass::linked)                                 \
   declare_constant(InstanceKlass::being_initialized)                      \
   declare_constant(InstanceKlass::fully_initialized)                      \
                                                                           \
+  declare_constant(LockingMode::LM_MONITOR)                               \
+  declare_constant(LockingMode::LM_LEGACY)                                \
+  declare_constant(LockingMode::LM_LIGHTWEIGHT)                           \
+                                                                          \
   /*********************************/                                     \
   /* InstanceKlass _misc_flags */                                         \
   /*********************************/                                     \
                                                                           \
   declare_constant(InstanceKlassFlags::_misc_has_nonstatic_concrete_methods)   \
@@ -722,10 +730,12 @@
                                                                           \
   AARCH64_ONLY(declare_constant(NMethodPatchingType::stw_instruction_and_data_patch))  \
   AARCH64_ONLY(declare_constant(NMethodPatchingType::conc_instruction_and_data_patch)) \
   AARCH64_ONLY(declare_constant(NMethodPatchingType::conc_data_patch))                 \
                                                                           \
+  declare_constant(ObjectMonitor::ANONYMOUS_OWNER)                        \
+                                                                          \
   declare_constant(ReceiverTypeData::nonprofiled_count_off_set)           \
   declare_constant(ReceiverTypeData::receiver_type_row_cell_count)        \
   declare_constant(ReceiverTypeData::receiver0_offset)                    \
   declare_constant(ReceiverTypeData::count0_offset)                       \
                                                                           \
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
@@ -317,12 +317,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
@@ -25,10 +25,11 @@
 #ifndef SHARE_OOPS_ARRAYOOP_HPP
 #define SHARE_OOPS_ARRAYOOP_HPP
 
 #include "oops/oop.hpp"
 #include "utilities/align.hpp"
+#include "utilities/globalDefinitions.hpp"
 
 // arrayOopDesc is the abstract baseclass for all arrays.  It doesn't
 // declare pure virtual to enforce this because that would allocate a vtbl
 // in each instance, which we don't want.
 
@@ -43,51 +44,61 @@
   friend class VMStructs;
   friend class arrayOopDescTest;
 
   // Interpreter/Compiler offsets
 
-  // Header size computation.
-  // The header is considered the oop part of this type plus the length.
-  // Returns the aligned header_size_in_bytes.  This is not equivalent to
-  // sizeof(arrayOopDesc) which should not appear in the code.
-  static int header_size_in_bytes() {
-    size_t hs = align_up(length_offset_in_bytes() + sizeof(int),
-                              HeapWordSize);
-#ifdef ASSERT
-    // make sure it isn't called before UseCompressedOops is initialized.
-    static size_t arrayoopdesc_hs = 0;
-    if (arrayoopdesc_hs == 0) arrayoopdesc_hs = hs;
-    assert(arrayoopdesc_hs == hs, "header size can't change");
-#endif // ASSERT
-    return (int)hs;
-  }
-
+private:
   // Returns the address of the length "field".  See length_offset_in_bytes().
   static int* length_addr_impl(void* obj_ptr) {
     char* ptr = static_cast<char*>(obj_ptr);
     return reinterpret_cast<int*>(ptr + length_offset_in_bytes());
   }
 
-  // Check whether an element of a typeArrayOop with the given type must be
-  // aligned 0 mod 8.  The typeArrayOop itself must be aligned at least this
+  // Check whether an element of an arrayOop with the given type must be
+  // aligned 0 mod 8.  The arrayOop itself must be aligned at least this
   // strongly.
   static bool element_type_should_be_aligned(BasicType type) {
+#ifdef _LP64
+    if (type == T_OBJECT || type == T_ARRAY) {
+      return !UseCompressedOops;
+    }
+#endif
     return type == T_DOUBLE || type == T_LONG;
   }
 
  public:
+  // Header size computation.
+  // The header is considered the oop part of this type plus the length.
+  // This is not equivalent to sizeof(arrayOopDesc) which should not appear in the code.
+  static int header_size_in_bytes() {
+    size_t hs = length_offset_in_bytes() + sizeof(int);
+#ifdef ASSERT
+    // make sure it isn't called before UseCompressedOops is initialized.
+    static size_t arrayoopdesc_hs = 0;
+    if (arrayoopdesc_hs == 0) arrayoopdesc_hs = hs;
+    assert(arrayoopdesc_hs == hs, "header size can't change");
+#endif // ASSERT
+    return (int)hs;
+  }
+
   // 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) {
-    return header_size(type) * HeapWordSize;
+    size_t hs = header_size_in_bytes();
+    return (int)(element_type_should_be_aligned(type) ? align_up(hs, BytesPerLong) : hs);
   }
 
   // Returns the address of the first element. The elements in the array will not
   // relocate from this address until a subsequent thread transition.
   void* base(BasicType type) const {
@@ -120,39 +131,32 @@
 
   static void set_length(HeapWord* mem, int length) {
     *length_addr_impl(mem) = length;
   }
 
-  // Should only be called with constants as argument
-  // (will not constant fold otherwise)
-  // Returns the header size in words aligned to the requirements of the
-  // array object type.
-  static int header_size(BasicType type) {
-    size_t typesize_in_bytes = header_size_in_bytes();
-    return (int)(element_type_should_be_aligned(type)
-      ? align_object_offset(typesize_in_bytes/HeapWordSize)
-      : typesize_in_bytes/HeapWordSize);
-  }
-
-  // Return the maximum length of an array of BasicType.  The length can passed
+  // Return the maximum length of an array of BasicType.  The length can be passed
   // to typeArrayOop::object_size(scale, length, header_size) without causing an
   // overflow. We also need to make sure that this will not overflow a size_t on
   // 32 bit platforms when we convert it to a byte size.
   static int32_t max_array_length(BasicType type) {
     assert(type >= 0 && type < T_CONFLICT, "wrong type");
     assert(type2aelembytes(type) != 0, "wrong type");
 
+    size_t hdr_size_in_bytes = base_offset_in_bytes(type);
+    // This is rounded-up and may overlap with the first array elements.
+    size_t hdr_size_in_words = align_up(hdr_size_in_bytes, HeapWordSize) / HeapWordSize;
+
     const size_t max_element_words_per_size_t =
-      align_down((SIZE_MAX/HeapWordSize - header_size(type)), MinObjAlignment);
+      align_down((SIZE_MAX/HeapWordSize - hdr_size_in_words), MinObjAlignment);
     const size_t max_elements_per_size_t =
       HeapWordSize * max_element_words_per_size_t / type2aelembytes(type);
     if ((size_t)max_jint < max_elements_per_size_t) {
       // It should be ok to return max_jint here, but parts of the code
       // (CollectedHeap, Klass::oop_oop_iterate(), and more) uses an int for
       // passing around the size (in words) of an object. So, we need to avoid
       // overflowing an int when we add the header. See CRs 4718400 and 7110613.
-      return align_down(max_jint - header_size(type), MinObjAlignment);
+      return align_down(max_jint - hdr_size_in_words, MinObjAlignment);
     }
     return (int32_t)max_elements_per_size_t;
   }
 
 };
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,15 +189,26 @@
 
 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;
+}
+
 // "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()));
@@ -738,10 +749,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
@@ -674,10 +676,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.hpp b/src/hotspot/share/oops/markWord.hpp
--- a/src/hotspot/share/oops/markWord.hpp
+++ b/src/hotspot/share/oops/markWord.hpp
@@ -23,10 +23,11 @@
  */
 
 #ifndef SHARE_OOPS_MARKWORD_HPP
 #define SHARE_OOPS_MARKWORD_HPP
 
+#include "gc/shared/gc_globals.hpp"
 #include "metaprogramming/primitiveConversions.hpp"
 #include "oops/oopsHierarchy.hpp"
 #include "runtime/globals.hpp"
 
 #include <type_traits>
@@ -41,10 +42,14 @@
 //
 //  64 bits:
 //  --------
 //  unused:25 hash:31 -->| unused_gap:1  age:4  unused_gap: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 +106,47 @@
   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_forwarded_bits            = 1;
+  static const int max_hash_bits                  = BitsPerWord - age_bits - lock_bits - self_forwarded_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              = max_hash_bits > 25 ? 25 : max_hash_bits;
+  // Used only without compact headers.
+  static const int unused_gap_bits                = LP64_ONLY(1) NOT_LP64(0);
+#ifdef _LP64
+  // Used only with compact headers.
+  static const int klass_bits                     = 32;
+#endif
 
   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_forwarded_shift           = lock_shift + lock_bits;
+  static const int age_shift                      = self_forwarded_shift + self_forwarded_bits;
+  static const int hash_shift                     = age_shift + age_bits + unused_gap_bits;
+  static const int hash_shift_compact             = age_shift + age_bits;
+#ifdef _LP64
+  // Used only with compact headers.
+  static const int klass_shift                    = hash_shift_compact + hash_bits_compact;
+#endif
 
   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_forwarded_mask      = right_n_bits(self_forwarded_bits);
+  static const uintptr_t self_forwarded_mask_in_place = self_forwarded_mask << self_forwarded_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.
+  static const uintptr_t klass_mask               = right_n_bits(klass_bits);
+  static const 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;
@@ -203,13 +230,19 @@
                                           : (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);
@@ -238,17 +271,30 @@
   }
   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;
   }
 
+#ifdef _LP64
+  inline markWord actual_mark() const;
+  inline Klass* klass() const;
+  inline Klass* klass_or_null() const;
+  inline narrowKlass narrow_klass() const;
+  inline markWord set_narrow_klass(narrowKlass nklass) const;
+  inline markWord set_klass(Klass* klass) const;
+#endif
+
   // Prototype mark for initialization
   static markWord prototype() {
     return markWord( no_hash_in_place | no_lock_in_place );
   }
 
@@ -258,10 +304,23 @@
   // 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(); }
+
+#ifdef _LP64
+  inline bool self_forwarded() const {
+    bool self_fwd = mask_bits(value(), self_forwarded_mask_in_place) != 0;
+    assert(!self_fwd || UseAltGCForwarding, "Only set self-fwd bit when using alt GC forwarding");
+    return self_fwd;
+  }
+
+  inline markWord set_self_forwarded() const {
+    assert(UseAltGCForwarding, "Only call this with alt GC forwarding");
+    return markWord(value() | self_forwarded_mask_in_place | marked_value);
+  }
+#endif
 };
 
 // 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,70 @@
+/*
+ * 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_OOPS_MARKWORD_INLINE_HPP
+#define SHARE_OOPS_MARKWORD_INLINE_HPP
+
+#include "oops/markWord.hpp"
+#include "oops/compressedOops.inline.hpp"
+
+#ifdef _LP64
+markWord markWord::actual_mark() const {
+  assert(UseCompactObjectHeaders, "only safe when using compact headers");
+  if (has_displaced_mark_helper()) {
+    return displaced_mark_helper();
+  } else {
+    return *this;
+  }
+}
+
+Klass* markWord::klass() const {
+  assert(UseCompactObjectHeaders, "only used with compact object headers");
+  assert(!CompressedKlassPointers::is_null(narrow_klass()), "narrow klass must not be null: " INTPTR_FORMAT, value());
+  return CompressedKlassPointers::decode_not_null(narrow_klass());
+}
+
+Klass* markWord::klass_or_null() const {
+  assert(UseCompactObjectHeaders, "only used with compact object headers");
+  return CompressedKlassPointers::decode(narrow_klass());
+}
+
+narrowKlass markWord::narrow_klass() const {
+  assert(UseCompactObjectHeaders, "only used with compact object headers");
+  return narrowKlass(value() >> klass_shift);
+}
+
+markWord markWord::set_narrow_klass(narrowKlass nklass) const {
+  assert(UseCompactObjectHeaders, "only used with compact object headers");
+  return markWord((value() & ~klass_mask_in_place) | ((uintptr_t) nklass << klass_shift));
+}
+
+markWord markWord::set_klass(Klass* klass) const {
+  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);
+}
+#endif // _LP64
+
+#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
@@ -154,11 +154,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/objArrayOop.hpp b/src/hotspot/share/oops/objArrayOop.hpp
--- a/src/hotspot/share/oops/objArrayOop.hpp
+++ b/src/hotspot/share/oops/objArrayOop.hpp
@@ -49,36 +49,10 @@
   template <class T>
   static ptrdiff_t obj_at_offset(int index) {
     return base_offset_in_bytes() + sizeof(T) * index;
   }
 
-private:
-  // Give size of objArrayOop in HeapWords minus the header
-  static int array_size(int length) {
-    const uint OopsPerHeapWord = HeapWordSize/heapOopSize;
-    assert(OopsPerHeapWord >= 1 && (HeapWordSize % heapOopSize == 0),
-           "Else the following (new) computation would be in error");
-    uint res = ((uint)length + OopsPerHeapWord - 1)/OopsPerHeapWord;
-#ifdef ASSERT
-    // The old code is left in for sanity-checking; it'll
-    // go away pretty soon. XXX
-    // Without UseCompressedOops, this is simply:
-    // oop->length() * HeapWordsPerOop;
-    // With narrowOops, HeapWordsPerOop is 1/2 or equal 0 as an integer.
-    // The oop elements are aligned up to wordSize
-    const uint HeapWordsPerOop = heapOopSize/HeapWordSize;
-    uint old_res;
-    if (HeapWordsPerOop > 0) {
-      old_res = length * HeapWordsPerOop;
-    } else {
-      old_res = align_up((uint)length, OopsPerHeapWord)/OopsPerHeapWord;
-    }
-    assert(res == old_res, "Inconsistency between old and new.");
-#endif  // ASSERT
-    return res;
-  }
-
  public:
   // Returns the offset of the first element.
   static int base_offset_in_bytes() {
     return arrayOopDesc::base_offset_in_bytes(T_OBJECT);
   }
@@ -92,20 +66,19 @@
   void obj_at_put(int index, oop value);
 
   oop replace_if_null(int index, oop exchange_value);
 
   // Sizing
-  static int header_size()    { return arrayOopDesc::header_size(T_OBJECT); }
   size_t object_size()        { return object_size(length()); }
 
   static size_t object_size(int length) {
     // This returns the object size in HeapWords.
-    uint asz = array_size(length);
-    uint osz = align_object_size(header_size() + asz);
-    assert(osz >= asz,   "no overflow");
-    assert((int)osz > 0, "no overflow");
-    return (size_t)osz;
+    size_t asz = (size_t)length * heapOopSize;
+    size_t size_words = heap_word_size(base_offset_in_bytes() + asz);
+    size_t osz = align_object_size(size_words);
+    assert(osz < max_jint, "no overflow");
+    return osz;
   }
 
   Klass* element_klass();
 
 public:
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
@@ -154,11 +154,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(DumpSharedSpaces, "Used by CDS only. Do not abuse!");
@@ -166,11 +167,13 @@
   _metadata._compressed_klass = nk;
 }
 #endif
 
 void* oopDesc::load_klass_raw(oop obj) {
-  if (UseCompressedClassPointers) {
+  if (UseCompactObjectHeaders) {
+    return obj->klass();
+  } else if (UseCompressedClassPointers) {
     narrowKlass narrow_klass = obj->_metadata._compressed_klass;
     if (narrow_klass == 0) return nullptr;
     return (void*)CompressedKlassPointers::decode_raw(narrow_klass);
   } else {
     return obj->_metadata._klass;
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
@@ -77,10 +77,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;
@@ -95,11 +100,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
@@ -107,10 +118,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;
@@ -259,18 +284,21 @@
 
   // Forward pointer operations for scavenge
   inline bool is_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;
 
   // Age of object during scavenge
   inline uint age() const;
   inline void incr_age();
 
@@ -310,16 +338,43 @@
 
   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) {
+      STATIC_ASSERT(markWord::klass_shift % 8 == 0);
+      return mark_offset_in_bytes() + markWord::klass_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_klass_raw(oop obj);
   static void* load_oop_raw(oop obj, int offset);
 
   DEBUG_ONLY(bool size_might_change();)
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,14 +32,15 @@
 #include "oops/access.inline.hpp"
 #include "oops/arrayKlass.hpp"
 #include "oops/arrayOop.hpp"
 #include "oops/compressedOops.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 "runtime/safepoint.hpp"
 #include "utilities/align.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/macros.hpp"
 #include "utilities/globalDefinitions.hpp"
 
@@ -64,84 +65,131 @@
 
 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::resolve_mark() const {
+  assert(LockingMode != LM_LEGACY, "Not safe with legacy stack-locking");
+  markWord m = mark();
+  if (m.has_displaced_mark_helper()) {
+    m = m.displaced_mark_helper();
+  }
+  return m;
+}
+
+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(klass()->prototype_header());
+  } else {
+    set_mark(markWord::prototype());
+  }
 }
 
 Klass* oopDesc::klass() const {
-  if (UseCompressedClassPointers) {
+#ifdef _LP64
+  if (UseCompactObjectHeaders) {
+    markWord m = resolve_mark();
+    return m.klass();
+  } else if (UseCompressedClassPointers) {
     return CompressedKlassPointers::decode_not_null(_metadata._compressed_klass);
-  } else {
+  } else
+#endif
+  {
     return _metadata._klass;
   }
 }
 
 Klass* oopDesc::klass_or_null() const {
-  if (UseCompressedClassPointers) {
+#ifdef _LP64
+  if (UseCompactObjectHeaders) {
+    markWord m = resolve_mark();
+    return m.klass_or_null();
+  } else if (UseCompressedClassPointers) {
     return CompressedKlassPointers::decode(_metadata._compressed_klass);
-  } else {
+  } else
+#endif
+  {
     return _metadata._klass;
   }
 }
 
 Klass* oopDesc::klass_or_null_acquire() const {
-  if (UseCompressedClassPointers) {
-    narrowKlass nklass = Atomic::load_acquire(&_metadata._compressed_klass);
-    return CompressedKlassPointers::decode(nklass);
-  } else {
+#ifdef _LP64
+  if (UseCompactObjectHeaders) {
+    markWord m = mark_acquire();
+    if (m.has_displaced_mark_helper()) {
+      m = m.displaced_mark_helper();
+    }
+    return m.klass_or_null();
+  } else if (UseCompressedClassPointers) {
+     narrowKlass nklass = Atomic::load_acquire(&_metadata._compressed_klass);
+     return CompressedKlassPointers::decode(nklass);
+  } else
+#endif
+  {
     return Atomic::load_acquire(&_metadata._klass);
   }
 }
 
 Klass* oopDesc::klass_raw() const {
-  if (UseCompressedClassPointers) {
+  if (UseCompactObjectHeaders) {
+    return klass();
+  } else if (UseCompressedClassPointers) {
     return CompressedKlassPointers::decode_raw(_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;
   }
 }
 
@@ -200,10 +248,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.actual_mark().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();             }
@@ -270,32 +365,91 @@
   return mark().is_marked();
 }
 
 // Used by scavengers
 void oopDesc::forward_to(oop p) {
+  assert(p != cast_to_oop(this) || !UseAltGCForwarding, "Must not be called with self-forwarding");
   markWord m = markWord::encode_pointer_as_mark(p);
-  assert(m.decode_pointer() == p, "encoding must be reversible");
+  assert(forwardee(m) == p, "encoding must be reversible");
   set_mark(m);
 }
 
+void oopDesc::forward_to_self() {
+#ifdef _LP64
+  if (UseAltGCForwarding) {
+    markWord m = mark();
+    // If mark is displaced, we need to preserve the real header during GC.
+    // It will be restored to the displaced header after GC.
+    assert(SafepointSynchronize::is_at_safepoint(), "we can only safely fetch the displaced header at safepoint");
+    if (m.has_displaced_mark_helper()) {
+      m = m.displaced_mark_helper();
+    }
+    m = m.set_self_forwarded();
+    assert(forwardee(m) == cast_to_oop(this), "encoding must be reversible");
+    set_mark(m);
+  } else
+#endif
+  {
+    forward_to(oop(this));
+  }
+}
+
 oop oopDesc::forward_to_atomic(oop p, markWord compare, atomic_memory_order order) {
+  assert(p != cast_to_oop(this) || !UseAltGCForwarding, "Must not be called with self-forwarding");
   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);
   if (old_mark == compare) {
     return nullptr;
   } else {
-    return cast_to_oop(old_mark.decode_pointer());
+    return forwardee(old_mark);
+  }
+}
+
+oop oopDesc::forward_to_self_atomic(markWord compare, atomic_memory_order order) {
+#ifdef _LP64
+  if (UseAltGCForwarding) {
+    markWord m = compare;
+    // If mark is displaced, we need to preserve the real header during GC.
+    // It will be restored to the displaced header after GC.
+    assert(SafepointSynchronize::is_at_safepoint(), "we can only safely fetch the displaced header at safepoint");
+    if (m.has_displaced_mark_helper()) {
+      m = m.displaced_mark_helper();
+    }
+    m = m.set_self_forwarded();
+    assert(forwardee(m) == cast_to_oop(this), "encoding must be reversible");
+    markWord old_mark = cas_set_mark(m, compare, order);
+    if (old_mark == compare) {
+      return nullptr;
+    } else {
+      assert(old_mark.is_marked(), "must be marked here");
+      return forwardee(old_mark);
+    }
+  } else
+#endif
+  {
+    return forward_to_atomic(cast_to_oop(this), compare, order);
+  }
+}
+
+oop oopDesc::forwardee(markWord header) const {
+  assert(header.is_marked(), "only decode when actually forwarded");
+#ifdef _LP64
+  if (header.self_forwarded()) {
+    return cast_to_oop(this);
+  } else
+#endif
+  {
+    return cast_to_oop(header.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());
 }
 
 // The following method needs to be MT safe.
 uint oopDesc::age() const {
   assert(!mark().is_marked(), "Attempt to read age from forwarded mark");
@@ -346,11 +500,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
@@ -226,11 +226,12 @@
 Klass* TypeArrayKlass::array_klass_or_null() {
   return array_klass_or_null(dimension() +  1);
 }
 
 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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 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.
@@ -95,10 +95,30 @@
 
   int max_size() const;
   void emit(C2_MacroAssembler& masm);
 };
 
+class C2FastUnlockLightweightStub : public C2CodeStub {
+private:
+  Register _obj;
+  Register _mark;
+  Register _t;
+  Register _thread;
+  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& 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(); }
+};
+
 #ifdef _LP64
 class C2HandleAnonOMOwnerStub : public C2CodeStub {
 private:
   Register _monitor;
   Register _tmp;
@@ -108,11 +128,21 @@
   Register monitor() { return _monitor; }
   Register tmp() { return _tmp; }
   int max_size() const;
   void emit(C2_MacroAssembler& masm);
 };
-#endif
+
+class C2LoadNKlassStub : public C2CodeStub {
+private:
+  Register _dst;
+public:
+  C2LoadNKlassStub(Register dst) : C2CodeStub(), _dst(dst) {}
+  Register dst() { return _dst; }
+  int max_size() const;
+  void emit(C2_MacroAssembler& masm);
+};
+#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
@@ -1573,12 +1573,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
@@ -1688,10 +1688,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/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
@@ -4538,12 +4538,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/macro.cpp b/src/hotspot/share/opto/macro.cpp
--- a/src/hotspot/share/opto/macro.cpp
+++ b/src/hotspot/share/opto/macro.cpp
@@ -1659,11 +1659,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
@@ -1883,10 +1883,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());
@@ -2055,10 +2062,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.
@@ -2145,11 +2159,11 @@
       return Type::get_zero_type(_type->basic_type());
     }
   }
 
   Node* alloc = is_new_object_mark_load(phase);
-  if (alloc != nullptr) {
+  if (!UseCompactObjectHeaders && alloc != nullptr) {
     return TypeX::make(markWord::prototype().value());
   }
 
   return _type;
 }
diff a/src/hotspot/share/opto/runtime.cpp b/src/hotspot/share/opto/runtime.cpp
--- a/src/hotspot/share/opto/runtime.cpp
+++ b/src/hotspot/share/opto/runtime.cpp
@@ -318,18 +318,21 @@
   if ((len > 0) && (result != nullptr) &&
       is_deoptimized_caller_frame(current)) {
     // Zero array here if the caller is deoptimized.
     const size_t size = TypeArrayKlass::cast(array_type)->oop_size(result);
     BasicType elem_type = TypeArrayKlass::cast(array_type)->element_type();
-    const size_t hs = arrayOopDesc::header_size(elem_type);
-    // Align to next 8 bytes to avoid trashing arrays's length.
-    const size_t aligned_hs = align_object_offset(hs);
+    size_t hs_bytes = arrayOopDesc::base_offset_in_bytes(elem_type);
+    assert(is_aligned(hs_bytes, BytesPerInt), "must be 4 byte aligned");
     HeapWord* obj = cast_from_oop<HeapWord*>(result);
-    if (aligned_hs > hs) {
-      Copy::zero_to_words(obj+hs, aligned_hs-hs);
+    if (!is_aligned(hs_bytes, BytesPerLong)) {
+      *reinterpret_cast<jint*>(reinterpret_cast<char*>(obj) + hs_bytes) = 0;
+      hs_bytes += BytesPerInt;
     }
+
     // Optimized zeroing.
+    assert(is_aligned(hs_bytes, BytesPerLong), "must be 8-byte aligned");
+    const size_t aligned_hs = hs_bytes / BytesPerLong;
     Copy::fill_to_aligned_words(obj+aligned_hs, size-aligned_hs);
   }
 
 JRT_END
 
diff a/src/hotspot/share/opto/type.cpp b/src/hotspot/share/opto/type.cpp
--- a/src/hotspot/share/opto/type.cpp
+++ b/src/hotspot/share/opto/type.cpp
@@ -5170,16 +5170,16 @@
   default:
     break;
   }
 
   if( _offset != 0 ) {
-    int header_size = objArrayOopDesc::header_size() * wordSize;
+    BasicType basic_elem_type = elem()->basic_type();
+    int header_size = arrayOopDesc::base_offset_in_bytes(basic_elem_type);
     if( _offset == OffsetTop )       st->print("+undefined");
     else if( _offset == OffsetBot )  st->print("+any");
     else if( _offset < header_size ) st->print("+%d", _offset);
     else {
-      BasicType basic_elem_type = elem()->basic_type();
       if (basic_elem_type == T_ILLEGAL) {
         st->print("+any");
       } else {
         int array_base = arrayOopDesc::base_offset_in_bytes(basic_elem_type);
         int elem_size = type2aelembytes(basic_elem_type);
diff a/src/hotspot/share/prims/unsafe.cpp b/src/hotspot/share/prims/unsafe.cpp
--- a/src/hotspot/share/prims/unsafe.cpp
+++ b/src/hotspot/share/prims/unsafe.cpp
@@ -66,11 +66,11 @@
  * Implementation of the jdk.internal.misc.Unsafe class
  */
 
 
 #define MAX_OBJECT_SIZE \
-  ( arrayOopDesc::header_size(T_DOUBLE) * HeapWordSize \
+  ( arrayOopDesc::base_offset_in_bytes(T_DOUBLE) \
     + ((julong)max_jint * sizeof(double)) )
 
 
 #define UNSAFE_ENTRY(result_type, header) \
   JVM_ENTRY(static result_type, header)
diff a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp
--- a/src/hotspot/share/prims/whitebox.cpp
+++ b/src/hotspot/share/prims/whitebox.cpp
@@ -80,10 +80,11 @@
 #include "runtime/handshake.hpp"
 #include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/javaCalls.hpp"
 #include "runtime/javaThread.inline.hpp"
 #include "runtime/jniHandles.inline.hpp"
+#include "runtime/lockStack.hpp"
 #include "runtime/os.hpp"
 #include "runtime/stackFrameStream.inline.hpp"
 #include "runtime/synchronizer.hpp"
 #include "runtime/threadSMR.hpp"
 #include "runtime/vframe.hpp"
@@ -1829,10 +1830,18 @@
 WB_ENTRY(jboolean, WB_IsMonitorInflated(JNIEnv* env, jobject wb, jobject obj))
   oop obj_oop = JNIHandles::resolve(obj);
   return (jboolean) obj_oop->mark().has_monitor();
 WB_END
 
+WB_ENTRY(jint, WB_getLockStackCapacity(JNIEnv* env))
+  return (jint) LockStack::CAPACITY;
+WB_END
+
+WB_ENTRY(jboolean, WB_supportsRecursiveLightweightLocking(JNIEnv* env))
+  return (jboolean) VM_Version::supports_recursive_lightweight_locking();
+WB_END
+
 WB_ENTRY(jboolean, WB_DeflateIdleMonitors(JNIEnv* env, jobject wb))
   log_info(monitorinflation)("WhiteBox initiated DeflateIdleMonitors");
   return ObjectSynchronizer::request_deflate_idle_monitors_from_wb();
 WB_END
 
@@ -2748,10 +2757,12 @@
                                                       (void*)&WB_AddModuleExportsToAllUnnamed },
   {CC"AddModuleExportsToAll", CC"(Ljava/lang/Object;Ljava/lang/String;)V",
                                                       (void*)&WB_AddModuleExportsToAll },
   {CC"deflateIdleMonitors", CC"()Z",                  (void*)&WB_DeflateIdleMonitors },
   {CC"isMonitorInflated0", CC"(Ljava/lang/Object;)Z", (void*)&WB_IsMonitorInflated  },
+  {CC"getLockStackCapacity", CC"()I",                 (void*)&WB_getLockStackCapacity },
+  {CC"supportsRecursiveLightweightLocking", CC"()Z",  (void*)&WB_supportsRecursiveLightweightLocking },
   {CC"forceSafepoint",     CC"()V",                   (void*)&WB_ForceSafepoint     },
   {CC"forceClassLoaderStatsSafepoint", CC"()V",       (void*)&WB_ForceClassLoaderStatsSafepoint },
   {CC"getConstantPool0",   CC"(Ljava/lang/Class;)J",  (void*)&WB_GetConstantPool    },
   {CC"getConstantPoolCacheIndexTag0", CC"()I",  (void*)&WB_GetConstantPoolCacheIndexTag},
   {CC"getConstantPoolCacheLength0", CC"(Ljava/lang/Class;)I",  (void*)&WB_GetConstantPoolCacheLength},
diff a/src/hotspot/share/runtime/abstract_vm_version.hpp b/src/hotspot/share/runtime/abstract_vm_version.hpp
--- a/src/hotspot/share/runtime/abstract_vm_version.hpp
+++ b/src/hotspot/share/runtime/abstract_vm_version.hpp
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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.
@@ -178,10 +178,13 @@
   static bool supports_fast_class_init_checks() { return false; }
 
   // Does platform support stack watermark barriers for concurrent stack processing?
   constexpr static bool supports_stack_watermark_barrier() { return false; }
 
+  // Is recursive lightweight locking implemented for this platform?
+  constexpr static bool supports_recursive_lightweight_locking() { return false; }
+
   // Does platform support float16 instructions?
   static bool supports_float16() { return false; }
 
   // Does this CPU support this intrinsic?
   static bool is_intrinsic_supported(vmIntrinsicID id) { return true; }
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
@@ -1916,11 +1916,11 @@
     jio_fprintf(defaultStream::error_stream(),
                 "LockingMode == 0 (LM_MONITOR) is not fully implemented on this architecture");
     return false;
   }
 #endif
-#if (defined(X86) || defined(PPC64)) && !defined(ZERO)
+#if defined(X86) && !defined(ZERO)
   if (LockingMode == LM_MONITOR && UseRTMForStackLocks) {
     jio_fprintf(defaultStream::error_stream(),
                 "LockingMode == 0 (LM_MONITOR) and -XX:+UseRTMForStackLocks are mutually exclusive");
 
     return false;
@@ -3083,10 +3083,32 @@
 
 #ifndef CAN_SHOW_REGISTERS_ON_ASSERT
   UNSUPPORTED_OPTION(ShowRegistersOnAssert);
 #endif // CAN_SHOW_REGISTERS_ON_ASSERT
 
+#ifdef _LP64
+  if (UseCompactObjectHeaders && UseZGC && !ZGenerational) {
+    if (FLAG_IS_CMDLINE(UseCompactObjectHeaders)) {
+      warning("Single-generational ZGC does not work with compact object headers, disabling UseCompactObjectHeaders");
+    }
+    FLAG_SET_DEFAULT(UseCompactObjectHeaders, false);
+  }
+  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_LEGACY) {
+    FLAG_SET_DEFAULT(LockingMode, LM_LIGHTWEIGHT);
+  }
+  if (UseCompactObjectHeaders && !UseAltGCForwarding) {
+    FLAG_SET_DEFAULT(UseAltGCForwarding, true);
+  }
+  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
@@ -3373,17 +3395,24 @@
   if (_default_shared_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_shared_archive_path = NEW_C_HEAP_ARRAY(char, len, mtArguments);
-    jio_snprintf(_default_shared_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_shared_archive_path = os::strdup(tmp.base());
   }
   return _default_shared_archive_path;
 }
 
 int Arguments::num_archives(const char* archive_path) {
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
@@ -64,21 +64,28 @@
   // we need to ensure that both locations refer to the current thread's stack.
   // There are some subtle concurrency issues, however, and since the benefit is
   // is small (given the support for inflated fast-path locking in the fast_lock, etc)
   // we'll leave that optimization for another time.
 
-  if (displaced_header().is_neutral()) {
-    // The object is locked and the resulting ObjectMonitor* will also be
-    // locked so it can't be async deflated until ownership is dropped.
-    ObjectSynchronizer::inflate_helper(obj);
-    // WARNING: We cannot put a check here, because the inflation
-    // will not update the displaced header. Once BasicLock is inflated,
-    // no one should ever look at its content.
-  } else {
-    // Typically the displaced header will be 0 (recursive stack lock) or
-    // unused_mark.  Naively we'd like to assert that the displaced mark
-    // 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.
+  if (LockingMode == LM_LEGACY) {
+    if (displaced_header().is_neutral()) {
+      // The object is locked and the resulting ObjectMonitor* will also be
+      // locked so it can't be async deflated until ownership is dropped.
+      ObjectSynchronizer::inflate_helper(obj);
+      // WARNING: We cannot put a check here, because the inflation
+      // will not update the displaced header. Once BasicLock is inflated,
+      // no one should ever look at its content.
+    } else {
+      // Typically the displaced header will be 0 (recursive stack lock) or
+      // unused_mark.  Naively we'd like to assert that the displaced mark
+      // 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());
   }
-  dest->set_displaced_header(displaced_header());
+#ifdef ASSERT
+  else {
+    dest->set_displaced_header(markWord(badDispHeaderDeopt));
+  }
+#endif
 }
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
@@ -388,19 +388,27 @@
   assert(thread == Thread::current(), "should be");
   HandleMark hm(thread);
 #ifndef PRODUCT
   bool first = true;
 #endif // !PRODUCT
+  DEBUG_ONLY(GrowableArray<oop> lock_order{0};)
   // Start locking from outermost/oldest frame
   for (int i = (chunk->length() - 1); i >= 0; i--) {
     compiledVFrame* cvf = chunk->at(i);
     assert (cvf->scope() != nullptr,"expect only compiled java frames");
     GrowableArray<MonitorInfo*>* monitors = cvf->monitors();
     if (monitors->is_nonempty()) {
       bool relocked = Deoptimization::relock_objects(thread, monitors, deoptee_thread, deoptee,
                                                      exec_mode, realloc_failures);
       deoptimized_objects = deoptimized_objects || relocked;
+#ifdef ASSERT
+      if (LockingMode == LM_LIGHTWEIGHT && !realloc_failures) {
+        for (MonitorInfo* mi : *monitors) {
+          lock_order.push(mi->owner());
+        }
+      }
+#endif // ASSERT
 #ifndef PRODUCT
       if (PrintDeoptimizationDetails) {
         ResourceMark rm;
         stringStream st;
         for (int j = 0; j < monitors->length(); j++) {
@@ -428,10 +436,15 @@
         tty->print_raw(st.freeze());
       }
 #endif // !PRODUCT
     }
   }
+#ifdef ASSERT
+  if (LockingMode == LM_LIGHTWEIGHT && !realloc_failures) {
+    deoptee_thread->lock_stack().verify_consistent_lock_order(lock_order, exec_mode != Deoptimization::Unpack_none);
+  }
+#endif // ASSERT
 }
 
 // Deoptimize objects, that is reallocate and relock them, just before they escape through JVMTI.
 // The given vframes cover one physical frame.
 bool Deoptimization::deoptimize_objects_internal(JavaThread* thread, GrowableArray<compiledVFrame*>* chunk,
@@ -1652,21 +1665,21 @@
               JvmtiDeferredUpdates::inc_relock_count_after_wait(deoptee_thread);
               continue;
             }
           }
         }
-        if (LockingMode == LM_LIGHTWEIGHT && exec_mode == Unpack_none) {
+        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(obj, nullptr, deoptee_thread);
+          ObjectSynchronizer::enter_for(obj, nullptr, deoptee_thread);
           assert(mon_info->owner()->is_locked(), "object must be locked now");
-          ObjectMonitor* mon = ObjectSynchronizer::inflate(deoptee_thread, obj(), ObjectSynchronizer::inflate_cause_vm_internal);
+          ObjectMonitor* mon = ObjectSynchronizer::inflate_for(deoptee_thread, obj(), ObjectSynchronizer::inflate_cause_vm_internal);
           assert(mon->owner() == deoptee_thread, "must be");
         } else {
           BasicLock* lock = mon_info->lock();
-          ObjectSynchronizer::enter(obj, lock, deoptee_thread);
+          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
@@ -128,10 +128,13 @@
                                                                             \
   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")                 \
+                                                                            \
   product(int, ObjectAlignmentInBytes, 8,                                   \
           "Default object alignment in bytes, 8 is minimum")                \
           range(8, 256)                                                     \
           constraint(ObjectAlignmentInBytesConstraintFunc, AtParse)
 
@@ -145,10 +148,11 @@
                            notproduct,                                      \
                            range,                                           \
                            constraint)
 const bool UseCompressedOops = false;
 const bool UseCompressedClassPointers = false;
+const bool UseCompactObjectHeaders = false;
 const int ObjectAlignmentInBytes = 8;
 
 #endif // _LP64
 
 #define RUNTIME_FLAGS(develop,                                              \
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
@@ -1,8 +1,9 @@
 /*
  * Copyright (c) 2022, Red Hat, Inc. All rights reserved.
  * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
+ * 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.
@@ -23,24 +24,38 @@
  *
  */
 
 #include "precompiled.hpp"
 #include "memory/allocation.hpp"
+#include "oops/markWord.hpp"
+#include "oops/oop.inline.hpp"
+#include "runtime/globals.hpp"
 #include "runtime/lockStack.inline.hpp"
+#include "runtime/objectMonitor.inline.hpp"
 #include "runtime/safepoint.hpp"
 #include "runtime/stackWatermark.hpp"
 #include "runtime/stackWatermarkSet.inline.hpp"
 #include "runtime/thread.hpp"
 #include "utilities/copy.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "utilities/growableArray.hpp"
 #include "utilities/ostream.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());
 const int LockStack::lock_stack_base_offset = in_bytes(JavaThread::lock_stack_base_offset());
 
 LockStack::LockStack(JavaThread* jt) :
   _top(lock_stack_base_offset), _base() {
+  // Make sure the layout of the object is compatible with the emitted code's assumptions.
+  STATIC_ASSERT(sizeof(_bad_oop_sentinel) == oopSize);
+  STATIC_ASSERT(sizeof(_base[0]) == oopSize);
+  STATIC_ASSERT(std::is_standard_layout<LockStack>::value);
+  STATIC_ASSERT(offsetof(LockStack, _bad_oop_sentinel) == offsetof(LockStack, _base) - oopSize);
 #ifdef ASSERT
   for (int i = 0; i < CAPACITY; i++) {
     _base[i] = nullptr;
   }
 #endif
@@ -60,15 +75,25 @@
 
 #ifndef PRODUCT
 void LockStack::verify(const char* msg) const {
   assert(LockingMode == LM_LIGHTWEIGHT, "never use lock-stack when light weight locking is disabled");
   assert((_top <= end_offset()), "lockstack overflow: _top %d end_offset %d", _top, end_offset());
-  assert((_top >= start_offset()), "lockstack underflow: _top %d end_offset %d", _top, start_offset());
+  assert((_top >= start_offset()), "lockstack underflow: _top %d start_offset %d", _top, start_offset());
   if (SafepointSynchronize::is_at_safepoint() || (Thread::current()->is_Java_thread() && is_owning_thread())) {
     int top = to_index(_top);
     for (int i = 0; i < top; i++) {
       assert(_base[i] != nullptr, "no zapped before top");
+      if (VM_Version::supports_recursive_lightweight_locking()) {
+        oop o = _base[i];
+        for (; i < top - 1; i++) {
+          // Consecutive entries may be the same
+          if (_base[i + 1] != o) {
+            break;
+          }
+        }
+      }
+
       for (int j = i + 1; j < top; j++) {
         assert(_base[i] != _base[j], "entries must be unique: %s", msg);
       }
     }
     for (int i = top; i < CAPACITY; i++) {
@@ -76,10 +101,64 @@
     }
   }
 }
 #endif
 
+#ifdef ASSERT
+void LockStack::verify_consistent_lock_order(GrowableArray<oop>& lock_order, bool leaf_frame) const {
+  int top_index = to_index(_top);
+  int lock_index = lock_order.length();
+
+  if (!leaf_frame) {
+    // If the lock_order is not from the leaf frame we must search
+    // for the top_index which fits with the most recent fast_locked
+    // objects in the lock stack.
+    while (lock_index-- > 0) {
+      const oop obj = lock_order.at(lock_index);
+      if (contains(obj)) {
+        for (int index = 0; index < top_index; index++) {
+          if (_base[index] == obj) {
+            // Found top index
+            top_index = index + 1;
+            break;
+          }
+        }
+
+        if (VM_Version::supports_recursive_lightweight_locking()) {
+          // With recursive looks there may be more of the same object
+          while (lock_index-- > 0 && lock_order.at(lock_index) == obj) {
+            top_index++;
+          }
+          assert(top_index <= to_index(_top), "too many obj in lock_order");
+        }
+
+        break;
+      }
+    }
+
+    lock_index = lock_order.length();
+  }
+
+  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");
+    if (top_index > 0 && obj == _base[top_index - 1]) {
+      assert(mark.is_fast_locked() || mark.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()),
+             "must be owned by (or waited on by) thread");
+      assert(!contains(obj), "must not be on lock_stack");
+    }
+  }
+}
+#endif
+
 void LockStack::print_on(outputStream* st) {
   for (int i = to_index(_top); (--i) >= 0;) {
     st->print("LockStack[%d]: ", i);
     oop o = _base[i];
     if (oopDesc::is_oop(o)) {
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
@@ -1,8 +1,9 @@
 /*
  * Copyright (c) 2022, Red Hat, Inc. All rights reserved.
  * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
+ * 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.
@@ -31,15 +32,20 @@
 #include "utilities/sizes.hpp"
 
 class JavaThread;
 class OopClosure;
 class outputStream;
+template<typename>
+class GrowableArray;
 
 class LockStack {
+  friend class LockStackTest;
   friend class VMStructs;
-private:
+  JVMCI_ONLY(friend class JVMCIVMStructs;)
+public:
   static const int CAPACITY = 8;
+private:
 
   // TODO: It would be very useful if JavaThread::lock_stack_offset() and friends were constexpr,
   // but this is currently not the case because we're using offset_of() which is non-constexpr,
   // GCC would warn about non-standard-layout types if we were using offsetof() (which *is* constexpr).
   static const int lock_stack_offset;
@@ -48,10 +54,13 @@
 
   // The offset of the next element, in bytes, relative to the JavaThread structure.
   // We do this instead of a simple index into the array because this allows for
   // efficient addressing in generated code.
   uint32_t _top;
+  // The _bad_oop_sentinel acts as a sentinel value to elide underflow checks in generated code.
+  // The correct layout is statically asserted in the constructor.
+  const uintptr_t _bad_oop_sentinel = badOopVal;
   oop _base[CAPACITY];
 
   // Get the owning thread of this lock-stack.
   inline JavaThread* get_thread() const;
 
@@ -72,28 +81,49 @@
 
   // 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 onto this lock-stack, false otherwise.
-  inline bool can_push() 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);
 
-  // Pops an oop from this lock-stack.
-  inline oop pop();
+  // Get the oldest oop from this lock-stack.
+  // Precondition: This lock-stack must not be empty.
+  inline oop bottom() const;
+
+  // Is the lock-stack empty.
+  inline bool is_empty() const;
+
+  // Check if object is recursive.
+  // Precondition: This lock-stack must contain the oop.
+  inline bool is_recursive(oop o) const;
+
+  // Try recursive enter.
+  // Precondition: This lock-stack must not be full.
+  inline bool try_recursive_enter(oop o);
+
+  // Try recursive exit.
+  // Precondition: This lock-stack must contain the oop.
+  inline bool try_recursive_exit(oop o);
 
   // Removes an oop from an arbitrary location of this lock-stack.
-  inline void remove(oop o);
+  // Precondition: This lock-stack must contain the oop.
+  // Returns the number of oops removed.
+  inline size_t remove(oop o);
 
   // Tests whether the oop is on this lock-stack.
   inline bool contains(oop o) const;
 
   // GC support
   inline void oops_do(OopClosure* cl);
 
   // Printing
   void print_on(outputStream* st);
+
+  // Verify Lock Stack consistent with lock order
+  void verify_consistent_lock_order(GrowableArray<oop>& lock_order, bool leaf_frame) const NOT_DEBUG_RETURN;
 };
 
 #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
@@ -1,8 +1,9 @@
 /*
  * Copyright (c) 2022, Red Hat, Inc. All rights reserved.
  * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
+ * 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.
@@ -24,28 +25,34 @@
  */
 
 #ifndef SHARE_RUNTIME_LOCKSTACK_INLINE_HPP
 #define SHARE_RUNTIME_LOCKSTACK_INLINE_HPP
 
+#include "runtime/lockStack.hpp"
+
 #include "memory/iterator.hpp"
 #include "runtime/javaThread.hpp"
-#include "runtime/lockStack.hpp"
 #include "runtime/safepoint.hpp"
 #include "runtime/stackWatermark.hpp"
 #include "runtime/stackWatermarkSet.inline.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);
+  assert((offset <= end_offset()), "lockstack overflow: offset %d end_offset %d", offset, end_offset());
+  assert((offset >= start_offset()), "lockstack underflow: offset %d start_offset %d", offset, start_offset());
   return (offset - lock_stack_base_offset) / oopSize;
 }
 
 JavaThread* LockStack::get_thread() const {
   char* addr = reinterpret_cast<char*>(const_cast<LockStack*>(this));
   return reinterpret_cast<JavaThread*>(addr - lock_stack_offset);
 }
 
-inline bool LockStack::can_push() const {
-  return to_index(_top) < CAPACITY;
+inline bool LockStack::is_full() const {
+  return to_index(_top) == CAPACITY;
 }
 
 inline bool LockStack::is_owning_thread() const {
   Thread* current = Thread::current();
   if (current->is_Java_thread()) {
@@ -59,49 +66,136 @@
 
 inline void LockStack::push(oop o) {
   verify("pre-push");
   assert(oopDesc::is_oop(o), "must be");
   assert(!contains(o), "entries must be unique");
-  assert(can_push(), "must have room");
+  assert(!is_full(), "must have room");
   assert(_base[to_index(_top)] == nullptr, "expect zapped entry");
   _base[to_index(_top)] = o;
   _top += oopSize;
   verify("post-push");
 }
 
-inline oop LockStack::pop() {
-  verify("pre-pop");
-  assert(to_index(_top) > 0, "underflow, probably unbalanced push/pop");
+inline oop LockStack::bottom() const {
+  assert(to_index(_top) > 0, "must contain an oop");
+  return _base[0];
+}
+
+inline bool LockStack::is_empty() const {
+  return to_index(_top) == 0;
+}
+
+inline bool LockStack::is_recursive(oop o) const {
+  if (!VM_Version::supports_recursive_lightweight_locking()) {
+    return false;
+  }
+  verify("pre-is_recursive");
+
+  // This will succeed iff there is a consecutive run of oops on the
+  // lock-stack with a length of at least 2.
+
+  assert(contains(o), "at least one entry must exist");
+  int end = to_index(_top);
+  // Start iterating from the top because the runtime code is more
+  // interested in the balanced locking case when the top oop on the
+  // lock-stack matches o. This will cause the for loop to break out
+  // in the first loop iteration if it is non-recursive.
+  for (int i = end - 1; i > 0; i--) {
+    if (_base[i - 1] == o && _base[i] == o) {
+      verify("post-is_recursive");
+      return true;
+    }
+    if (_base[i] == o) {
+      // o can only occur in one consecutive run on the lock-stack.
+      // Only one of the two oops checked matched o, so this run
+      // must be of length 1 and thus not be recursive. Stop the search.
+      break;
+    }
+  }
+
+  verify("post-is_recursive");
+  return false;
+}
+
+inline bool LockStack::try_recursive_enter(oop o) {
+  if (!VM_Version::supports_recursive_lightweight_locking()) {
+    return false;
+  }
+  verify("pre-try_recursive_enter");
+
+  // This will succeed iff the top oop on the stack matches o.
+  // When successful o will be pushed to the lock-stack creating
+  // a consecutive run at least 2 oops that matches o on top of
+  // the lock-stack.
+
+  assert(!is_full(), "precond");
+
+  int end = to_index(_top);
+  if (end == 0 || _base[end - 1] != o) {
+    // Topmost oop does not match o.
+    verify("post-try_recursive_enter");
+    return false;
+  }
+
+  _base[end] = o;
+  _top += oopSize;
+  verify("post-try_recursive_enter");
+  return true;
+}
+
+inline bool LockStack::try_recursive_exit(oop o) {
+  if (!VM_Version::supports_recursive_lightweight_locking()) {
+    return false;
+  }
+  verify("pre-try_recursive_exit");
+
+  // This will succeed iff the top two oops on the stack matches o.
+  // When successful the top oop will be popped of the lock-stack.
+  // When unsuccessful the lock may still be recursive, in which
+  // case the locking is unbalanced. This case is handled externally.
+
+  assert(contains(o), "entries must exist");
+
+  int end = to_index(_top);
+  if (end <= 1 || _base[end - 1] != o || _base[end - 2] != o) {
+    // The two topmost oops do not match o.
+    verify("post-try_recursive_exit");
+    return false;
+  }
+
   _top -= oopSize;
-  oop o = _base[to_index(_top)];
-#ifdef ASSERT
-  _base[to_index(_top)] = nullptr;
-#endif
-  assert(!contains(o), "entries must be unique: " PTR_FORMAT, p2i(o));
-  verify("post-pop");
-  return o;
+  DEBUG_ONLY(_base[to_index(_top)] = nullptr;)
+  verify("post-try_recursive_exit");
+  return true;
 }
 
-inline void LockStack::remove(oop o) {
+inline size_t LockStack::remove(oop o) {
   verify("pre-remove");
   assert(contains(o), "entry must be present: " PTR_FORMAT, p2i(o));
+
   int end = to_index(_top);
+  int inserted = 0;
   for (int i = 0; i < end; i++) {
-    if (_base[i] == o) {
-      int last = end - 1;
-      for (; i < last; i++) {
-        _base[i] = _base[i + 1];
+    if (_base[i] != o) {
+      if (inserted != i) {
+        _base[inserted] = _base[i];
       }
-      _top -= oopSize;
-#ifdef ASSERT
-      _base[to_index(_top)] = nullptr;
-#endif
-      break;
+      inserted++;
     }
   }
-  assert(!contains(o), "entries must be unique: " PTR_FORMAT, p2i(o));
+
+#ifdef ASSERT
+  for (int i = inserted; i < end; i++) {
+    _base[i] = nullptr;
+  }
+#endif
+
+  uint32_t removed = end - inserted;
+  _top -= removed * oopSize;
+  assert(!contains(o), "entry must have been removed: " PTR_FORMAT, p2i(o));
   verify("post-remove");
+  return removed;
 }
 
 inline bool LockStack::contains(oop o) const {
   verify("pre-contains");
 
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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 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.
@@ -37,10 +37,11 @@
 #include "oops/oopHandle.inline.hpp"
 #include "oops/weakHandle.inline.hpp"
 #include "prims/jvmtiDeferredUpdates.hpp"
 #include "prims/jvmtiExport.hpp"
 #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/mutexLocker.hpp"
 #include "runtime/objectMonitor.hpp"
@@ -51,10 +52,11 @@
 #include "runtime/safefetch.hpp"
 #include "runtime/safepointMechanism.inline.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "services/threadService.hpp"
 #include "utilities/dtrace.hpp"
+#include "utilities/globalDefinitions.hpp"
 #include "utilities/macros.hpp"
 #include "utilities/preserveException.hpp"
 #if INCLUDE_JFR
 #include "jfr/support/jfrFlush.hpp"
 #endif
@@ -310,11 +312,74 @@
 }
 
 // -----------------------------------------------------------------------------
 // Enter support
 
+bool ObjectMonitor::enter_for(JavaThread* locking_thread) {
+  // 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");
+
+  // Block out deflation as soon as possible.
+  add_to_contentions(1);
+
+  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);
+      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));
+  } 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);
+    }
+  }
+
+  add_to_contentions(-1);
+
+  assert(!success || owner_raw() == locking_thread, "must be");
+
+  return success;
+}
+
 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.
 
   void* cur = try_set_owner_from(nullptr, current);
   if (cur == nullptr) {
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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 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.
@@ -215,10 +215,11 @@
   static PerfCounter * _sync_Deflations;
   static PerfLongVariable * _sync_MonExtant;
 
   static int Knob_SpinLimit;
 
+  static ByteSize header_offset()      { return byte_offset_of(ObjectMonitor, _header); }
   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); }
@@ -296,10 +297,11 @@
   int       waiters() const;
 
   int       contentions() const;
   void      add_to_contentions(int value);
   intx      recursions() const                                         { return _recursions; }
+  void      set_recursions(size_t recursions);
 
   // JVM/TI GetObjectMonitorUsage() needs this:
   ObjectWaiter* first_waiter()                                         { return _WaitSet; }
   ObjectWaiter* next_waiter(ObjectWaiter* o)                           { return o->_next; }
   JavaThread* thread_of_waiter(ObjectWaiter* o)                        { return o->_thread; }
@@ -330,10 +332,11 @@
    public:
     ClearSuccOnSuspend(ObjectMonitor* om) : _om(om)  {}
     void operator()(JavaThread* current);
   };
  public:
+  bool      enter_for(JavaThread* locking_thread);
   bool      enter(JavaThread* current);
   void      exit(JavaThread* current, bool not_suspended = true);
   void      wait(jlong millis, bool interruptible, TRAPS);
   void      notify(TRAPS);
   void      notifyAll(TRAPS);
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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 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.
@@ -100,10 +100,16 @@
 // Add value to the contentions field.
 inline void ObjectMonitor::add_to_contentions(int value) {
   Atomic::add(&_contentions, value);
 }
 
+inline void ObjectMonitor::set_recursions(size_t recursions) {
+  assert(_recursions == 0, "must be");
+  assert(has_owner(), "must be owned");
+  _recursions = checked_cast<intx>(recursions);
+}
+
 // Clear _owner field; current value must match old_value.
 inline void ObjectMonitor::release_clear_owner(void* old_value) {
 #ifdef ASSERT
   void* prev = Atomic::load(&_owner);
   assert(prev == old_value, "unexpected prev owner=" INTPTR_FORMAT
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
@@ -3255,20 +3255,28 @@
   for (BasicObjectLock *kptr2 = fr.interpreter_frame_monitor_end();
        kptr2 < fr.interpreter_frame_monitor_begin();
        kptr2 = fr.next_monitor_in_interpreter_frame(kptr2) ) {
     if (kptr2->obj() != nullptr) {         // Avoid 'holes' in the monitor array
       BasicLock *lock = kptr2->lock();
-      // Inflate so the object's header no longer refers to the BasicLock.
-      if (lock->displaced_header().is_unlocked()) {
-        // The object is locked and the resulting ObjectMonitor* will also be
-        // locked so it can't be async deflated until ownership is dropped.
-        // See the big comment in basicLock.cpp: BasicLock::move_to().
-        ObjectSynchronizer::inflate_helper(kptr2->obj());
+      if (LockingMode == LM_LEGACY) {
+        // Inflate so the object's header no longer refers to the BasicLock.
+        if (lock->displaced_header().is_unlocked()) {
+          // The object is locked and the resulting ObjectMonitor* will also be
+          // locked so it can't be async deflated until ownership is dropped.
+          // See the big comment in basicLock.cpp: BasicLock::move_to().
+          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();
       }
-      // 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();
+#ifdef ASSERT
+      else {
+        buf[i] = badDispHeaderOSR;
+      }
+#endif
+      i++;
       buf[i++] = cast_from_oop<intptr_t>(kptr2->obj());
     }
   }
   assert(i - max_locals == active_monitor_count*2, "found the expected number of monitors");
 
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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 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.
@@ -34,10 +34,11 @@
 #include "memory/universe.hpp"
 #include "oops/markWord.hpp"
 #include "oops/oop.inline.hpp"
 #include "runtime/atomic.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/lockStack.inline.hpp"
@@ -58,10 +59,11 @@
 #include "runtime/vframe.hpp"
 #include "runtime/vmThread.hpp"
 #include "utilities/align.hpp"
 #include "utilities/dtrace.hpp"
 #include "utilities/events.hpp"
+#include "utilities/globalDefinitions.hpp"
 #include "utilities/linkedlist.hpp"
 #include "utilities/preserveException.hpp"
 
 void MonitorList::add(ObjectMonitor* m) {
   ObjectMonitor* head;
@@ -382,10 +384,23 @@
 
   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;
+    }
+  }
+
   const markWord mark = obj->mark();
 
   if (mark.has_monitor()) {
     ObjectMonitor* const m = mark.monitor();
     // An async deflation or GC can race us before we manage to make
@@ -435,12 +450,13 @@
 
   return false;        // revert to slow-path
 }
 
 // Handle notifications when synchronizing on value based classes
-void ObjectSynchronizer::handle_sync_on_value_based_class(Handle obj, JavaThread* current) {
-  frame last_frame = current->last_frame();
+void ObjectSynchronizer::handle_sync_on_value_based_class(Handle obj, JavaThread* locking_thread) {
+  assert(locking_thread == Thread::current() || locking_thread->is_obj_deopt_suspend(), "must be");
+  frame last_frame = locking_thread->last_frame();
   bool bcp_was_adjusted = false;
   // Don't decrement bcp if it points to the frame's first instruction.  This happens when
   // handle_sync_on_value_based_class() is called because of a synchronized method.  There
   // is no actual monitorenter instruction in the byte code in this case.
   if (last_frame.is_interpreted_frame() &&
@@ -449,28 +465,28 @@
     last_frame.interpreter_frame_set_bcp(last_frame.interpreter_frame_bcp() - 1);
     bcp_was_adjusted = true;
   }
 
   if (DiagnoseSyncOnValueBasedClasses == FATAL_EXIT) {
-    ResourceMark rm(current);
+    ResourceMark rm;
     stringStream ss;
-    current->print_active_stack_on(&ss);
+    locking_thread->print_active_stack_on(&ss);
     char* base = (char*)strstr(ss.base(), "at");
     char* newline = (char*)strchr(ss.base(), '\n');
     if (newline != nullptr) {
       *newline = '\0';
     }
     fatal("Synchronizing on object " INTPTR_FORMAT " of klass %s %s", p2i(obj()), obj->klass()->external_name(), base);
   } else {
     assert(DiagnoseSyncOnValueBasedClasses == LOG_WARNING, "invalid value for DiagnoseSyncOnValueBasedClasses");
-    ResourceMark rm(current);
+    ResourceMark rm;
     Log(valuebasedclasses) vblog;
 
     vblog.info("Synchronizing on object " INTPTR_FORMAT " of klass %s", p2i(obj()), obj->klass()->external_name());
-    if (current->has_last_Java_frame()) {
+    if (locking_thread->has_last_Java_frame()) {
       LogStream info_stream(vblog.info());
-      current->print_active_stack_on(&info_stream);
+      locking_thread->print_active_stack_on(&info_stream);
     } else {
       vblog.info("Cannot find the last Java frame");
     }
 
     EventSyncOnValueBasedClass event;
@@ -493,97 +509,177 @@
 #endif
 }
 
 // -----------------------------------------------------------------------------
 // Monitor Enter/Exit
+
+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 (!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
+    // if we have lost the race to async deflation and we simply try again.
+    while (true) {
+      ObjectMonitor* monitor = inflate_for(locking_thread, obj(), inflate_cause_monitor_enter);
+      if (monitor->enter_for(locking_thread)) {
+        return;
+      }
+      assert(monitor->is_being_async_deflated(), "must be");
+    }
+  }
+}
+
+void ObjectSynchronizer::enter(Handle obj, BasicLock* lock, JavaThread* current) {
+  assert(current == Thread::current(), "must be");
+  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
+    // we have lost the race to async deflation and we simply try again.
+    while (true) {
+      ObjectMonitor* monitor = inflate(current, obj(), inflate_cause_monitor_enter);
+      if (monitor->enter(current)) {
+        return;
+      }
+    }
+  }
+}
+
 // 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) {
 
-void ObjectSynchronizer::enter(Handle obj, BasicLock* lock, JavaThread* current) {
   if (obj->klass()->is_value_based()) {
-    handle_sync_on_value_based_class(obj, current);
+    handle_sync_on_value_based_class(obj, locking_thread);
   }
 
-  current->inc_held_monitor_count();
+  locking_thread->inc_held_monitor_count();
 
   if (!useHeavyMonitors()) {
     if (LockingMode == LM_LIGHTWEIGHT) {
       // Fast-locking does not use the 'lock' argument.
-      LockStack& lock_stack = current->lock_stack();
-      if (lock_stack.can_push()) {
-        markWord mark = obj()->mark_acquire();
-        while (mark.is_neutral()) {
-          // 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;
-          }
-          mark = old_mark;
+      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_neutral()) {
+        // 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;
       }
-      // All other paths fall-through to inflate-enter.
+
+      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) {
       markWord mark = obj->mark();
       if (mark.is_neutral()) {
         // Anticipate successful CAS -- the ST of the displaced mark must
         // be visible <= the ST performed by the CAS.
         lock->set_displaced_header(mark);
         if (mark == obj()->cas_set_mark(markWord::from_pointer(lock), mark)) {
-          return;
+          return true;
         }
-        // Fall through to inflate() ...
       } else if (mark.has_locker() &&
-                 current->is_lock_owned((address) mark.locker())) {
+                 locking_thread->is_lock_owned((address) mark.locker())) {
         assert(lock != mark.locker(), "must not re-lock the same lock");
         assert(lock != (BasicLock*) obj->mark().value(), "don't relock with same BasicLock");
         lock->set_displaced_header(markWord::from_pointer(nullptr));
-        return;
+        return true;
       }
 
       // The object header will never be displaced to this lock,
       // so it does not matter what the value is, except that it
       // must be non-zero to avoid looking like a re-entrant lock,
       // and must not look locked either.
       lock->set_displaced_header(markWord::unused_mark());
+
+      // Failed to fast lock.
+      return false;
     }
   } else if (VerifyHeavyMonitors) {
     guarantee((obj->mark().value() & markWord::lock_mask_in_place) != markWord::locked_value, "must not be lightweight/stack-locked");
   }
 
-  // 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_monitor_enter);
-    if (monitor->enter(current)) {
-      return;
-    }
-  }
+  return false;
 }
 
 void ObjectSynchronizer::exit(oop object, BasicLock* lock, JavaThread* current) {
   current->dec_held_monitor_count();
 
   if (!useHeavyMonitors()) {
     markWord mark = object->mark();
     if (LockingMode == LM_LIGHTWEIGHT) {
       // Fast-locking does not use the 'lock' argument.
-      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) {
-          current->lock_stack().remove(object);
-          return;
+      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;
         }
-        mark = old_mark;
       }
     } else 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
@@ -629,17 +725,11 @@
 
   // We have to take the slow-path of possible inflation and then exit.
   // The ObjectMonitor* can't be async deflated until ownership is
   // dropped inside exit() and the ObjectMonitor* must be !is_busy().
   ObjectMonitor* monitor = inflate(current, object, inflate_cause_vm_internal);
-  if (LockingMode == LM_LIGHTWEIGHT && monitor->is_owner_anonymous()) {
-    // It must be owned by us. Pop lock object from lock stack.
-    LockStack& lock_stack = current->lock_stack();
-    oop popped = lock_stack.pop();
-    assert(popped == object, "must be owned by this thread");
-    monitor->set_owner_from_anonymous(current);
-  }
+  assert(!monitor->is_owner_anonymous(), "must not be");
   monitor->exit(current);
 }
 
 // -----------------------------------------------------------------------------
 // JNI locks on java objects
@@ -903,11 +993,11 @@
     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;
 }
 
@@ -1298,31 +1388,44 @@
     return;
   }
   (void)inflate(Thread::current(), obj, inflate_cause_vm_internal);
 }
 
-// Can be called from non JavaThreads (e.g., VMThread) for FastHashCode
-// calculations as part of JVM/TI tagging.
-static bool is_lock_owned(Thread* thread, oop obj) {
-  assert(LockingMode == LM_LIGHTWEIGHT, "only call this with new lightweight locking enabled");
-  return thread->is_Java_thread() ? JavaThread::cast(thread)->lock_stack().contains(obj) : false;
+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);
+}
+
+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(Thread* current, oop object,
-                                           const InflateCause 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.
   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 current thread owns the
-    //                   object lock, then we make the current thread the
-    //                   ObjectMonitor owner and remove the lock from the
-    //                   current thread's lock stack.
+    //                   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.
     // *  stack-locked - Coerce it to inflated from stack-locked.
     // *  INFLATING    - Busy wait for conversion from stack-locked to
     //                   inflated.
     // *  neutral      - Aggressively inflate the object.
@@ -1330,13 +1433,15 @@
     // 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() && is_lock_owned(current, object)) {
-        inf->set_owner_from_anonymous(current);
-        JavaThread::cast(current)->lock_stack().remove(object);
+      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) {
@@ -1352,47 +1457,48 @@
         continue;
       }
     }
 
     // CASE: fast-locked
-    // Could be fast-locked either by current or by some other thread.
+    // 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
-    // this thread owns the monitor, then we set the ObjectMonitor's
-    // owner to this thread. Otherwise, we set the ObjectMonitor's owner
+    // 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 = is_lock_owned(current, object);
+      bool own = inflating_thread != nullptr && inflating_thread->lock_stack().contains(object);
       if (own) {
-        // Owned by us.
-        monitor->set_owner_from(nullptr, current);
+        // 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) {
-          JavaThread::cast(current)->lock_stack().remove(object);
+          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(current);
+          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()) {
@@ -1487,11 +1593,11 @@
 
       // 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(current);
+        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()) {
@@ -1531,11 +1637,11 @@
 
     // 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(current);
+      ResourceMark rm;
       lsh.print_cr("inflate(neutral): object=" INTPTR_FORMAT ", mark="
                    INTPTR_FORMAT ", type='%s'", p2i(object),
                    object->mark().value(), object->klass()->external_name());
     }
     if (event.should_commit()) {
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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 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.
@@ -90,11 +90,22 @@
   // deoptimization at monitor exit. Hence, it does not take a Handle argument.
 
   // This is the "slow path" version of monitor enter and exit.
   static void enter(Handle obj, BasicLock* lock, JavaThread* current);
   static void exit(oop obj, BasicLock* lock, JavaThread* current);
+  // Used to enter a monitor for another thread. This requires that the
+  // locking_thread is suspended, and that entering on a potential
+  // inflated monitor may only contend with deflation. That is the obj being
+  // locked on is either already locked by the locking_thread or cannot
+  // escape the locking_thread.
+  static void enter_for(Handle obj, BasicLock* lock, JavaThread* locking_thread);
+private:
+  // Shared implementation for enter and enter_for. Performs all but
+  // inflated monitor enter.
+  static bool enter_fast_impl(Handle obj, BasicLock* lock, JavaThread* locking_thread);
 
+public:
   // Used only to handle jni locks or other unmatched monitor enter/exit
   // Internally they will use heavy weight monitor.
   static void jni_enter(Handle obj, JavaThread* current);
   static void jni_exit(oop obj, TRAPS);
 
@@ -111,10 +122,18 @@
   // throwing unexpected InterruptedExecutionExceptions.
   static void waitUninterruptibly(Handle obj, jlong Millis, TRAPS);
 
   // Inflate light weight monitor to heavy weight monitor
   static ObjectMonitor* inflate(Thread* current, oop obj, const InflateCause cause);
+  // 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);
+
+public:
   // This version is only for internal use
   static void inflate_helper(oop obj);
   static const char* inflate_cause_name(const InflateCause cause);
 
   // Returns the identity hash value for an oop
@@ -191,11 +210,11 @@
   static u_char* get_gvars_addr();
   static u_char* get_gvars_hc_sequence_addr();
   static size_t get_gvars_size();
   static u_char* get_gvars_stw_random_addr();
 
-  static void handle_sync_on_value_based_class(Handle obj, JavaThread* current);
+  static void handle_sync_on_value_based_class(Handle obj, JavaThread* locking_thread);
 };
 
 // ObjectLocker enforces balanced locking and can never throw an
 // IllegalMonitorStateException. However, a pending exception may
 // have to pass through, and we must also be able to deal with
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
@@ -2579,21 +2579,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
@@ -1077,11 +1077,12 @@
 const juint    uninitMetaWordVal= 0xf7f7f7f7;               // value used to zap newly allocated metachunk
 const juint    badHeapWordVal   = 0xBAADBABE;               // value used to zap heap after GC
 const juint    badMetaWordVal   = 0xBAADFADE;               // value used to zap metadata heap after GC
 const int      badCodeHeapNewVal= 0xCC;                     // value used to zap Code heap at allocation
 const int      badCodeHeapFreeVal = 0xDD;                   // value used to zap Code heap at deallocation
-
+const intptr_t badDispHeaderDeopt = 0xDE0BD000;             // value to fill unused displaced header during deoptimization
+const intptr_t badDispHeaderOSR   = 0xDEAD05A0;             // value to fill unused displaced header during OSR
 
 // (These must be implemented as #defines because C++ compilers are
 // not obligated to inline non-integral constants!)
 #define       badAddress        ((address)::badAddressVal)
 #define       badHeapWord       (::badHeapWordVal)
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/memory/Universe.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/memory/Universe.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/memory/Universe.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/memory/Universe.java
@@ -114,17 +114,10 @@
   public void print() { printOn(System.out); }
   public void printOn(PrintStream tty) {
     heap().printOn(tty);
   }
 
-  // Check whether an element of a typeArrayOop with the given type must be
-  // aligned 0 mod 8.  The typeArrayOop itself must be aligned at least this
-  // strongly.
-  public static boolean elementTypeShouldBeAligned(BasicType type) {
-    return type == BasicType.T_DOUBLE || type == BasicType.T_LONG;
-  }
-
   // Check whether an object field (static/non-static) of the given type must be
   // aligned 0 mod 8.
   public static boolean fieldTypeShouldBeAligned(BasicType type) {
     return type == BasicType.T_DOUBLE || type == BasicType.T_LONG;
   }
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
@@ -55,36 +55,37 @@
   // Size of the arrayOopDesc
   private static long headerSize=0;
   private static long lengthOffsetInBytes=0;
   private static long typeSize;
 
+  // Check whether an element of an arrayOop with the given type must be
+  // aligned 0 mod 8.  The arrayOop itself must be aligned at least this
+  // strongly.
+  private static boolean elementTypeShouldBeAligned(BasicType type) {
+    if (VM.getVM().isLP64()) {
+      if (type == BasicType.T_OBJECT || type == BasicType.T_ARRAY) {
+        return !VM.getVM().isCompressedOopsEnabled();
+      }
+    }
+    return type == BasicType.T_DOUBLE || type == BasicType.T_LONG;
+  }
+
   private static long headerSizeInBytes() {
     if (headerSize != 0) {
       return headerSize;
     }
-    if (VM.getVM().isCompressedKlassPointersEnabled()) {
-      headerSize = typeSize;
-    } else {
-      headerSize = VM.getVM().alignUp(typeSize + VM.getVM().getIntSize(),
-                                      VM.getVM().getHeapWordSize());
-    }
+    headerSize = lengthOffsetInBytes() + VM.getVM().getIntSize();
     return headerSize;
   }
 
-  private static long headerSize(BasicType type) {
-    if (Universe.elementTypeShouldBeAligned(type)) {
-       return alignObjectSize(headerSizeInBytes())/VM.getVM().getHeapWordSize();
-    } else {
-      return headerSizeInBytes()/VM.getVM().getHeapWordSize();
-    }
-  }
-
-  private long lengthOffsetInBytes() {
+  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;
@@ -106,11 +107,17 @@
     s = Oop.alignObjectSize(s);
     return s;
   }
 
   public static long baseOffsetInBytes(BasicType type) {
-    return headerSize(type) * VM.getVM().getHeapWordSize();
+    long typeSizeInBytes = headerSizeInBytes();
+    if (elementTypeShouldBeAligned(type)) {
+      VM vm = VM.getVM();
+      return vm.alignUp(typeSizeInBytes, vm.getVM().getHeapWordSize());
+    } else {
+      return typeSizeInBytes;
+    }
   }
 
   public boolean isArray()             { return true; }
 
   public void iterateFields(OopVisitor visitor, boolean doVMFields) {
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() {
@@ -172,17 +188,27 @@
   }
   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());
+    assert(!hasMonitor());
+    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,25 @@
   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());
+    if (mark.hasMonitor()) {
+      ObjectMonitor mon = mark.monitor();
+      mark = mon.header();
+    }
+    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 +163,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 +224,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/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;
@@ -968,10 +969,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
@@ -20,10 +20,11 @@
  * 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/preservedMarks.inline.hpp"
 #include "oops/oop.inline.hpp"
 #include "unittest.hpp"
 
 // Class to create a "fake" oop with a mark that will
@@ -53,10 +54,12 @@
   FakeOop o1;
   FakeOop o2;
   FakeOop o3;
   FakeOop o4;
 
+  FlagSetting fs(UseAltGCForwarding, false);
+
   // 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());
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,124 @@
+/*
+ * 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.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) {
+  FlagSetting fs(UseAltGCForwarding, true);
+  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<true>(obj1, obj2);
+  ASSERT_EQ(obj1->mark().value(), make_mark(0 /* target_region */, 0 /* offset */));
+  ASSERT_EQ(SlidingForwarding::forwardee<true>(obj1), obj2);
+
+  SlidingForwarding::end();
+}
+
+// Test forwardings crossing 2 regions.
+TEST_VM(SlidingForwarding, tworegions) {
+  FlagSetting fs(UseAltGCForwarding, true);
+  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<true>(obj1, obj2);
+  ASSERT_EQ(obj1->mark().value(), make_mark(0 /* target_region */, 2 /* offset */));
+  ASSERT_EQ(SlidingForwarding::forwardee<true>(obj1), obj2);
+
+  SlidingForwarding::forward_to<true>(obj1, obj3);
+  ASSERT_EQ(obj1->mark().value(), make_mark(1 /* target_region */, 2 /* offset */));
+  ASSERT_EQ(SlidingForwarding::forwardee<true>(obj1), obj3);
+
+  SlidingForwarding::end();
+}
+
+// Test fallback forwardings crossing 4 regions.
+TEST_VM(SlidingForwarding, fallback) {
+  FlagSetting fs(UseAltGCForwarding, true);
+  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<true>(s_obj1, t_obj1);
+  ASSERT_EQ(s_obj1->mark().value(), make_mark(0 /* target_region */, 2 /* offset */));
+  ASSERT_EQ(SlidingForwarding::forwardee<true>(s_obj1), t_obj1);
+
+  SlidingForwarding::forward_to<true>(s_obj2, t_obj2);
+  ASSERT_EQ(s_obj2->mark().value(), make_mark(1 /* target_region */, 0 /* offset */));
+  ASSERT_EQ(SlidingForwarding::forwardee<true>(s_obj2), t_obj2);
+
+  SlidingForwarding::forward_to<true>(s_obj3, t_obj3);
+  ASSERT_EQ(s_obj3->mark().value(), make_fallback());
+  ASSERT_EQ(SlidingForwarding::forwardee<true>(s_obj3), t_obj3);
+
+  SlidingForwarding::forward_to<true>(s_obj4, t_obj4);
+  ASSERT_EQ(s_obj4->mark().value(), make_fallback());
+  ASSERT_EQ(SlidingForwarding::forwardee<true>(s_obj4), t_obj4);
+
+  SlidingForwarding::end();
+}
+
+#endif // PRODUCT
+#endif // _LP64
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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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.
@@ -25,23 +25,15 @@
 #include "oops/arrayOop.hpp"
 #include "oops/oop.inline.hpp"
 #include "unittest.hpp"
 #include "utilities/globalDefinitions.hpp"
 
-class arrayOopDescTest {
- public:
-
-  static int header_size_in_bytes() {
-    return arrayOopDesc::header_size_in_bytes();
-  }
-};
-
 static bool check_max_length_overflow(BasicType type) {
   julong length = arrayOopDesc::max_array_length(type);
   julong bytes_per_element = type2aelembytes(type);
   julong bytes = length * bytes_per_element
-          + arrayOopDescTest::header_size_in_bytes();
+          + arrayOopDesc::base_offset_in_bytes(type);
   return (julong) (size_t) bytes == bytes;
 }
 
 TEST_VM(arrayOopDesc, boolean) {
   ASSERT_PRED1(check_max_length_overflow, T_BOOLEAN);
@@ -85,5 +77,60 @@
 
 TEST_VM(arrayOopDesc, narrowOop) {
   ASSERT_PRED1(check_max_length_overflow, T_NARROWOOP);
 }
 // T_VOID and T_ADDRESS are not supported by max_array_length()
+
+TEST_VM(arrayOopDesc, base_offset) {
+#ifdef _LP64
+  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);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_OBJECT),  12);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_ARRAY),   12);
+  } 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);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_FLOAT),   16);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_LONG),    16);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_DOUBLE),  16);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_OBJECT),  16);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_ARRAY),   16);
+  } else {
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_BOOLEAN), 20);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_BYTE),    20);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_SHORT),   20);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_CHAR),    20);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_INT),     20);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_FLOAT),   20);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_LONG),    24);
+    EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_DOUBLE),  24);
+    if (UseCompressedOops) {
+      EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_OBJECT), 20);
+      EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_ARRAY),  20);
+    } else {
+      EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_OBJECT), 24);
+      EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_ARRAY),  24);
+    }
+  }
+#else
+  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);
+  EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_OBJECT),  12);
+  EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_ARRAY),   12);
+#endif
+}
diff a/test/hotspot/gtest/oops/test_objArrayOop.cpp b/test/hotspot/gtest/oops/test_objArrayOop.cpp
--- /dev/null
+++ b/test/hotspot/gtest/oops/test_objArrayOop.cpp
@@ -0,0 +1,69 @@
+/*
+ * 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 "oops/objArrayOop.hpp"
+#include "unittest.hpp"
+#include "utilities/globalDefinitions.hpp"
+
+TEST_VM(objArrayOop, osize) {
+  static const struct {
+    int objal; bool ccp; bool coops; bool coh; int result;
+  } x[] = {
+//    ObjAligInB, UseCCP, UseCoops, UseCOH, object size in heap words
+#ifdef _LP64
+    { 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
+    { 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
+    { 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
+    { 8,          false,  false,    true,   3 },  // 16 byte header, 8 byte oops
+    { 8,          false,  true,     true,   2 },  // 12 byte header, 4 byte oops
+    { 8,          true,   false,    true,   3 },  // 16 byte header, 8 byte oops
+    { 8,          true,   true,     true,   2 },  // 12 byte header, 4 byte oops
+    { 16,         false,  false,    true,   4 },  // 16 byte header, 8 byte oops, 16-byte align
+    { 16,         false,  true,     true,   2 },  // 12 byte header, 4 byte oops, 16-byte align
+    { 16,         true,   false,    true,   4 },  // 16 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,    true,   32 }, // 16 byte header, 8 byte oops, 256-byte align
+    { 256,        false,  true,     true,   32 }, // 12 byte header, 4 byte oops, 256-byte align
+    { 256,        true,   false,    true,   32 }, // 16 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,    false,  4 }, // 12 byte header, 4 byte oops, wordsize 4
+#endif
+    { -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 && 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::boolArrayKlassObj());
+  if (UseCompactObjectHeaders) {
+    o->set_mark(Universe::boolArrayKlassObj()->prototype_header());
+  } else {
+    o->set_klass(Universe::boolArrayKlassObj());
+  }
   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_lockStack.cpp b/test/hotspot/gtest/runtime/test_lockStack.cpp
--- /dev/null
+++ b/test/hotspot/gtest/runtime/test_lockStack.cpp
@@ -0,0 +1,427 @@
+/*
+ * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * 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 "runtime/interfaceSupport.inline.hpp"
+#include "runtime/lockStack.inline.hpp"
+#include "runtime/os.hpp"
+#include "unittest.hpp"
+#include "utilities/globalDefinitions.hpp"
+
+class LockStackTest : public ::testing::Test {
+public:
+  static void push_raw(LockStack& ls, oop obj) {
+    ls._base[ls.to_index(ls._top)] = obj;
+    ls._top += oopSize;
+  }
+
+  static void pop_raw(LockStack& ls) {
+    ls._top -= oopSize;
+#ifdef ASSERT
+    ls._base[ls.to_index(ls._top)] = nullptr;
+#endif
+  }
+
+  static oop at(LockStack& ls, int index) {
+    return ls._base[index];
+  }
+
+  static size_t size(LockStack& ls) {
+    return ls.to_index(ls._top);
+  }
+};
+
+#define recursive_enter(ls, obj)             \
+  do {                                       \
+    bool ret = ls.try_recursive_enter(obj);  \
+    EXPECT_TRUE(ret);                        \
+  } while (false)
+
+#define recursive_exit(ls, obj)             \
+  do {                                      \
+    bool ret = ls.try_recursive_exit(obj);  \
+    EXPECT_TRUE(ret);                       \
+  } while (false)
+
+TEST_VM_F(LockStackTest, is_recursive) {
+  if (LockingMode != LM_LIGHTWEIGHT || !VM_Version::supports_recursive_lightweight_locking()) {
+    return;
+  }
+
+  JavaThread* THREAD = JavaThread::current();
+  // the thread should be in vm to use locks
+  ThreadInVMfromNative ThreadInVMfromNative(THREAD);
+
+  LockStack& ls = THREAD->lock_stack();
+
+  EXPECT_TRUE(ls.is_empty());
+
+  oop obj0 = Universe::int_mirror();
+  oop obj1 = Universe::float_mirror();
+
+  push_raw(ls, obj0);
+
+  // 0
+  EXPECT_FALSE(ls.is_recursive(obj0));
+
+  push_raw(ls, obj1);
+
+  // 0, 1
+  EXPECT_FALSE(ls.is_recursive(obj0));
+  EXPECT_FALSE(ls.is_recursive(obj1));
+
+  push_raw(ls, obj1);
+
+  // 0, 1, 1
+  EXPECT_FALSE(ls.is_recursive(obj0));
+  EXPECT_TRUE(ls.is_recursive(obj1));
+
+  pop_raw(ls);
+  pop_raw(ls);
+  push_raw(ls, obj0);
+
+  // 0, 0
+  EXPECT_TRUE(ls.is_recursive(obj0));
+
+  push_raw(ls, obj0);
+
+  // 0, 0, 0
+  EXPECT_TRUE(ls.is_recursive(obj0));
+
+  pop_raw(ls);
+  push_raw(ls, obj1);
+
+  // 0, 0, 1
+  EXPECT_TRUE(ls.is_recursive(obj0));
+  EXPECT_FALSE(ls.is_recursive(obj1));
+
+  push_raw(ls, obj1);
+
+  // 0, 0, 1, 1
+  EXPECT_TRUE(ls.is_recursive(obj0));
+  EXPECT_TRUE(ls.is_recursive(obj1));
+
+  // Clear stack
+  pop_raw(ls);
+  pop_raw(ls);
+  pop_raw(ls);
+  pop_raw(ls);
+
+  EXPECT_TRUE(ls.is_empty());
+}
+
+TEST_VM_F(LockStackTest, try_recursive_enter) {
+  if (LockingMode != LM_LIGHTWEIGHT || !VM_Version::supports_recursive_lightweight_locking()) {
+    return;
+  }
+
+  JavaThread* THREAD = JavaThread::current();
+  // the thread should be in vm to use locks
+  ThreadInVMfromNative ThreadInVMfromNative(THREAD);
+
+  LockStack& ls = THREAD->lock_stack();
+
+  EXPECT_TRUE(ls.is_empty());
+
+  oop obj0 = Universe::int_mirror();
+  oop obj1 = Universe::float_mirror();
+
+  ls.push(obj0);
+
+  // 0
+  EXPECT_FALSE(ls.is_recursive(obj0));
+
+  ls.push(obj1);
+
+  // 0, 1
+  EXPECT_FALSE(ls.is_recursive(obj0));
+  EXPECT_FALSE(ls.is_recursive(obj1));
+
+  recursive_enter(ls, obj1);
+
+  // 0, 1, 1
+  EXPECT_FALSE(ls.is_recursive(obj0));
+  EXPECT_TRUE(ls.is_recursive(obj1));
+
+  recursive_exit(ls, obj1);
+  pop_raw(ls);
+  recursive_enter(ls, obj0);
+
+  // 0, 0
+  EXPECT_TRUE(ls.is_recursive(obj0));
+
+  recursive_enter(ls, obj0);
+
+  // 0, 0, 0
+  EXPECT_TRUE(ls.is_recursive(obj0));
+
+  recursive_exit(ls, obj0);
+  push_raw(ls, obj1);
+
+  // 0, 0, 1
+  EXPECT_TRUE(ls.is_recursive(obj0));
+  EXPECT_FALSE(ls.is_recursive(obj1));
+
+  recursive_enter(ls, obj1);
+
+  // 0, 0, 1, 1
+  EXPECT_TRUE(ls.is_recursive(obj0));
+  EXPECT_TRUE(ls.is_recursive(obj1));
+
+  // Clear stack
+  pop_raw(ls);
+  pop_raw(ls);
+  pop_raw(ls);
+  pop_raw(ls);
+
+  EXPECT_TRUE(ls.is_empty());
+}
+
+TEST_VM_F(LockStackTest, contains) {
+  if (LockingMode != LM_LIGHTWEIGHT) {
+    return;
+  }
+
+  const bool test_recursive = VM_Version::supports_recursive_lightweight_locking();
+
+  JavaThread* THREAD = JavaThread::current();
+  // the thread should be in vm to use locks
+  ThreadInVMfromNative ThreadInVMfromNative(THREAD);
+
+  LockStack& ls = THREAD->lock_stack();
+
+  EXPECT_TRUE(ls.is_empty());
+
+  oop obj0 = Universe::int_mirror();
+  oop obj1 = Universe::float_mirror();
+
+  EXPECT_FALSE(ls.contains(obj0));
+
+  ls.push(obj0);
+
+  // 0
+  EXPECT_TRUE(ls.contains(obj0));
+  EXPECT_FALSE(ls.contains(obj1));
+
+  if (test_recursive) {
+    push_raw(ls, obj0);
+
+    // 0, 0
+    EXPECT_TRUE(ls.contains(obj0));
+    EXPECT_FALSE(ls.contains(obj1));
+  }
+
+  push_raw(ls, obj1);
+
+  // 0, 0, 1
+  EXPECT_TRUE(ls.contains(obj0));
+  EXPECT_TRUE(ls.contains(obj1));
+
+  if (test_recursive) {
+    push_raw(ls, obj1);
+
+    // 0, 0, 1, 1
+    EXPECT_TRUE(ls.contains(obj0));
+    EXPECT_TRUE(ls.contains(obj1));
+  }
+
+  pop_raw(ls);
+  if (test_recursive) {
+    pop_raw(ls);
+    pop_raw(ls);
+  }
+  push_raw(ls, obj1);
+
+  // 0, 1
+  EXPECT_TRUE(ls.contains(obj0));
+  EXPECT_TRUE(ls.contains(obj1));
+
+  // Clear stack
+  pop_raw(ls);
+  pop_raw(ls);
+
+  EXPECT_TRUE(ls.is_empty());
+}
+
+TEST_VM_F(LockStackTest, remove) {
+  if (LockingMode != LM_LIGHTWEIGHT) {
+    return;
+  }
+
+  const bool test_recursive = VM_Version::supports_recursive_lightweight_locking();
+
+  JavaThread* THREAD = JavaThread::current();
+  // the thread should be in vm to use locks
+  ThreadInVMfromNative ThreadInVMfromNative(THREAD);
+
+  LockStack& ls = THREAD->lock_stack();
+
+  EXPECT_TRUE(ls.is_empty());
+
+  oop obj0 = Universe::int_mirror();
+  oop obj1 = Universe::float_mirror();
+  oop obj2 = Universe::short_mirror();
+  oop obj3 = Universe::long_mirror();
+
+  push_raw(ls, obj0);
+
+  // 0
+  {
+    size_t removed = ls.remove(obj0);
+    EXPECT_EQ(removed, 1u);
+    EXPECT_FALSE(ls.contains(obj0));
+  }
+
+  if (test_recursive) {
+    push_raw(ls, obj0);
+    push_raw(ls, obj0);
+
+    // 0, 0
+    {
+      size_t removed = ls.remove(obj0);
+      EXPECT_EQ(removed, 2u);
+      EXPECT_FALSE(ls.contains(obj0));
+    }
+  }
+
+  push_raw(ls, obj0);
+  push_raw(ls, obj1);
+
+  // 0, 1
+  {
+    size_t removed = ls.remove(obj0);
+    EXPECT_EQ(removed, 1u);
+    EXPECT_FALSE(ls.contains(obj0));
+    EXPECT_TRUE(ls.contains(obj1));
+
+    ls.remove(obj1);
+    EXPECT_TRUE(ls.is_empty());
+  }
+
+  push_raw(ls, obj0);
+  push_raw(ls, obj1);
+
+  // 0, 1
+  {
+    size_t removed = ls.remove(obj1);
+    EXPECT_EQ(removed, 1u);
+    EXPECT_FALSE(ls.contains(obj1));
+    EXPECT_TRUE(ls.contains(obj0));
+
+    ls.remove(obj0);
+    EXPECT_TRUE(ls.is_empty());
+  }
+
+  if (test_recursive) {
+    push_raw(ls, obj0);
+    push_raw(ls, obj0);
+    push_raw(ls, obj1);
+
+    // 0, 0, 1
+    {
+      size_t removed = ls.remove(obj0);
+      EXPECT_EQ(removed, 2u);
+      EXPECT_FALSE(ls.contains(obj0));
+      EXPECT_TRUE(ls.contains(obj1));
+
+      ls.remove(obj1);
+      EXPECT_TRUE(ls.is_empty());
+    }
+
+    push_raw(ls, obj0);
+    push_raw(ls, obj1);
+    push_raw(ls, obj1);
+
+    // 0, 1, 1
+    {
+      size_t removed = ls.remove(obj1);
+      EXPECT_EQ(removed, 2u);
+      EXPECT_FALSE(ls.contains(obj1));
+      EXPECT_TRUE(ls.contains(obj0));
+
+      ls.remove(obj0);
+      EXPECT_TRUE(ls.is_empty());
+    }
+
+    push_raw(ls, obj0);
+    push_raw(ls, obj1);
+    push_raw(ls, obj1);
+    push_raw(ls, obj2);
+    push_raw(ls, obj2);
+    push_raw(ls, obj2);
+    push_raw(ls, obj2);
+    push_raw(ls, obj3);
+
+    // 0, 1, 1, 2, 2, 2, 2, 3
+    {
+      EXPECT_EQ(size(ls), 8u);
+
+      size_t removed = ls.remove(obj1);
+      EXPECT_EQ(removed, 2u);
+
+      EXPECT_TRUE(ls.contains(obj0));
+      EXPECT_FALSE(ls.contains(obj1));
+      EXPECT_TRUE(ls.contains(obj2));
+      EXPECT_TRUE(ls.contains(obj3));
+
+      EXPECT_EQ(at(ls, 0), obj0);
+      EXPECT_EQ(at(ls, 1), obj2);
+      EXPECT_EQ(at(ls, 2), obj2);
+      EXPECT_EQ(at(ls, 3), obj2);
+      EXPECT_EQ(at(ls, 4), obj2);
+      EXPECT_EQ(at(ls, 5), obj3);
+      EXPECT_EQ(size(ls), 6u);
+
+      removed = ls.remove(obj2);
+      EXPECT_EQ(removed, 4u);
+
+      EXPECT_TRUE(ls.contains(obj0));
+      EXPECT_FALSE(ls.contains(obj1));
+      EXPECT_FALSE(ls.contains(obj2));
+      EXPECT_TRUE(ls.contains(obj3));
+
+      EXPECT_EQ(at(ls, 0), obj0);
+      EXPECT_EQ(at(ls, 1), obj3);
+      EXPECT_EQ(size(ls), 2u);
+
+      removed = ls.remove(obj0);
+      EXPECT_EQ(removed, 1u);
+
+      EXPECT_FALSE(ls.contains(obj0));
+      EXPECT_FALSE(ls.contains(obj1));
+      EXPECT_FALSE(ls.contains(obj2));
+      EXPECT_TRUE(ls.contains(obj3));
+
+      EXPECT_EQ(at(ls, 0), obj3);
+      EXPECT_EQ(size(ls), 1u);
+
+      removed = ls.remove(obj3);
+      EXPECT_EQ(removed, 1u);
+
+      EXPECT_TRUE(ls.is_empty());
+      EXPECT_EQ(size(ls), 0u);
+    }
+  }
+
+  EXPECT_TRUE(ls.is_empty());
+}
diff a/test/hotspot/jtreg/ProblemList-lilliput.txt b/test/hotspot/jtreg/ProblemList-lilliput.txt
--- /dev/null
+++ b/test/hotspot/jtreg/ProblemList-lilliput.txt
@@ -0,0 +1,40 @@
+#
+# 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.
+#
+
+#
+# These tests are problematic when +UseCompactObjectHeaders is enabled.
+# The test exclusions are for the cases when we are sure the tests would fail
+# for the known and innocuous implementation reasons.
+#
+
+
+compiler/ciReplay/TestInlining.java 8316441 generic-all
+compiler/ciReplay/TestInliningProtectionDomain.java 8316441 generic-all
+compiler/ciReplay/TestServerVM.java 8316441 generic-all
+compiler/ciReplay/TestUnresolvedClasses.java 8316441 generic-all
+compiler/ciReplay/TestLambdas.java 8316441 generic-all
+compiler/ciReplay/TestDumpReplay.java 8316441 generic-all
+compiler/ciReplay/TestDumpReplayCommandLine.java 8316441 generic-all
+compiler/ciReplay/TestIncrementalInlining.java 8316441 generic-all
+compiler/ciReplay/TestInvalidReplayFile.java 8316441 generic-all
+compiler/ciReplay/TestNoClassFile.java 8316441 generic-all
diff a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt
--- a/test/hotspot/jtreg/ProblemList.txt
+++ b/test/hotspot/jtreg/ProblemList.txt
@@ -53,17 +53,17 @@
 
 compiler/rtm/locking/TestRTMAbortRatio.java 8183263 generic-x64,generic-i586
 compiler/rtm/locking/TestRTMAbortThreshold.java 8183263 generic-x64,generic-i586
 compiler/rtm/locking/TestRTMAfterNonRTMDeopt.java 8183263 generic-x64,generic-i586
 compiler/rtm/locking/TestRTMDeoptOnHighAbortRatio.java 8183263 generic-x64,generic-i586
-compiler/rtm/locking/TestRTMDeoptOnLowAbortRatio.java 8183263,8307907 generic-x64,generic-i586,aix-ppc64
+compiler/rtm/locking/TestRTMDeoptOnLowAbortRatio.java 8183263 generic-x64,generic-i586
 compiler/rtm/locking/TestRTMLockingCalculationDelay.java 8183263 generic-x64,generic-i586
-compiler/rtm/locking/TestRTMLockingThreshold.java 8183263,8307907 generic-x64,generic-i586,aix-ppc64
+compiler/rtm/locking/TestRTMLockingThreshold.java 8183263 generic-x64,generic-i586
 compiler/rtm/locking/TestRTMSpinLoopCount.java 8183263 generic-x64,generic-i586
 compiler/rtm/locking/TestUseRTMDeopt.java 8183263 generic-x64,generic-i586
-compiler/rtm/locking/TestUseRTMXendForLockBusy.java 8183263,8307907 generic-x64,generic-i586,aix-ppc64
-compiler/rtm/print/TestPrintPreciseRTMLockingStatistics.java 8183263,8307907 generic-x64,generic-i586,aix-ppc64
+compiler/rtm/locking/TestUseRTMXendForLockBusy.java 8183263 generic-x64,generic-i586
+compiler/rtm/print/TestPrintPreciseRTMLockingStatistics.java 8183263 generic-x64,generic-i586
 
 compiler/c2/Test8004741.java 8235801 generic-all
 compiler/c2/irTests/TestDuplicateBackedge.java 8318904 generic-all
 
 compiler/codecache/jmx/PoolsIndependenceTest.java 8264632 macosx-all
diff a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups
--- a/test/hotspot/jtreg/TEST.groups
+++ b/test/hotspot/jtreg/TEST.groups
@@ -1,7 +1,7 @@
 #
-# Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2013, 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.
@@ -147,10 +147,11 @@
   -serviceability/jvmti/negative
 
 tier1_common = \
   sanity/BasicVMTest.java \
   gtest/GTestWrapper.java \
+  gtest/LockStackGtests.java \
   gtest/MetaspaceGtests.java \
   gtest/LargePageGtests.java \
   gtest/NMTGtests.java \
 
 tier1_compiler = \
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
@@ -150,11 +150,12 @@
     }
 
     @Test
     @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" },
         applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"},
-        applyIfPlatform = {"64-bit", "true"})
+        applyIfPlatform = {"64-bit", "true"},
+        applyIf = {"UseCompactObjectHeaders", "false"})
     // 32-bit: offsets are badly aligned (UNSAFE.ARRAY_BYTE_BASE_OFFSET is 4 byte aligned, but not 8 byte aligned).
     //         might get fixed with JDK-8325155.
     public static void testByteLong1a(byte[] dest, long[] src) {
         for (int i = 0; i < src.length; i++) {
             UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * i, src[i]);
@@ -162,32 +163,35 @@
     }
 
     @Test
     @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" },
         applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"},
-        applyIfPlatform = {"64-bit", "true"})
+        applyIfPlatform = {"64-bit", "true"},
+        applyIf = {"UseCompactObjectHeaders", "false"})
     // 32-bit: address has ConvL2I for cast of long to address, not supported.
     public static void testByteLong1b(byte[] dest, long[] src) {
         for (int i = 0; i < src.length; i++) {
             UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * i, src[i]);
         }
     }
 
     @Test
     @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" },
-        applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"})
+        applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"},
+        applyIf = {"UseCompactObjectHeaders", "false"})
     public static void testByteLong1c(byte[] dest, long[] src) {
         long base = 64; // make sure it is big enough and 8 byte aligned (required for 32-bit)
         for (int i = 0; i < src.length - 8; i++) {
             UNSAFE.putLongUnaligned(dest, base + 8 * i, src[i]);
         }
     }
 
     @Test
     @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" },
         applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"},
-        applyIfPlatform = {"64-bit", "true"})
+        applyIfPlatform = {"64-bit", "true"},
+        applyIf = {"UseCompactObjectHeaders", "false"})
     // 32-bit: address has ConvL2I for cast of long to address, not supported.
     public static void testByteLong1d(byte[] dest, long[] src) {
         long base = 64; // make sure it is big enough and 8 byte aligned (required for 32-bit)
         for (int i = 0; i < src.length - 8; i++) {
             UNSAFE.putLongUnaligned(dest, base + 8L * i, src[i]);
@@ -203,11 +207,12 @@
     }
 
     @Test
     @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" },
         applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"},
-        applyIfPlatform = {"64-bit", "true"})
+        applyIfPlatform = {"64-bit", "true"},
+        applyIf = {"UseCompactObjectHeaders", "false"})
     // 32-bit: offsets are badly aligned (UNSAFE.ARRAY_BYTE_BASE_OFFSET is 4 byte aligned, but not 8 byte aligned).
     //         might get fixed with JDK-8325155.
     public static void testByteLong2a(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]);
@@ -215,11 +220,12 @@
     }
 
     @Test
     @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" },
         applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"},
-        applyIfPlatform = {"64-bit", "true"})
+        applyIfPlatform = {"64-bit", "true"},
+        applyIf = {"UseCompactObjectHeaders", "false"})
     // 32-bit: address has ConvL2I for cast of long to address, not supported.
     public static void testByteLong2b(byte[] dest, long[] src) {
         for (int i = 1; i < src.length; i++) {
             UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * (i - 1), src[i]);
         }
@@ -232,11 +238,12 @@
     }
 
     @Test
     @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" },
         applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"},
-        applyIfPlatform = {"64-bit", "true"})
+        applyIfPlatform = {"64-bit", "true"},
+        applyIf = {"UseCompactObjectHeaders", "false"})
     // 32-bit: offsets are badly aligned (UNSAFE.ARRAY_BYTE_BASE_OFFSET is 4 byte aligned, but not 8 byte aligned).
     //         might get fixed with JDK-8325155.
     public static void testByteLong3a(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]);
@@ -244,11 +251,12 @@
     }
 
     @Test
     @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" },
         applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"},
-        applyIfPlatform = {"64-bit", "true"})
+        applyIfPlatform = {"64-bit", "true"},
+        applyIf = {"UseCompactObjectHeaders", "false"})
     // 32-bit: address has ConvL2I for cast of long to address, not supported.
     public static void testByteLong3b(byte[] dest, long[] src) {
         for (int i = 0; i < src.length - 1; i++) {
             UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * (i + 1), src[i]);
         }
@@ -291,11 +299,12 @@
     }
 
     @Test
     @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" },
         applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"},
-        applyIfPlatform = {"64-bit", "true"})
+        applyIfPlatform = {"64-bit", "true"},
+        applyIf = {"UseCompactObjectHeaders", "false"})
     // 32-bit: offsets are badly aligned (UNSAFE.ARRAY_BYTE_BASE_OFFSET is 4 byte aligned, but not 8 byte aligned).
     //         might get fixed with JDK-8325155.
     public static void testByteLong5a(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]);
@@ -303,11 +312,12 @@
     }
 
     @Test
     @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" },
         applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"},
-        applyIfPlatform = {"64-bit", "true"})
+        applyIfPlatform = {"64-bit", "true"},
+        applyIf = {"UseCompactObjectHeaders", "false"})
     // 32-bit: address has ConvL2I for cast of long to address, not supported.
     public static void testByteLong5b(byte[] dest, long[] src, int start, int stop) {
         for (int i = start; i < stop; i++) {
             UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * (i + baseOffset), src[i]);
         }
@@ -321,11 +331,12 @@
     }
 
     @Test
     @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" },
         applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"},
-        applyIfPlatform = {"64-bit", "true"})
+        applyIfPlatform = {"64-bit", "true"},
+        applyIf = {"UseCompactObjectHeaders", "false"})
     // 32-bit: offsets are badly aligned (UNSAFE.ARRAY_BYTE_BASE_OFFSET is 4 byte aligned, but not 8 byte aligned).
     //         might get fixed with JDK-8325155.
     public static void testByteByte1a(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));
@@ -333,11 +344,12 @@
     }
 
     @Test
     @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" },
         applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"},
-        applyIfPlatform = {"64-bit", "true"})
+        applyIfPlatform = {"64-bit", "true"},
+        applyIf = {"UseCompactObjectHeaders", "false"})
     // 32-bit: address has ConvL2I for cast of long to address, not supported.
     public static void testByteByte1b(byte[] dest, byte[] src) {
         for (int i = 0; i < src.length / 8; i++) {
             UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * i, UNSAFE.getLongUnaligned(src, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * 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(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" },
+        applyIf = {"UseCompactObjectHeaders", "false"})
     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/escapeAnalysis/Test8329757.java b/test/hotspot/jtreg/compiler/escapeAnalysis/Test8329757.java
--- /dev/null
+++ b/test/hotspot/jtreg/compiler/escapeAnalysis/Test8329757.java
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8329757
+ * @summary Deoptimization with nested eliminated and not eliminated locks
+ *          caused reordered lock stacks. This can be handled by the interpreter
+ *          but when a frame is migrated back to compiled code via OSR the C2
+ *          assumption about balanced monitorenter-monitorexit is broken.
+ *
+ * @requires vm.compMode != "Xint"
+ *
+ * @run main/othervm compiler.escapeAnalysis.Test8329757
+ */
+
+package compiler.escapeAnalysis;
+
+public class Test8329757 {
+
+    int a = 400;
+    Double ddd;
+
+    void q() {
+        int e;
+        synchronized (new Double(1.1f)) {
+        int[] f = new int[a];
+        synchronized (Test8329757.class) {
+            for (int d = 4; d < 127; d++) {
+            e = 13;
+            do switch (d * 5) {
+                case 0:
+                case 42:
+                case 29:
+                e = d;
+                default:
+                f[1] = e;
+            } while (--e > 0);
+            }
+        }
+        }
+    }
+
+    void n() {
+        for (int j = 6; j < 274; ++j) q();
+    }
+
+    public static void main(String[] args) {
+        Test8329757 r = new Test8329757();
+        for (int i = 0; i < 1000; i++) r.n();
+    }
+}
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
@@ -139,11 +139,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/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
@@ -265,11 +265,12 @@
         verify("test6", dataL, goldL6);
     }
 
     @Test
     @IR(counts = {IRNode.ADD_VI, "> 0", IRNode.MUL_VI, "> 0", IRNode.ADD_VF, "> 0"},
-        applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"})
+        applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"},
+        applyIf = {"UseCompactObjectHeaders", "false"})
     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
             int v00 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 0) + 3;
diff a/test/hotspot/jtreg/compiler/rtm/cli/RTMGenericCommandLineOptionTest.java b/test/hotspot/jtreg/compiler/rtm/cli/RTMGenericCommandLineOptionTest.java
--- a/test/hotspot/jtreg/compiler/rtm/cli/RTMGenericCommandLineOptionTest.java
+++ b/test/hotspot/jtreg/compiler/rtm/cli/RTMGenericCommandLineOptionTest.java
@@ -34,12 +34,10 @@
  */
 public abstract class RTMGenericCommandLineOptionTest {
 
     protected static final String RTM_INSTR_ERROR
             = "RTM instructions are not available on this CPU";
-    protected static final String RTM_OS_ERROR
-            = "RTM is not supported on this OS version";
     protected static final String RTM_UNSUPPORTED_VM_ERROR
             = "RTM locking optimization is not supported in this VM";
     protected static final String RTM_FOR_STACK_LOCKS_WARNING
             = "UseRTMForStackLocks flag should be off when UseRTMLocking "
             + "flag is off";
@@ -82,11 +80,11 @@
         this.experimentalOptionError = CommandLineOptionTest.
                 getExperimentalOptionErrorMessage(optionName);
     }
 
     public void runTestCases() throws Throwable {
-        if (Platform.isX86() || Platform.isX64() || Platform.isPPC()) {
+        if (Platform.isX86() || Platform.isX64()) {
             if (Platform.isServer()) {
                 runX86SupportedVMTestCases();
             } else {
                 runX86UnsupportedVMTestCases();
             }
diff a/test/hotspot/jtreg/compiler/rtm/cli/TestUseRTMLockingOptionOnUnsupportedCPU.java b/test/hotspot/jtreg/compiler/rtm/cli/TestUseRTMLockingOptionOnUnsupportedCPU.java
--- a/test/hotspot/jtreg/compiler/rtm/cli/TestUseRTMLockingOptionOnUnsupportedCPU.java
+++ b/test/hotspot/jtreg/compiler/rtm/cli/TestUseRTMLockingOptionOnUnsupportedCPU.java
@@ -47,41 +47,21 @@
         String unrecognizedOption
                 = CommandLineOptionTest.getUnrecognizedOptionErrorMessage(
                 "UseRTMLocking");
         String errorMessage = RTMGenericCommandLineOptionTest.RTM_INSTR_ERROR;
 
-        if (Platform.isX86() || Platform.isX64() || Platform.isPPC()) {
+        if (Platform.isX86() || Platform.isX64()) {
             String shouldFailMessage = "JVM startup should fail with option " +
                                        "-XX:+UseRTMLocking on unsupported CPU";
 
-            try {
-                // verify that we get an error when use +UseRTMLocking
-                // on unsupported CPU
-                CommandLineOptionTest.verifySameJVMStartup(
-                        new String[] { errorMessage },
-                        new String[] { unrecognizedOption }, shouldFailMessage,
-                        shouldFailMessage + ". Error message should be shown.",
-                        ExitCode.FAIL, "-XX:+UseRTMLocking");
-            } catch (Throwable e) {
-                // verify that we get an error when use +UseRTMLocking
-                // on unsupported OS. It might be the case that although CPU
-                // supports RTM the OS version does not support RTM
-                if (Platform.isPPC()) {
-                    String errorMessage2 = RTMGenericCommandLineOptionTest.RTM_OS_ERROR;
-                    String shouldFailMessage2 = "JVM startup should fail with option " +
-                                                "-XX:+UseRTMLocking on unsupported CPU or " +
-                                                "OS version";
-
-                    CommandLineOptionTest.verifySameJVMStartup(
-                            new String[] { errorMessage2 },
-                            new String[] { unrecognizedOption}, shouldFailMessage2,
-                            shouldFailMessage2 + ". Error message should be shown.",
-                            ExitCode.FAIL, "-XX:+UseRTMLocking");
-                } else {
-                    throw e; // checking unsupported OS error is not necessary
-                }
-            }
+            // verify that we get an error when use +UseRTMLocking
+            // on unsupported CPU
+            CommandLineOptionTest.verifySameJVMStartup(
+                    new String[] { errorMessage },
+                    new String[] { unrecognizedOption }, shouldFailMessage,
+                    shouldFailMessage + ". Error message should be shown.",
+                    ExitCode.FAIL, "-XX:+UseRTMLocking");
 
             String shouldPassMessage = "JVM startup should pass with option "
                     + "-XX:-UseRTMLocking even on unsupported CPU";
             // verify that we can pass -UseRTMLocking without
             // getting any error messages
diff a/test/hotspot/jtreg/compiler/rtm/locking/TestRTMSpinLoopCount.java b/test/hotspot/jtreg/compiler/rtm/locking/TestRTMSpinLoopCount.java
--- a/test/hotspot/jtreg/compiler/rtm/locking/TestRTMSpinLoopCount.java
+++ b/test/hotspot/jtreg/compiler/rtm/locking/TestRTMSpinLoopCount.java
@@ -61,15 +61,11 @@
     private static final long MAX_ABORTS = RTM_RETRY_COUNT + 1L;
     private static int[] SPIN_LOOP_COUNTS;
 
     protected void runTestCases() throws Throwable {
 
-        if (Platform.isPPC()) {
-            SPIN_LOOP_COUNTS = new int[] { 0, 10, 100, 1_000, 10_000 };
-        } else {
-            SPIN_LOOP_COUNTS = new int[] { 0, 100, 1_000, 10_000, 100_000 };
-        }
+        SPIN_LOOP_COUNTS = new int[] { 0, 100, 1_000, 10_000, 100_000 };
 
         long[] aborts = new long[TestRTMSpinLoopCount.SPIN_LOOP_COUNTS.length];
 
         for (int i = 0; i < TestRTMSpinLoopCount.SPIN_LOOP_COUNTS.length; i++) {
             aborts[i] = getAbortsCountOnLockBusy(
diff a/test/hotspot/jtreg/gc/TestAllocHumongousFragment.java b/test/hotspot/jtreg/gc/TestAllocHumongousFragment.java
--- a/test/hotspot/jtreg/gc/TestAllocHumongousFragment.java
+++ b/test/hotspot/jtreg/gc/TestAllocHumongousFragment.java
@@ -175,10 +175,22 @@
  * @run main/othervm -Xlog:gc+region=trace -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g
  *      -XX:VerifyGCType=full -XX:+VerifyDuringGC -XX:+VerifyAfterGC
  *      TestAllocHumongousFragment
  */
 
+ /*
+ * @test id=g1-alt-forwarding
+ * @summary Make sure G1 can recover from humongous allocation fragmentation, with alt GC forwarding
+ * @key randomness
+ * @requires vm.gc.G1
+ * @library /test/lib
+ *
+ * @run main/othervm -Xlog:gc+region=trace -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g
+ *      -XX:VerifyGCType=full -XX:+VerifyDuringGC -XX:+VerifyAfterGC -XX:+UseAltGCForwarding
+ *      TestAllocHumongousFragment
+ */
+
 import java.util.*;
 import jdk.test.lib.Utils;
 
 public class TestAllocHumongousFragment {
 
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;
@@ -43,18 +43,22 @@
 import gc.g1.plab.lib.AppPLABPromotion;
 import gc.g1.plab.lib.LogParser;
 import gc.g1.plab.lib.PLABUtils;
 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;
 
@@ -70,11 +74,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;
     private static final int OBJECT_SIZE_MEDIUM = 100;
-    private static final int OBJECT_SIZE_HIGH = 3250;
+    private static final int OBJECT_SIZE_HIGH = COMPACT_HEADERS ? 3266 : 3250;
     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/gc/stress/systemgc/TestSystemGCWithG1.java b/test/hotspot/jtreg/gc/stress/systemgc/TestSystemGCWithG1.java
--- a/test/hotspot/jtreg/gc/stress/systemgc/TestSystemGCWithG1.java
+++ b/test/hotspot/jtreg/gc/stress/systemgc/TestSystemGCWithG1.java
@@ -23,18 +23,29 @@
  */
 
 package gc.stress.systemgc;
 
 /*
- * @test TestSystemGCWithG1
+ * @test id=default
  * @key stress
  * @bug 8190703
  * @library /
  * @requires vm.gc.G1
  * @summary Stress the G1 GC full GC by allocating objects of different lifetimes concurrently with System.gc().
  * @run main/othervm/timeout=300 -Xlog:gc*=info -Xmx512m -XX:+UseG1GC gc.stress.systemgc.TestSystemGCWithG1 270
  */
+
+/*
+ * @test id=alt-forwarding
+ * @key stress
+ * @bug 8190703
+ * @library /
+ * @requires vm.gc.G1
+ * @requires (vm.bits == "64")
+ * @summary Stress the G1 GC full GC by allocating objects of different lifetimes concurrently with System.gc().
+ * @run main/othervm/timeout=300 -XX:+UnlockExperimentalVMOptions -XX:+UseAltGCForwarding -Xlog:gc*=info -Xmx512m -XX:+UseG1GC gc.stress.systemgc.TestSystemGCWithG1 270
+ */
 public class TestSystemGCWithG1 {
     public static void main(String[] args) throws Exception {
         TestSystemGC.main(args);
     }
 }
diff a/test/hotspot/jtreg/gc/stress/systemgc/TestSystemGCWithSerial.java b/test/hotspot/jtreg/gc/stress/systemgc/TestSystemGCWithSerial.java
--- a/test/hotspot/jtreg/gc/stress/systemgc/TestSystemGCWithSerial.java
+++ b/test/hotspot/jtreg/gc/stress/systemgc/TestSystemGCWithSerial.java
@@ -23,18 +23,49 @@
  */
 
 package gc.stress.systemgc;
 
 /*
- * @test TestSystemGCWithSerial
+ * @test id=default
  * @key stress
  * @bug 8190703
  * @library /
  * @requires vm.gc.Serial
  * @summary Stress the Serial GC full GC by allocating objects of different lifetimes concurrently with System.gc().
  * @run main/othervm/timeout=300 -Xlog:gc*=info -Xmx512m -XX:+UseSerialGC gc.stress.systemgc.TestSystemGCWithSerial 270
  */
+
+/*
+ * @test id=alt-forwarding
+ * @key stress
+ * @bug 8190703
+ * @library /
+ * @requires vm.gc.Serial
+ * @summary Stress the Serial GC full GC by allocating objects of different lifetimes concurrently with System.gc().
+ * @run main/othervm/timeout=300 -XX:+UnlockExperimentalVMOptions -XX:+UseAltGCForwarding -Xlog:gc*=info -Xmx512m -XX:+UseSerialGC gc.stress.systemgc.TestSystemGCWithSerial 270
+ */
+
+/*
+ * @test id=alt-forwarding-unaligned
+ * @key stress
+ * @bug 8190703
+ * @library /
+ * @requires vm.gc.Serial
+ * @summary Stress the Serial GC full GC by allocating objects of different lifetimes concurrently with System.gc().
+ * @run main/othervm/timeout=300 -XX:+UnlockExperimentalVMOptions -XX:+UseAltGCForwarding -Xlog:gc*=info -Xmx700m -XX:+UseSerialGC gc.stress.systemgc.TestSystemGCWithSerial 270
+ */
+
+/*
+ * @test id=alt-forwarding-large-heap
+ * @key stress
+ * @bug 8190703
+ * @library /
+ * @requires vm.gc.Serial
+ * @requires (vm.bits == "64") & (os.maxMemory >= 6G)
+ * @summary Stress the Serial GC full GC by allocating objects of different lifetimes concurrently with System.gc().
+ * @run main/othervm/timeout=300 -XX:+UnlockExperimentalVMOptions -XX:+UseAltGCForwarding -Xlog:gc*=info -Xmx6g -XX:+UseSerialGC gc.stress.systemgc.TestSystemGCWithSerial 270
+ */
 public class TestSystemGCWithSerial {
     public static void main(String[] args) throws Exception {
         TestSystemGC.main(args);
     }
 }
diff a/test/hotspot/jtreg/gc/stress/systemgc/TestSystemGCWithShenandoah.java b/test/hotspot/jtreg/gc/stress/systemgc/TestSystemGCWithShenandoah.java
--- a/test/hotspot/jtreg/gc/stress/systemgc/TestSystemGCWithShenandoah.java
+++ b/test/hotspot/jtreg/gc/stress/systemgc/TestSystemGCWithShenandoah.java
@@ -38,10 +38,26 @@
  *
  * @run main/othervm/timeout=300 -Xlog:gc*=info -Xmx512m -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions
  *      -XX:+UseShenandoahGC
  *      gc.stress.systemgc.TestSystemGCWithShenandoah 270
  */
+/*
+ * @test id=alt-forwarding
+ * @key stress
+ * @library /
+ * @requires vm.gc.Shenandoah
+ * @summary Stress the Shenandoah GC full GC by allocating objects of different lifetimes concurrently with System.gc().
+ *
+ * @run main/othervm/timeout=300 -Xlog:gc*=info -Xmx512m -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions
+ *      -XX:+UseShenandoahGC -XX:+UseAltGCForwarding
+ *      -XX:+ShenandoahVerify
+ *      gc.stress.systemgc.TestSystemGCWithShenandoah 270
+ *
+ * @run main/othervm/timeout=300 -Xlog:gc*=info -Xmx512m -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions
+ *      -XX:+UseShenandoahGC
+ *      gc.stress.systemgc.TestSystemGCWithShenandoah 270
+ */
 
 /*
  * @test id=iu
  * @key stress
  * @library /
diff a/test/hotspot/jtreg/gtest/ArrayTests.java b/test/hotspot/jtreg/gtest/ArrayTests.java
--- /dev/null
+++ b/test/hotspot/jtreg/gtest/ArrayTests.java
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ *
+ */
+
+/*
+ * This tests object array sizes by running gtests with different settings.
+ */
+
+/* @test id=with-coops-with-ccp
+ * @summary Run object array size tests with compressed oops and compressed class pointers
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.xml
+ * @run main/native GTestWrapper --gtest_filter=arrayOop -XX:+UseCompressedClassPointers -XX:+UseCompressedOops
+ */
+/* @test id=with-coops-no-ccp
+ * @summary Run object array size tests with compressed oops and compressed class pointers
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.xml
+ * @run main/native GTestWrapper --gtest_filter=arrayOop -XX:-UseCompressedClassPointers -XX:+UseCompressedOops
+ */
+/* @test id=no-coops-with-ccp
+ * @summary Run object array size tests with compressed oops and compressed class pointers
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.xml
+ * @run main/native GTestWrapper --gtest_filter=arrayOop -XX:+UseCompressedClassPointers -XX:-UseCompressedOops
+ */
+/* @test id=no-coops-no-ccp
+ * @summary Run object array size tests with compressed oops and compressed class pointers
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.xml
+ * @run main/native GTestWrapper --gtest_filter=arrayOop -XX:-UseCompressedClassPointers -XX:-UseCompressedOops
+ */
diff a/test/hotspot/jtreg/gtest/LockStackGtests.java b/test/hotspot/jtreg/gtest/LockStackGtests.java
--- /dev/null
+++ b/test/hotspot/jtreg/gtest/LockStackGtests.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * 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
+ * @summary Run LockStack gtests with LockingMode=2
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.xml
+ * @requires vm.flagless
+ * @run main/native GTestWrapper --gtest_filter=LockStackTest* -XX:+UnlockExperimentalVMOptions -XX:LockingMode=2
+ */
diff a/test/hotspot/jtreg/gtest/ObjArrayTests.java b/test/hotspot/jtreg/gtest/ObjArrayTests.java
--- /dev/null
+++ b/test/hotspot/jtreg/gtest/ObjArrayTests.java
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ *
+ */
+
+/*
+ * This tests object array sizes by running gtests with different settings.
+ */
+
+/* @test id=with-coops-with-ccp
+ * @summary Run object array size tests with compressed oops and compressed class pointers
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.xml
+ * @run main/native GTestWrapper --gtest_filter=objArrayOop -XX:+UseCompressedClassPointers -XX:+UseCompressedOops
+ */
+/* @test id=with-coops-no-ccp
+ * @summary Run object array size tests with compressed oops and compressed class pointers
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.xml
+ * @run main/native GTestWrapper --gtest_filter=objArrayOop -XX:-UseCompressedClassPointers -XX:+UseCompressedOops
+ */
+/* @test id=no-coops-with-ccp
+ * @summary Run object array size tests with compressed oops and compressed class pointers
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.xml
+ * @run main/native GTestWrapper --gtest_filter=objArrayOop -XX:+UseCompressedClassPointers -XX:-UseCompressedOops
+ */
+/* @test id=no-coops-no-ccp
+ * @summary Run object array size tests with compressed oops and compressed class pointers
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.xml
+ * @run main/native GTestWrapper --gtest_filter=objArrayOop -XX:-UseCompressedClassPointers -XX:-UseCompressedOops
+ */
+
+/* @test id=with-coops-with-ccp-large-align
+ * @summary Run object array size tests with compressed oops and compressed class pointers
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.xml
+ * @run main/native GTestWrapper --gtest_filter=objArrayOop -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:ObjAlignmentInBytes=256
+ */
+/* @test id=with-coops-no-ccp-large-align
+ * @summary Run object array size tests with compressed oops and compressed class pointers
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.xml
+ * @run main/native GTestWrapper --gtest_filter=objArrayOop -XX:-UseCompressedClassPointers -XX:+UseCompressedOops -XX:ObjAlignmentInBytes=256
+ */
+/* @test id=no-coops-with-ccp-large-align
+ * @summary Run object array size tests with compressed oops and compressed class pointers
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.xml
+ * @run main/native GTestWrapper --gtest_filter=objArrayOop -XX:+UseCompressedClassPointers -XX:-UseCompressedOops -XX:ObjAlignmentInBytes=256
+ */
+/* @test id=no-coops-no-ccp-large-align
+ * @summary Run object array size tests with compressed oops and compressed class pointers
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ *          java.xml
+ * @run main/native GTestWrapper --gtest_filter=objArrayOop -XX:-UseCompressedClassPointers -XX:-UseCompressedOops -XX:ObjAlignmentInBytes=256
+ */
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,130 @@
+/*
+ * 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=default
+ * @library /test/lib /
+ * @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
+ */
+/*
+ * @test id=no-coops
+ * @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:-UseCompressedOops BaseOffsets
+ */
+/*
+ * @test id=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:-UseCompressedClassPointers BaseOffsets
+ */
+/*
+ * @test id=no-compact-headers
+ * @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:+UseCompactObjectHeaders 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()) {
+            INT_OFFSET = 8;
+            INT_ARRAY_OFFSET = 12;
+            LONG_ARRAY_OFFSET = 16;
+        } else if (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/CdsDifferentCompactObjectHeaders.java b/test/hotspot/jtreg/runtime/cds/CdsDifferentCompactObjectHeaders.java
--- /dev/null
+++ b/test/hotspot/jtreg/runtime/cds/CdsDifferentCompactObjectHeaders.java
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+/**
+ * @test CdsDifferentCompactObjectHeaders
+ * @summary Testing CDS (class data sharing) using opposite compact object header settings.
+ *          Using different compact bject headers setting for each dump/load pair.
+ *          This is a negative test; using compact header setting for loading that
+ *          is different from compact headers for creating a CDS file
+ *          should fail when loading.
+ * @requires vm.cds
+ * @requires vm.bits == 64
+ * @library /test/lib
+ * @run driver CdsDifferentCompactObjectHeaders
+ */
+
+import jdk.test.lib.cds.CDSTestUtils;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.Platform;
+
+public class CdsDifferentCompactObjectHeaders {
+
+    public static void main(String[] args) throws Exception {
+        createAndLoadSharedArchive(true, false);
+        createAndLoadSharedArchive(false, true);
+    }
+
+    // Parameters are object alignment expressed in bytes
+    private static void
+    createAndLoadSharedArchive(boolean createCompactHeaders, boolean loadCompactHeaders)
+    throws Exception {
+        String createCompactHeadersArg = "-XX:" + (createCompactHeaders ? "+" : "-") + "UseCompactObjectHeaders";
+        String loadCompactHeadersArg   = "-XX:" + (loadCompactHeaders   ? "+" : "-") + "UseCompactObjectHeaders";
+        String expectedErrorMsg =
+            String.format(
+            "The shared archive file's UseCompactObjectHeaders setting (%s)" +
+            " does not equal the current UseCompactObjectHeaders setting (%s)",
+            createCompactHeaders ? "enabled" : "disabled",
+            loadCompactHeaders   ? "enabled" : "disabled");
+
+        CDSTestUtils.createArchiveAndCheck("-XX:+UnlockExperimentalVMOptions", createCompactHeadersArg);
+
+        OutputAnalyzer out = CDSTestUtils.runWithArchive("-Xlog:cds", "-XX:+UnlockExperimentalVMOptions", loadCompactHeadersArg);
+        CDSTestUtils.checkExecExpectError(out, 1, expectedErrorMsg);
+    }
+}
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);
@@ -105,10 +112,12 @@
          out = TestCommon
                    .exec(helloJar,
                          "-XX:+UseSerialGC",
                          "-XX:-UseCompressedOops",
                          "-XX:+UseCompressedClassPointers",
+                         "-XX:+UnlockExperimentalVMOptions",
+                         compactHeaders,
                          "-Xlog:cds",
                          "Hello");
          out.shouldContain(HELLO);
          out.shouldHaveExitValue(0);
 
@@ -128,10 +137,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 +152,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/dynamicArchive/TestAutoCreateSharedArchiveNoDefaultArchive.java b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestAutoCreateSharedArchiveNoDefaultArchive.java
--- a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestAutoCreateSharedArchiveNoDefaultArchive.java
+++ b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestAutoCreateSharedArchiveNoDefaultArchive.java
@@ -142,10 +142,11 @@
     }
 
     private static void removeDefaultArchives(String java_home_dst, String variant) {
         removeDefaultArchive(java_home_dst, variant, "");
         removeDefaultArchive(java_home_dst, variant, "_nocoops");
+        removeDefaultArchive(java_home_dst, variant, "_coh");
     }
 
     private static void removeDefaultArchive(String java_home_dst, String variant, String suffix) {
         String fileName = java_home_dst + File.separator + "lib" + File.separator + variant +
                           File.separator +  "classes" + suffix + ".jsa";
diff a/test/hotspot/jtreg/runtime/lockStack/TestLockStackCapacity.java b/test/hotspot/jtreg/runtime/lockStack/TestLockStackCapacity.java
--- /dev/null
+++ b/test/hotspot/jtreg/runtime/lockStack/TestLockStackCapacity.java
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ *
+ */
+
+/*
+ * @test TestLockStackCapacity
+ * @summary Tests the interaction between recursive lightweight locking and
+ *          when the lock stack capacity is exceeded.
+ * @requires vm.flagless
+ * @library /testlibrary /test/lib
+ * @build jdk.test.whitebox.WhiteBox
+ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
+ * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xint -XX:LockingMode=2 TestLockStackCapacity
+ */
+
+import jdk.test.lib.Asserts;
+import jdk.test.whitebox.WhiteBox;
+import jtreg.SkippedException;
+
+public class TestLockStackCapacity {
+    static final WhiteBox WB = WhiteBox.getWhiteBox();
+    static final int LockingMode = WB.getIntVMFlag("LockingMode").intValue();
+    static final int LM_LIGHTWEIGHT = 2;
+
+    static class SynchronizedObject {
+        static final SynchronizedObject OUTER = new SynchronizedObject();
+        static final SynchronizedObject INNER = new SynchronizedObject();
+        static final int LockStackCapacity = WB.getLockStackCapacity();
+
+        synchronized void runInner(int depth) {
+            assertNotInflated();
+            if (depth == 1) {
+                return;
+            } else {
+                runInner(depth - 1);
+            }
+            assertNotInflated();
+        }
+
+        synchronized void runOuter(int depth, SynchronizedObject inner) {
+            assertNotInflated();
+            if (depth == 1) {
+                inner.runInner(LockStackCapacity);
+            } else {
+                runOuter(depth - 1, inner);
+            }
+            assertInflated();
+        }
+
+        public static void runTest() {
+            // Test Requires a capacity of at least 2.
+            Asserts.assertGTE(LockStackCapacity, 2);
+
+            // Just checking
+            OUTER.assertNotInflated();
+            INNER.assertNotInflated();
+
+            synchronized(OUTER) {
+                OUTER.assertNotInflated();
+                INNER.assertNotInflated();
+                OUTER.runOuter(LockStackCapacity - 1, INNER);
+
+                OUTER.assertInflated();
+                INNER.assertNotInflated();
+            }
+        }
+
+        void assertNotInflated() {
+            Asserts.assertFalse(WB.isMonitorInflated(this));
+        }
+
+        void assertInflated() {
+            Asserts.assertTrue(WB.isMonitorInflated(this));
+        }
+    }
+
+    public static void main(String... args) throws Exception {
+        if (LockingMode != LM_LIGHTWEIGHT) {
+            throw new SkippedException("Test only valid for LM_LIGHTWEIGHT");
+        }
+
+        if (!WB.supportsRecursiveLightweightLocking()) {
+            throw new SkippedException("Test only valid if LM_LIGHTWEIGHT supports recursion");
+        }
+
+        SynchronizedObject.runTest();
+    }
+}
diff a/test/jdk/com/sun/jdi/EATests.java b/test/jdk/com/sun/jdi/EATests.java
--- a/test/jdk/com/sun/jdi/EATests.java
+++ b/test/jdk/com/sun/jdi/EATests.java
@@ -118,11 +118,50 @@
  *                 -XX:+WhiteBoxAPI
  *                 -Xbatch
  *                 -XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks
  *                 -XX:+IgnoreUnrecognizedVMOptions -XX:+DeoptimizeObjectsALot
  *
+ * @bug 8324881
+ * @comment Regression test for using the wrong thread when logging during re-locking from deoptimization.
+ *
+ * @comment DiagnoseSyncOnValueBasedClasses=2 will cause logging when locking on \@ValueBased objects.
+ * @run driver EATests
+ *                 -XX:+UnlockDiagnosticVMOptions
+ *                 -Xms256m -Xmx256m
+ *                 -Xbootclasspath/a:.
+ *                 -XX:CompileCommand=dontinline,*::dontinline_*
+ *                 -XX:+WhiteBoxAPI
+ *                 -Xbatch
+ *                 -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks
+ *                 -XX:+UnlockExperimentalVMOptions -XX:LockingMode=1
+ *                 -XX:DiagnoseSyncOnValueBasedClasses=2
+ *
+ * @comment Re-lock may inflate monitors when re-locking, which cause monitorinflation trace logging.
+ * @run driver EATests
+ *                 -XX:+UnlockDiagnosticVMOptions
+ *                 -Xms256m -Xmx256m
+ *                 -Xbootclasspath/a:.
+ *                 -XX:CompileCommand=dontinline,*::dontinline_*
+ *                 -XX:+WhiteBoxAPI
+ *                 -Xbatch
+ *                 -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks
+ *                 -XX:+UnlockExperimentalVMOptions -XX:LockingMode=2
+ *                 -Xlog:monitorinflation=trace:file=monitorinflation.log
+ *
+ * @comment Re-lock may race with deflation.
+ * @run driver EATests
+ *                 -XX:+UnlockDiagnosticVMOptions
+ *                 -Xms256m -Xmx256m
+ *                 -Xbootclasspath/a:.
+ *                 -XX:CompileCommand=dontinline,*::dontinline_*
+ *                 -XX:+WhiteBoxAPI
+ *                 -Xbatch
+ *                 -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks
+ *                 -XX:+UnlockExperimentalVMOptions -XX:LockingMode=0
+ *                 -XX:GuaranteedAsyncDeflationInterval=1000
  */
+
 /**
  * @test
  * @bug 8227745
  *
  * @summary This is another configuration of EATests.java to test Graal. Some testcases are expected
@@ -251,16 +290,18 @@
         new EARelockingSimpleTarget()                                                       .run();
         new EARelockingSimpleWithAccessInOtherThreadTarget()                                .run();
         new EARelockingRecursiveTarget()                                                    .run();
         new EARelockingNestedInflatedTarget()                                               .run();
         new EARelockingNestedInflated_02Target()                                            .run();
+        new EARelockingNestedInflated_03Target()                                            .run();
         new EARelockingArgEscapeLWLockedInCalleeFrameTarget()                               .run();
         new EARelockingArgEscapeLWLockedInCalleeFrame_2Target()                             .run();
         new EARelockingArgEscapeLWLockedInCalleeFrameNoRecursiveTarget()                    .run();
         new EAGetOwnedMonitorsTarget()                                                      .run();
         new EAEntryCountTarget()                                                            .run();
         new EARelockingObjectCurrentlyWaitingOnTarget()                                     .run();
+        new EARelockingValueBasedTarget()                                                   .run();
 
         // Test cases that require deoptimization even though neither
         // locks nor allocations are eliminated at the point where
         // escape state is changed.
         new EADeoptFrameAfterReadLocalObject_01Target()                                     .run();
@@ -370,16 +411,18 @@
         new EARelockingSimple()                                                       .run(this);
         new EARelockingSimpleWithAccessInOtherThread()                                .run(this);
         new EARelockingRecursive()                                                    .run(this);
         new EARelockingNestedInflated()                                               .run(this);
         new EARelockingNestedInflated_02()                                            .run(this);
+        new EARelockingNestedInflated_03()                                            .run(this);
         new EARelockingArgEscapeLWLockedInCalleeFrame()                               .run(this);
         new EARelockingArgEscapeLWLockedInCalleeFrame_2()                             .run(this);
         new EARelockingArgEscapeLWLockedInCalleeFrameNoRecursive()                    .run(this);
         new EAGetOwnedMonitors()                                                      .run(this);
         new EAEntryCount()                                                            .run(this);
         new EARelockingObjectCurrentlyWaitingOn()                                     .run(this);
+        new EARelockingValueBased()                                                   .run(this);
 
         // Test cases that require deoptimization even though neither
         // locks nor allocations are eliminated at the point where
         // escape state is changed.
         new EADeoptFrameAfterReadLocalObject_01()                                     .run(this);
@@ -1920,10 +1963,98 @@
     }
 }
 
 /////////////////////////////////////////////////////////////////////////////
 
+/**
+ * Like {@link EARelockingNestedInflated_02} with the difference that the
+ * inflation of the lock happens because of contention.
+ */
+class EARelockingNestedInflated_03 extends EATestCaseBaseDebugger {
+
+    public void runTestCase() throws Exception {
+        BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
+        printStack(bpe.thread());
+        @SuppressWarnings("unused")
+        ObjectReference o = getLocalRef(bpe.thread().frame(2), XYVAL_NAME, "l1");
+    }
+}
+
+class EARelockingNestedInflated_03Target extends EATestCaseBaseTarget {
+
+    public XYVal lockInflatedByContention;
+    public boolean doLockNow;
+    public EATestCaseBaseTarget testCase;
+
+    @Override
+    public void setUp() {
+        super.setUp();
+        testMethodDepth = 2;
+        lockInflatedByContention = new XYVal(1, 1);
+        testCase = this;
+    }
+
+    @Override
+    public void warmupDone() {
+        super.warmupDone();
+        // Use new lock. lockInflatedByContention might have been inflated because of recursion.
+        lockInflatedByContention = new XYVal(1, 1);
+        // Start thread that tries to enter lockInflatedByContention while the main thread owns it -> inflation
+        TestScaffold.newThread(() -> {
+            while (true) {
+                synchronized (testCase) {
+                    try {
+                        if (doLockNow) {
+                            doLockNow = false; // reset for main thread
+                            testCase.notify();
+                            break;
+                        }
+                        testCase.wait();
+                    } catch (InterruptedException e) { /* ignored */ }
+                }
+            }
+            synchronized (lockInflatedByContention) { // will block and trigger inflation
+                msg(Thread.currentThread().getName() + ": acquired lockInflatedByContention");
+            }
+            }, testCaseName + ": Lock Contender (test thread)").start();
+    }
+
+    public void dontinline_testMethod() {
+        @SuppressWarnings("unused")
+        XYVal xy = new XYVal(1, 1);            // scalar replaced
+        XYVal l1 = lockInflatedByContention;   // read by debugger
+        synchronized (l1) {
+            testMethod_inlined(l1);
+        }
+    }
+
+    public void testMethod_inlined(XYVal l2) {
+        synchronized (l2) {                 // eliminated nested locking
+            dontinline_notifyOtherThread();
+            dontinline_brkpt();
+        }
+    }
+
+    public void dontinline_notifyOtherThread() {
+        if (!warmupDone) {
+            return;
+        }
+        synchronized (testCase) {
+            doLockNow = true;
+            testCase.notify();
+            // wait for other thread to reset doLockNow again
+            while (doLockNow) {
+                try {
+                    testCase.wait();
+                } catch (InterruptedException e) { /* ignored */ }
+            }
+        }
+    }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
 /**
  * Checks if an eliminated lock of an ArgEscape object l1 can be relocked if
  * l1 is locked in a callee frame.
  */
 class EARelockingArgEscapeLWLockedInCalleeFrame extends EATestCaseBaseDebugger {
@@ -2135,10 +2266,36 @@
             l2.wait();
         }
     }
 }
 
+
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Test relocking eliminated @ValueBased object.
+ */
+class EARelockingValueBased extends EATestCaseBaseDebugger {
+
+    public void runTestCase() throws Exception {
+        BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
+        printStack(bpe.thread());
+        @SuppressWarnings("unused")
+        ObjectReference o = getLocalRef(bpe.thread().frame(1), Integer.class.getName(), "l1");
+    }
+}
+
+class EARelockingValueBasedTarget extends EATestCaseBaseTarget {
+
+    public void dontinline_testMethod() {
+        Integer l1 = new Integer(255);
+        synchronized (l1) {
+            dontinline_brkpt();
+        }
+    }
+}
+
 /////////////////////////////////////////////////////////////////////////////
 //
 // Test cases that require deoptimization even though neither locks
 // nor allocations are eliminated at the point where escape state is changed.
 //
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());
@@ -311,10 +312,13 @@
 
     // These should overflow 4G size boundary
     static final int LARGE_INT_ARRAY_SIZE = 1024*1024*1024 + 1024;
     static final int LARGE_OBJ_ARRAY_SIZE = (4096/(int)REF_SIZE)*1024*1024 + 1024;
 
+    static final boolean CCP = WhiteBox.getWhiteBox().getBooleanVMFlag("UseCompressedClassPointers");
+    static final int ARRAY_HEADER_SIZE = CCP ? 16 : (Platform.is64bit() ? 20 : 16);
+
     final String mode;
 
     public GetObjectSizeIntrinsicsTest(String name, String mode) {
         super(name);
         this.mode = mode;
@@ -369,93 +373,103 @@
 
     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));
         }
     }
 
     private void testSize_newSmallIntArray() {
-        long expected = roundUp(4L*SMALL_ARRAY_SIZE + 16, OBJ_ALIGN);
+        long expected = roundUp(4L*SMALL_ARRAY_SIZE + ARRAY_HEADER_SIZE, OBJ_ALIGN);
         for (int c = 0; c < ITERS; c++) {
             assertEquals(expected, fInst.getObjectSize(new int[SMALL_ARRAY_SIZE]));
         }
     }
 
     private void testSize_localSmallIntArray() {
         int[] arr = new int[SMALL_ARRAY_SIZE];
-        long expected = roundUp(4L*SMALL_ARRAY_SIZE + 16, OBJ_ALIGN);
+        long expected = roundUp(4L*SMALL_ARRAY_SIZE + ARRAY_HEADER_SIZE, OBJ_ALIGN);
         for (int c = 0; c < ITERS; c++) {
             assertEquals(expected, fInst.getObjectSize(arr));
         }
     }
 
     static int[] smallArr = new int[SMALL_ARRAY_SIZE];
 
     private void testSize_fieldSmallIntArray() {
-        long expected = roundUp(4L*SMALL_ARRAY_SIZE + 16, OBJ_ALIGN);
+        long expected = roundUp(4L*SMALL_ARRAY_SIZE + ARRAY_HEADER_SIZE, OBJ_ALIGN);
         for (int c = 0; c < ITERS; c++) {
             assertEquals(expected, fInst.getObjectSize(smallArr));
         }
     }
 
     private void testSize_newSmallObjArray() {
-        long expected = roundUp(REF_SIZE*SMALL_ARRAY_SIZE + 16, OBJ_ALIGN);
+        long expected = roundUp(REF_SIZE*SMALL_ARRAY_SIZE + ARRAY_HEADER_SIZE, OBJ_ALIGN);
         for (int c = 0; c < ITERS; c++) {
             assertEquals(expected, fInst.getObjectSize(new Object[SMALL_ARRAY_SIZE]));
         }
     }
 
     private void testSize_localSmallObjArray() {
         Object[] arr = new Object[SMALL_ARRAY_SIZE];
-        long expected = roundUp(REF_SIZE*SMALL_ARRAY_SIZE + 16, OBJ_ALIGN);
+        long expected = roundUp(REF_SIZE*SMALL_ARRAY_SIZE + ARRAY_HEADER_SIZE, OBJ_ALIGN);
         for (int c = 0; c < ITERS; c++) {
             assertEquals(expected, fInst.getObjectSize(arr));
         }
     }
 
     static Object[] smallObjArr = new Object[SMALL_ARRAY_SIZE];
 
     private void testSize_fieldSmallObjArray() {
-        long expected = roundUp(REF_SIZE*SMALL_ARRAY_SIZE + 16, OBJ_ALIGN);
+        long expected = roundUp(REF_SIZE*SMALL_ARRAY_SIZE + ARRAY_HEADER_SIZE, OBJ_ALIGN);
         for (int c = 0; c < ITERS; c++) {
             assertEquals(expected, fInst.getObjectSize(smallObjArr));
         }
     }
 
     private void testSize_localLargeIntArray() {
         int[] arr = new int[LARGE_INT_ARRAY_SIZE];
-        long expected = roundUp(4L*LARGE_INT_ARRAY_SIZE + 16, OBJ_ALIGN);
+        long expected = roundUp(4L*LARGE_INT_ARRAY_SIZE + ARRAY_HEADER_SIZE, OBJ_ALIGN);
         for (int c = 0; c < ITERS; c++) {
             assertEquals(expected, fInst.getObjectSize(arr));
         }
     }
 
     private void testSize_localLargeObjArray() {
         Object[] arr = new Object[LARGE_OBJ_ARRAY_SIZE];
-        long expected = roundUp(REF_SIZE*LARGE_OBJ_ARRAY_SIZE + 16, OBJ_ALIGN);
+        long expected = roundUp(REF_SIZE*LARGE_OBJ_ARRAY_SIZE + ARRAY_HEADER_SIZE, OBJ_ALIGN);
         for (int c = 0; c < ITERS; c++) {
             assertEquals(expected, fInst.getObjectSize(arr));
         }
     }
 
diff a/test/jdk/tools/jlink/plugins/CDSPluginTest.java b/test/jdk/tools/jlink/plugins/CDSPluginTest.java
--- a/test/jdk/tools/jlink/plugins/CDSPluginTest.java
+++ b/test/jdk/tools/jlink/plugins/CDSPluginTest.java
@@ -24,10 +24,11 @@
 import java.io.File;
 
 import jdk.test.lib.JDKToolFinder;
 import jdk.test.lib.Platform;
 import jdk.test.lib.process.*;
+import jdk.test.whitebox.WhiteBox;
 
 import tests.Helper;
 
 import jtreg.SkippedException;
 
@@ -42,11 +43,13 @@
  *          jdk.jlink/jdk.tools.jlink.internal
  *          jdk.jlink/jdk.tools.jmod
  *          jdk.jlink/jdk.tools.jimage
  *          jdk.compiler
  * @build tests.*
- * @run main CDSPluginTest
+ * @build jdk.test.whitebox.WhiteBox
+ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
+ * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. CDSPluginTest
  */
 
 public class CDSPluginTest {
 
     public static void main(String[] args) throws Throwable {
@@ -73,14 +76,19 @@
         } else {
             subDir = "lib" + sep;
         }
         subDir += "server" + sep;
 
+        boolean COMPACT_HEADERS =
+                Platform.is64bit() && WhiteBox.getWhiteBox().getBooleanVMFlag("UseCompactObjectHeaders");
+
+        String suffix = COMPACT_HEADERS ? "_coh.jsa" : ".jsa";
+
         if (Platform.isAArch64() || Platform.isX64()) {
             helper.checkImage(image, module, null, null,
-                      new String[] { subDir + "classes.jsa", subDir + "classes_nocoops.jsa" });
+                      new String[] { subDir + "classes" + suffix, subDir + "classes_nocoops" + suffix });
         } else {
             helper.checkImage(image, module, null, null,
-                      new String[] { subDir + "classes.jsa" });
+                      new String[] { subDir + "classes" + suffix });
         }
     }
 }
diff a/test/lib/jdk/test/whitebox/WhiteBox.java b/test/lib/jdk/test/whitebox/WhiteBox.java
--- a/test/lib/jdk/test/whitebox/WhiteBox.java
+++ b/test/lib/jdk/test/whitebox/WhiteBox.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 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.
@@ -117,10 +117,14 @@
   public         boolean isMonitorInflated(Object obj) {
     Objects.requireNonNull(obj);
     return isMonitorInflated0(obj);
   }
 
+  public native int getLockStackCapacity();
+
+  public native boolean supportsRecursiveLightweightLocking();
+
   public native void forceSafepoint();
 
   public native void forceClassLoaderStatsSafepoint();
 
   private native long getConstantPool0(Class<?> aClass);
