diff a/.jcheck/conf b/.jcheck/conf
--- a/.jcheck/conf
+++ b/.jcheck/conf
@@ -1,9 +1,9 @@
 [general]
-project=jdk-updates
+project=lilliput
 jbs=JDK
-version=17.0.11
+version=17
 
 [checks]
 error=author,committer,reviewers,merge,issues,executable,symlink,message,hg-tag,whitespace,problemlists
 
 [repository]
@@ -20,11 +20,11 @@
 
 [checks "merge"]
 message=Merge
 
 [checks "reviewers"]
-reviewers=1
+committers=1
 ignore=duke
 
 [checks "committer"]
 role=committer
 
diff a/make/RunTests.gmk b/make/RunTests.gmk
--- a/make/RunTests.gmk
+++ b/make/RunTests.gmk
@@ -812,10 +812,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/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
@@ -1984,11 +1984,13 @@
 
   if (do_polling() && C->is_method_compilation()) {
     Label dummy_label;
     Label* code_stub = &dummy_label;
     if (!C->output()->in_scratch_emit_size()) {
-      code_stub = &C->output()->safepoint_poll_table()->add_safepoint(__ offset());
+      C2SafepointPollStub* stub = new (C->comp_arena()) C2SafepointPollStub(__ offset());
+      C->output()->add_stub(stub);
+      code_stub = &stub->entry();
     }
     __ relocate(relocInfo::poll_return_type);
     __ safepoint_poll(*code_stub, true /* at_return */, false /* acquire */, true /* in_nmethod */);
   }
 }
@@ -3797,170 +3799,10 @@
     // callee expects it in r3
     __ mov(r3, lr);
     __ br(target_reg);
   %}
 
-  enc_class aarch64_enc_fast_lock(iRegP object, iRegP box, iRegP tmp, iRegP tmp2) %{
-    C2_MacroAssembler _masm(&cbuf);
-    Register oop = as_Register($object$$reg);
-    Register box = as_Register($box$$reg);
-    Register disp_hdr = as_Register($tmp$$reg);
-    Register tmp = as_Register($tmp2$$reg);
-    Label cont;
-    Label object_has_monitor;
-    Label cas_failed;
-
-    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()));
-
-    if (DiagnoseSyncOnValueBasedClasses != 0) {
-      __ load_klass(tmp, oop);
-      __ ldrw(tmp, Address(tmp, Klass::access_flags_offset()));
-      __ tstw(tmp, JVM_ACC_IS_VALUE_BASED_CLASS);
-      __ br(Assembler::NE, cont);
-    }
-
-    if (UseBiasedLocking && !UseOptoBiasInlining) {
-      __ biased_locking_enter(box, oop, disp_hdr, tmp, true, cont);
-    }
-
-    // Check for existing monitor
-    __ tbnz(disp_hdr, exact_log2(markWord::monitor_value), object_has_monitor);
-
-    // 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()));
-
-    // 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(oop, tmp, box, Assembler::xword, /*acquire*/ true,
-               /*release*/ true, /*weak*/ false, disp_hdr);
-    __ br(Assembler::EQ, cont);
-
-    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
-
-    __ bind(cas_failed);
-    // 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.
-    __ mov(rscratch1, sp);
-    __ sub(disp_hdr, disp_hdr, rscratch1);
-    __ mov(tmp, (address) (~(os::vm_page_size()-1) | markWord::lock_mask_in_place));
-    // 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);
-
-    // 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, (ObjectMonitor::owner_offset_in_bytes()-markWord::monitor_value));
-    __ cmpxchg(tmp, zr, rthread, Assembler::xword, /*acquire*/ true,
-               /*release*/ true, /*weak*/ false, rscratch1); // Sets flags for result
-
-    // 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
-
-    // Recursive lock case
-    __ increment(Address(disp_hdr, ObjectMonitor::recursions_offset_in_bytes() - markWord::monitor_value), 1);
-    // flag == EQ still from the cmp above, checking if this is a reentrant lock
-
-    __ bind(cont);
-    // flag == EQ indicates success
-    // flag == NE indicates failure
-  %}
-
-  enc_class aarch64_enc_fast_unlock(iRegP object, iRegP box, iRegP tmp, iRegP tmp2) %{
-    C2_MacroAssembler _masm(&cbuf);
-    Register oop = as_Register($object$$reg);
-    Register box = as_Register($box$$reg);
-    Register disp_hdr = as_Register($tmp$$reg);
-    Register tmp = as_Register($tmp2$$reg);
-    Label cont;
-    Label object_has_monitor;
-
-    assert_different_registers(oop, box, tmp, disp_hdr);
-
-    if (UseBiasedLocking && !UseOptoBiasInlining) {
-      __ biased_locking_exit(oop, tmp, cont);
-    }
-
-    // Find the lock address and load the displaced header from the stack.
-    __ ldr(disp_hdr, Address(box, BasicLock::displaced_header_offset_in_bytes()));
-
-    // If the displaced header is 0, we have a recursive unlock.
-    __ cmp(disp_hdr, zr);
-    __ br(Assembler::EQ, cont);
-
-    // Handle existing monitor.
-    __ ldr(tmp, Address(oop, oopDesc::mark_offset_in_bytes()));
-    __ tbnz(tmp, exact_log2(markWord::monitor_value), object_has_monitor);
-
-    // 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);
-
-    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
-    __ ldr(disp_hdr, Address(tmp, ObjectMonitor::recursions_offset_in_bytes()));
-
-    Label notRecursive;
-    __ cbz(disp_hdr, notRecursive);
-
-    // Recursive lock
-    __ sub(disp_hdr, disp_hdr, 1u);
-    __ str(disp_hdr, Address(tmp, ObjectMonitor::recursions_offset_in_bytes()));
-    __ cmp(disp_hdr, disp_hdr); // Sets flags for result
-    __ b(cont);
-
-    __ bind(notRecursive);
-    __ ldr(rscratch1, Address(tmp, ObjectMonitor::EntryList_offset_in_bytes()));
-    __ ldr(disp_hdr, Address(tmp, ObjectMonitor::cxq_offset_in_bytes()));
-    __ orr(rscratch1, rscratch1, disp_hdr); // Will be 0 if both are 0.
-    __ cmp(rscratch1, zr); // Sets flags for result
-    __ cbnz(rscratch1, cont);
-    // need a release store here
-    __ lea(tmp, Address(tmp, ObjectMonitor::owner_offset_in_bytes()));
-    __ stlr(zr, tmp); // set unowned
-
-    __ bind(cont);
-    // flag == EQ indicates success
-    // flag == NE indicates failure
-  %}
-
 %}
 
 //----------FRAME--------------------------------------------------------------
 // Definition of frame structure and management information.
 //
@@ -7437,20 +7279,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));
@@ -16678,38 +16534,73 @@
 // TODO: fixme
 
 // ============================================================================
 // inlined locking and unlocking
 
-instruct cmpFastLock(rFlagsReg cr, iRegP object, iRegP box, iRegPNoSp tmp, iRegPNoSp tmp2)
+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);
+  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(aarch64_enc_fast_lock(object, box, tmp, tmp2));
+  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" %}
 
-  ins_encode(aarch64_enc_fast_unlock(object, box, tmp, tmp2));
+  ins_encode %{
+    __ fast_unlock($object$$Register, $box$$Register, $tmp$$Register, $tmp2$$Register);
+  %}
 
   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()->
@@ -251,10 +252,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
@@ -436,11 +436,11 @@
   // Preform needed unlocking
   MonitorExitStub* stub = NULL;
   if (method()->is_synchronized()) {
     monitor_address(0, FrameMap::r0_opr);
     stub = new MonitorExitStub(FrameMap::r0_opr, true, 0);
-    __ unlock_object(r5, r4, r0, *stub->entry());
+    __ unlock_object(r5, r4, r0, r6, *stub->entry());
     __ bind(*stub->continuation());
   }
 
   if (compilation()->env()->dtrace_method_probes()) {
     __ mov(c_rarg0, rthread);
@@ -1237,11 +1237,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());
@@ -2354,16 +2354,16 @@
   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);
+        __ load_nklass(tmp, src);
+        __ load_nklass(rscratch1, dst);
         __ cmpw(tmp, rscratch1);
       } else {
-        __ ldr(tmp, src_klass_addr);
-        __ ldr(rscratch1, dst_klass_addr);
+        __ ldr(tmp, Address(src, oopDesc::klass_offset_in_bytes()));
+        __ ldr(rscratch1, Address(dst, oopDesc::klass_offset_in_bytes()));
         __ cmp(tmp, rscratch1);
       }
       __ br(Assembler::NE, *stub->entry());
     } else {
       // For object arrays, if src is a sub class of dst then we can
@@ -2483,40 +2483,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);
@@ -2560,27 +2538,28 @@
 
 void LIR_Assembler::emit_lock(LIR_OpLock* op) {
   Register obj = op->obj_opr()->as_register();  // may not be an oop
   Register hdr = op->hdr_opr()->as_register();
   Register lock = op->lock_opr()->as_register();
-  if (!UseFastLocking) {
+  Register temp = op->scratch_opr()->as_register();
+  if (LockingMode == LM_MONITOR) {
+    if (op->info() != NULL) {
+      add_debug_info_for_null_check_here(op->info());
+      __ null_check(obj, -1);
+    }
     __ b(*op->stub()->entry());
   } else if (op->code() == lir_lock) {
-    Register scratch = noreg;
-    if (UseBiasedLocking) {
-      scratch = op->scratch_opr()->as_register();
-    }
     assert(BasicLock::displaced_header_offset_in_bytes() == 0, "lock_reg must point to the displaced header");
     // add debug info for NullPointerException only if one is possible
-    int null_check_offset = __ lock_object(hdr, obj, lock, scratch, *op->stub()->entry());
+    int null_check_offset = __ lock_object(hdr, obj, lock, temp, *op->stub()->entry());
     if (op->info() != NULL) {
       add_debug_info_for_null_check(null_check_offset, op->info());
     }
     // done
   } else if (op->code() == lir_unlock) {
     assert(BasicLock::displaced_header_offset_in_bytes() == 0, "lock_reg must point to the displaced header");
-    __ unlock_object(hdr, obj, lock, *op->stub()->entry());
+    __ unlock_object(hdr, obj, lock, temp, *op->stub()->entry());
   } else {
     Unimplemented();
   }
   __ bind(*op->stub()->continuation());
 }
@@ -2593,11 +2572,22 @@
   if (info != NULL) {
     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_LIRGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRGenerator_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/c1_LIRGenerator_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/c1_LIRGenerator_aarch64.cpp
@@ -312,15 +312,11 @@
 
   set_no_result(x);
 
   // "lock" stores the address of the monitor stack slot, so this is not an oop
   LIR_Opr lock = new_register(T_INT);
-  // Need a scratch register for biased locking
-  LIR_Opr scratch = LIR_OprFact::illegalOpr;
-  if (UseBiasedLocking) {
-    scratch = new_register(T_INT);
-  }
+  LIR_Opr scratch = new_register(T_INT);
 
   CodeEmitInfo* info_for_exception = NULL;
   if (x->needs_null_check()) {
     info_for_exception = state_for(x);
   }
@@ -338,12 +334,13 @@
   LIRItem obj(x->obj(), this);
   obj.dont_load_item();
 
   LIR_Opr lock = new_register(T_INT);
   LIR_Opr obj_temp = new_register(T_INT);
+  LIR_Opr scratch = new_register(T_INT);
   set_no_result(x);
-  monitor_exit(obj_temp, lock, syncTempOpr(), LIR_OprFact::illegalOpr, x->monitor_no());
+  monitor_exit(obj_temp, lock, syncTempOpr(), scratch, x->monitor_no());
 }
 
 
 void LIRGenerator::do_NegateOp(NegateOp* x) {
 
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, 2021, 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
@@ -59,14 +59,14 @@
     cset(result, NE);  // Not equal or unordered
     cneg(result, result, LO);  // Less than
   }
 }
 
-int C1_MacroAssembler::lock_object(Register hdr, Register obj, Register disp_hdr, Register scratch, Label& slow_case) {
+int C1_MacroAssembler::lock_object(Register hdr, Register obj, Register disp_hdr, Register temp, Label& slow_case) {
   const int aligned_mask = BytesPerWord -1;
   const int hdr_offset = oopDesc::mark_offset_in_bytes();
-  assert(hdr != obj && hdr != disp_hdr && obj != disp_hdr, "registers must be different");
+  assert_different_registers(hdr, obj, disp_hdr, temp, rscratch2);
   Label done;
   int null_check_offset = -1;
 
   verify_oop(obj);
 
@@ -81,92 +81,104 @@
     tstw(hdr, JVM_ACC_IS_VALUE_BASED_CLASS);
     br(Assembler::NE, slow_case);
   }
 
   if (UseBiasedLocking) {
-    assert(scratch != noreg, "should have scratch register at this point");
-    biased_locking_enter(disp_hdr, obj, hdr, scratch, false, done, &slow_case);
+    assert(temp != noreg, "should have temp register at this point");
+    biased_locking_enter(disp_hdr, obj, hdr, temp, false, done, &slow_case);
   }
 
-  // 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
-  // displaced header address in the object header - if it is not the same, get the
-  // object header instead
-  lea(rscratch2, Address(obj, hdr_offset));
-  cmpxchgptr(hdr, disp_hdr, rscratch2, rscratch1, done, /*fallthough*/NULL);
-  // if the object header was the same, we're done
-  // if the object header was not the same, it is now in the hdr register
-  // => test if it is a stack pointer into the same stack (recursive locking), i.e.:
-  //
-  // 1) (hdr & aligned_mask) == 0
-  // 2) sp <= hdr
-  // 3) hdr <= sp + page_size
-  //
-  // these 3 tests can be done by evaluating the following expression:
-  //
-  // (hdr - sp) & (aligned_mask - page_size)
-  //
-  // assuming both the stack pointer and page_size have their least
-  // significant 2 bits cleared and page_size is a power of 2
-  mov(rscratch1, sp);
-  sub(hdr, hdr, rscratch1);
-  ands(hdr, hdr, aligned_mask - os::vm_page_size());
-  // for recursive locking, the result is zero => save it in the displaced header
-  // location (NULL in the displaced hdr location indicates recursive locking)
-  str(hdr, Address(disp_hdr, 0));
-  // otherwise we don't care about the result and handle locking via runtime call
-  cbnz(hdr, slow_case);
-  // done
-  bind(done);
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    lightweight_lock(obj, hdr, temp, rscratch2, slow_case);
+  } else {
+    // 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
+    // displaced header address in the object header - if it is not the same, get the
+    // object header instead
+    lea(rscratch2, Address(obj, hdr_offset));
+    cmpxchgptr(hdr, disp_hdr, rscratch2, rscratch1, done, /*fallthough*/NULL);
+    // if the object header was the same, we're done
+    // if the object header was not the same, it is now in the hdr register
+    // => test if it is a stack pointer into the same stack (recursive locking), i.e.:
+    //
+    // 1) (hdr & aligned_mask) == 0
+    // 2) sp <= hdr
+    // 3) hdr <= sp + page_size
+    //
+    // these 3 tests can be done by evaluating the following expression:
+    //
+    // (hdr - sp) & (aligned_mask - page_size)
+    //
+    // assuming both the stack pointer and page_size have their least
+    // significant 2 bits cleared and page_size is a power of 2
+    mov(rscratch1, sp);
+    sub(hdr, hdr, rscratch1);
+    ands(hdr, hdr, aligned_mask - os::vm_page_size());
+    // for recursive locking, the result is zero => save it in the displaced header
+    // location (NULL in the displaced hdr location indicates recursive locking)
+    str(hdr, Address(disp_hdr, 0));
+    // otherwise we don't care about the result and handle locking via runtime call
+    cbnz(hdr, slow_case);
+    // done
+    bind(done);
+  }
   if (PrintBiasedLockingStatistics) {
     lea(rscratch2, ExternalAddress((address)BiasedLocking::fast_path_entry_count_addr()));
     addmw(Address(rscratch2, 0), 1, rscratch1);
   }
   return null_check_offset;
 }
 
 
-void C1_MacroAssembler::unlock_object(Register hdr, Register obj, Register disp_hdr, Label& slow_case) {
+void C1_MacroAssembler::unlock_object(Register hdr, Register obj, Register disp_hdr, Register temp, Label& slow_case) {
   const int aligned_mask = BytesPerWord -1;
   const int hdr_offset = oopDesc::mark_offset_in_bytes();
-  assert(hdr != obj && hdr != disp_hdr && obj != disp_hdr, "registers must be different");
+  assert_different_registers(hdr, obj, disp_hdr, temp, rscratch2);
   Label done;
 
   if (UseBiasedLocking) {
     // load object
     ldr(obj, Address(disp_hdr, BasicObjectLock::obj_offset_in_bytes()));
     biased_locking_exit(obj, hdr, done);
   }
 
-  // load displaced header
-  ldr(hdr, Address(disp_hdr, 0));
-  // if the loaded hdr is NULL we had recursive locking
-  // if we had recursive locking, we are done
-  cbz(hdr, done);
+  if (LockingMode != LM_LIGHTWEIGHT) {
+    // load displaced header
+    ldr(hdr, Address(disp_hdr, 0));
+    // if the loaded hdr is null we had recursive locking
+    // if we had recursive locking, we are done
+    cbz(hdr, done);
+  }
+
   if (!UseBiasedLocking) {
     // load object
     ldr(obj, Address(disp_hdr, BasicObjectLock::obj_offset_in_bytes()));
   }
   verify_oop(obj);
-  // 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
-  // if the object header was not pointing to the displaced header,
-  // we do unlocking via runtime call
-  if (hdr_offset) {
-    lea(rscratch1, Address(obj, hdr_offset));
-    cmpxchgptr(disp_hdr, hdr, rscratch1, rscratch2, done, &slow_case);
+
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    lightweight_unlock(obj, hdr, temp, rscratch2, slow_case);
   } else {
-    cmpxchgptr(disp_hdr, hdr, obj, rscratch2, done, &slow_case);
+    // 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
+    // if the object header was not pointing to the displaced header,
+    // we do unlocking via runtime call
+    if (hdr_offset) {
+      lea(rscratch1, Address(obj, hdr_offset));
+      cmpxchgptr(disp_hdr, hdr, rscratch1, rscratch2, done, &slow_case);
+    } else {
+      cmpxchgptr(disp_hdr, hdr, obj, rscratch2, done, &slow_case);
+    }
+    // done
+    bind(done);
   }
-  // done
-  bind(done);
 }
 
 
 // Defines obj, preserves var_size_in_bytes
 void C1_MacroAssembler::try_allocate(Register obj, Register var_size_in_bytes, int con_size_in_bytes, Register t1, Register t2, Label& slow_case) {
@@ -177,29 +189,31 @@
   }
 }
 
 void C1_MacroAssembler::initialize_header(Register obj, Register klass, Register len, Register t1, Register t2) {
   assert_different_registers(obj, klass, len);
-  if (UseBiasedLocking && !len->is_valid()) {
+  if (UseCompactObjectHeaders || (UseBiasedLocking && !len->is_valid())) {
     assert_different_registers(obj, klass, len, t1, t2);
     ldr(t1, Address(klass, Klass::prototype_header_offset()));
   } else {
     // 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 (!UseCompactObjectHeaders) {
+    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
@@ -214,10 +228,16 @@
 
   // len_in_bytes is positive and ptr sized
   subs(len_in_bytes, len_in_bytes, hdr_size_in_bytes);
   br(Assembler::EQ, done);
 
+  // Zero first 4 bytes, if start offset is not word aligned.
+  if (!is_aligned(hdr_size_in_bytes, BytesPerWord)) {
+    strw(zr, Address(obj, hdr_size_in_bytes));
+    hdr_size_in_bytes += BytesPerInt;
+  }
+
   // zero_words() takes ptr in r10 and count in words in r11
   mov(rscratch1, len_in_bytes);
   lea(t1, Address(obj, hdr_size_in_bytes));
   lsr(t2, rscratch1, LogBytesPerWord);
   zero_words(t1, t2);
@@ -263,11 +283,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");
 
@@ -276,20 +296,20 @@
   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 rest of allocated space
-  initialize_body(obj, arr_size, header_size * BytesPerWord, t1, t2);
+  initialize_body(obj, arr_size, base_offset_in_bytes, t1, t2);
 
   membar(StoreStore);
 
   if (CURRENT_ENV->dtrace_alloc_probes()) {
     assert(obj == r0, "must be");
@@ -302,12 +322,15 @@
 
 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");
-
+  if (UseCompactObjectHeaders) {
+    assert(!MacroAssembler::needs_explicit_null_check(oopDesc::mark_offset_in_bytes()), "must add explicit null check");
+  } else {
+    assert(!MacroAssembler::needs_explicit_null_check(oopDesc::klass_offset_in_bytes()), "must add explicit null check");
+  }
   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
@@ -56,19 +56,20 @@
 
   // locking
   // hdr     : must be r0, contents destroyed
   // obj     : must point to the object to lock, contents preserved
   // disp_hdr: must point to the displaced header location, contents preserved
-  // scratch : scratch register, contents destroyed
+  // temp    : temporary register, must not be rscratch1 or rscratch2
   // returns code offset at which to add null check debug information
-  int lock_object  (Register swap, Register obj, Register disp_hdr, Register scratch, Label& slow_case);
+  int lock_object  (Register swap, Register obj, Register disp_hdr, Register temp, Label& slow_case);
 
   // unlocking
   // hdr     : contents destroyed
   // obj     : must point to the object to lock, contents preserved
   // disp_hdr: must be r0 & must point to the displaced header location, contents destroyed
-  void unlock_object(Register swap, Register obj, Register lock, Label& slow_case);
+  // temp    : temporary register, must not be rscratch1 or rscratch2
+  void unlock_object(Register swap, Register obj, Register lock, Register temp, Label& slow_case);
 
   void initialize_object(
     Register obj,                      // result: pointer to object after successful allocation
     Register klass,                    // object klass
     Register var_size_in_bytes,        // object size in bytes if unknown at compile time; invalid otherwise
diff a/src/hotspot/cpu/aarch64/c2_CodeStubs_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_CodeStubs_aarch64.cpp
--- /dev/null
+++ b/src/hotspot/cpu/aarch64/c2_CodeStubs_aarch64.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2020, 2022 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 "opto/c2_MacroAssembler.hpp"
+#include "opto/c2_CodeStubs.hpp"
+#include "runtime/objectMonitor.hpp"
+#include "runtime/sharedRuntime.hpp"
+#include "runtime/stubRoutines.hpp"
+
+#define __ masm.
+
+int C2SafepointPollStub::max_size() const {
+  return 20;
+}
+
+void C2SafepointPollStub::emit(C2_MacroAssembler& masm) {
+  assert(SharedRuntime::polling_page_return_handler_blob() != NULL,
+         "polling page return stub not created yet");
+  address stub = SharedRuntime::polling_page_return_handler_blob()->entry_point();
+
+  RuntimeAddress callback_addr(stub);
+
+  __ bind(entry());
+  InternalAddress safepoint_pc(masm.pc() - masm.offset() + _safepoint_offset);
+  __ adr(rscratch1, safepoint_pc);
+  __ str(rscratch1, Address(rthread, JavaThread::saved_exception_pc_offset()));
+  __ far_jump(callback_addr);
+}
+
+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 24;
+}
+
+void C2HandleAnonOMOwnerStub::emit(C2_MacroAssembler& masm) {
+  __ bind(entry());
+  Register mon = monitor();
+  Register t = tmp();
+  assert(t != noreg, "need tmp register");
+
+  // Fix owner to be the current thread.
+  __ str(rthread, Address(mon, ObjectMonitor::owner_offset_in_bytes()));
+
+  // Pop owner object from lock-stack.
+  __ ldrw(t, Address(rthread, JavaThread::lock_stack_top_offset()));
+  __ subw(t, t, oopSize);
+#ifdef ASSERT
+  __ str(zr, Address(rthread, t));
+#endif
+  __ 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
@@ -23,14 +23,18 @@
  */
 
 #include "precompiled.hpp"
 #include "asm/assembler.hpp"
 #include "asm/assembler.inline.hpp"
+#include "opto/c2_CodeStubs.hpp"
 #include "opto/c2_MacroAssembler.hpp"
+#include "opto/compile.hpp"
+#include "opto/output.hpp"
 #include "opto/intrinsicnode.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
@@ -40,10 +44,438 @@
 
 #define BIND(label) bind(label); BLOCK_COMMENT(#label ":")
 
 typedef void (MacroAssembler::* chr_insn)(Register Rt, const Address &adr);
 
+void C2_MacroAssembler::fast_lock(Register objectReg, Register boxReg, Register tmpReg,
+                                  Register tmp2Reg, Register tmp3Reg) {
+  Register oop = objectReg;
+  Register box = boxReg;
+  Register disp_hdr = tmpReg;
+  Register tmp = tmp2Reg;
+  Label cont;
+  Label object_has_monitor;
+  Label cas_failed;
+
+  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()));
+
+  if (DiagnoseSyncOnValueBasedClasses != 0) {
+    load_klass(tmp, oop);
+    ldrw(tmp, Address(tmp, Klass::access_flags_offset()));
+    tstw(tmp, JVM_ACC_IS_VALUE_BASED_CLASS);
+    br(Assembler::NE, cont);
+  }
+
+  if (UseBiasedLocking && !UseOptoBiasInlining) {
+    biased_locking_enter(box, oop, disp_hdr, tmp, true, cont);
+  }
+
+  // Check for existing monitor
+  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 {
+    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()));
+
+    // 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(oop, tmp, box, Assembler::xword, /*acquire*/ true,
+            /*release*/ true, /*weak*/ false, disp_hdr);
+    br(Assembler::EQ, cont);
+
+    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
+
+    bind(cas_failed);
+    // 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.
+    mov(rscratch1, sp);
+    sub(disp_hdr, disp_hdr, rscratch1);
+    mov(tmp, (address) (~(os::vm_page_size()-1) | markWord::lock_mask_in_place));
+    // 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);
+  }
+
+  // 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, (ObjectMonitor::owner_offset_in_bytes()-markWord::monitor_value));
+  cmpxchg(tmp, zr, rthread, Assembler::xword, /*acquire*/ true,
+          /*release*/ true, /*weak*/ false, rscratch1); // Sets flags for result
+
+  // 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
+
+  // Recursive lock case
+  increment(Address(disp_hdr, ObjectMonitor::recursions_offset_in_bytes() - markWord::monitor_value), 1);
+  // flag == EQ still from the cmp above, checking if this is a reentrant lock
+
+  bind(cont);
+  // flag == EQ indicates success
+  // flag == NE indicates failure
+}
+
+void C2_MacroAssembler::fast_unlock(Register objectReg, Register boxReg, Register tmpReg,
+                                    Register tmp2Reg) {
+  Register oop = objectReg;
+  Register box = boxReg;
+  Register disp_hdr = tmpReg;
+  Register tmp = tmp2Reg;
+  Label cont;
+  Label object_has_monitor;
+
+  assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking should use fast_unlock_lightweight");
+  assert_different_registers(oop, box, tmp, disp_hdr);
+
+  if (UseBiasedLocking && !UseOptoBiasInlining) {
+    biased_locking_exit(oop, tmp, cont);
+  }
+
+  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()));
+
+    // If the displaced header is 0, we have a recursive unlock.
+    cmp(disp_hdr, zr);
+    br(Assembler::EQ, cont);
+  }
+
+  // Handle existing monitor.
+  ldr(tmp, Address(oop, oopDesc::mark_offset_in_bytes()));
+  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 {
+    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);
+  }
+
+  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
+
+  ldr(disp_hdr, Address(tmp, ObjectMonitor::recursions_offset_in_bytes()));
+
+  Label notRecursive;
+  cbz(disp_hdr, notRecursive);
+
+  // Recursive lock
+  sub(disp_hdr, disp_hdr, 1u);
+  str(disp_hdr, Address(tmp, ObjectMonitor::recursions_offset_in_bytes()));
+  cmp(disp_hdr, disp_hdr); // Sets flags for result
+  b(cont);
+
+  bind(notRecursive);
+  ldr(rscratch1, Address(tmp, ObjectMonitor::EntryList_offset_in_bytes()));
+  ldr(disp_hdr, Address(tmp, ObjectMonitor::cxq_offset_in_bytes()));
+  orr(rscratch1, rscratch1, disp_hdr); // Will be 0 if both are 0.
+  cmp(rscratch1, zr); // Sets flags for result
+  cbnz(rscratch1, cont);
+  // need a release store here
+  lea(tmp, Address(tmp, ObjectMonitor::owner_offset_in_bytes()));
+  stlr(zr, tmp); // set unowned
+
+  bind(cont);
+  // flag == EQ indicates success
+  // flag == NE indicates failure
+}
+
+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, ObjectMonitor::owner_offset_in_bytes() - 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, ObjectMonitor::recursions_offset_in_bytes() - monitor_tag), 1);
+  }
+
+  bind(locked);
+#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_in_bytes()));
+    cbz(t2_recursions, not_recursive);
+
+    // Recursive unlock.
+    sub(t2_recursions, t2_recursions, 1u);
+    str(t2_recursions, Address(t1_monitor, ObjectMonitor::recursions_offset_in_bytes()));
+    // 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_in_bytes()));
+
+    // Check if the entry lists are empty.
+    ldr(rscratch1, Address(t1_monitor, ObjectMonitor::EntryList_offset_in_bytes()));
+    ldr(t3_t, Address(t1_monitor, ObjectMonitor::cxq_offset_in_bytes()));
+    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);
+#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,
@@ -874,5 +1306,32 @@
         assert(false, "unsupported");
         ShouldNotReachHere();
     }
   }
 }
+
+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, 2021, 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.
@@ -26,10 +26,16 @@
 #define CPU_AARCH64_C2_MACROASSEMBLER_AARCH64_HPP
 
 // C2_MacroAssembler contains high-level macros for C2
 
  public:
+  // Code used by cmpFastLock and cmpFastUnlock mach instructions in .ad file.
+  void fast_lock(Register object, Register box, Register tmp, Register tmp2, Register tmp3);
+  void fast_unlock(Register object, Register box, Register tmp, Register tmp2);
+  // Code used by cmpFastLockLightweight and cmpFastUnlockLightweight mach instructions in .ad file.
+  void fast_lock_lightweight(Register object, Register t1, Register t2, Register t3);
+  void fast_unlock_lightweight(Register object, Register t1, Register t2, Register t3);
 
   void string_compare(Register str1, Register str2,
                       Register cnt1, Register cnt2, Register result,
                       Register tmp1, Register tmp2, FloatRegister vtmp1,
                       FloatRegister vtmp2, FloatRegister vtmp3, int ae);
@@ -51,6 +57,8 @@
 
   // SIMD&FP comparison
   void neon_compare(FloatRegister dst, BasicType bt, FloatRegister src1,
                     FloatRegister src2, int cond, bool isQ);
 
+  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/c2_safepointPollStubTable_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_safepointPollStubTable_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/c2_safepointPollStubTable_aarch64.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (c) 2020, 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 "asm/macroAssembler.hpp"
-#include "opto/compile.hpp"
-#include "opto/node.hpp"
-#include "opto/output.hpp"
-#include "runtime/sharedRuntime.hpp"
-
-#define __ masm.
-void C2SafepointPollStubTable::emit_stub_impl(MacroAssembler& masm, C2SafepointPollStub* entry) const {
-  assert(SharedRuntime::polling_page_return_handler_blob() != NULL,
-         "polling page return stub not created yet");
-  address stub = SharedRuntime::polling_page_return_handler_blob()->entry_point();
-
-  RuntimeAddress callback_addr(stub);
-
-  __ bind(entry->_stub_label);
-  InternalAddress safepoint_pc(masm.pc() - masm.offset() + entry->_safepoint_offset);
-  __ adr(rscratch1, safepoint_pc);
-  __ str(rscratch1, Address(rthread, JavaThread::saved_exception_pc_offset()));
-  __ far_jump(callback_addr);
-}
-#undef __
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, 2021, 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
@@ -719,25 +719,27 @@
 // Args:
 //      c_rarg1: BasicObjectLock to be used for locking
 //
 // Kills:
 //      r0
-//      c_rarg0, c_rarg1, c_rarg2, c_rarg3, .. (param regs)
+//      c_rarg0, c_rarg1, c_rarg2, c_rarg3, c_rarg4, .. (param regs)
 //      rscratch1, rscratch2 (scratch regs)
 void InterpreterMacroAssembler::lock_object(Register lock_reg)
 {
   assert(lock_reg == c_rarg1, "The argument is only for looks. It must be c_rarg1");
-  if (UseHeavyMonitors) {
+  if (LockingMode == LM_MONITOR) {
     call_VM(noreg,
             CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
             lock_reg);
   } else {
     Label done;
 
     const Register swap_reg = r0;
     const Register tmp = c_rarg2;
     const Register obj_reg = c_rarg3; // Will contain the oop
+    const Register tmp2 = c_rarg4;
+    const Register tmp3 = c_rarg5;
 
     const int obj_offset = BasicObjectLock::obj_offset_in_bytes();
     const int lock_offset = BasicObjectLock::lock_offset_in_bytes ();
     const int mark_offset = lock_offset +
                             BasicLock::displaced_header_offset_in_bytes();
@@ -752,86 +754,96 @@
       ldrw(tmp, Address(tmp, Klass::access_flags_offset()));
       tstw(tmp, JVM_ACC_IS_VALUE_BASED_CLASS);
       br(Assembler::NE, slow_case);
     }
 
-    if (UseBiasedLocking) {
-      biased_locking_enter(lock_reg, obj_reg, swap_reg, tmp, false, done, &slow_case);
-    }
-
-    // Load (object->mark() | 1) into swap_reg
-    ldr(rscratch1, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
-    orr(swap_reg, rscratch1, 1);
-
-    // Save (object->mark() | 1) into BasicLock's displaced header
-    str(swap_reg, Address(lock_reg, mark_offset));
-
-    assert(lock_offset == 0,
-           "displached header must be first word in BasicObjectLock");
-
-    Label fail;
-    if (PrintBiasedLockingStatistics) {
-      Label fast;
-      cmpxchg_obj_header(swap_reg, lock_reg, obj_reg, rscratch1, fast, &fail);
-      bind(fast);
-      atomic_incw(Address((address)BiasedLocking::fast_path_entry_count_addr()),
-                  rscratch2, rscratch1, tmp);
+    if (LockingMode == LM_LIGHTWEIGHT) {
+      lightweight_lock(obj_reg, tmp, tmp2, tmp3, slow_case);
       b(done);
-      bind(fail);
     } else {
-      cmpxchg_obj_header(swap_reg, lock_reg, obj_reg, rscratch1, done, /*fallthrough*/NULL);
-    }
+      if (UseBiasedLocking) {
+        biased_locking_enter(lock_reg, obj_reg, swap_reg, tmp, false, done, &slow_case);
+      }
 
-    // Fast check for recursive lock.
-    //
-    // Can apply the optimization only if this is a stack lock
-    // allocated in this thread. For efficiency, we can focus on
-    // recently allocated stack locks (instead of reading the stack
-    // base and checking whether 'mark' points inside the current
-    // thread stack):
-    //  1) (mark & 7) == 0, and
-    //  2) sp <= mark < mark + os::pagesize()
-    //
-    // Warning: sp + os::pagesize can overflow the stack base. We must
-    // neither apply the optimization for an inflated lock allocated
-    // just above the thread stack (this is why condition 1 matters)
-    // nor apply the optimization if the stack lock is inside the stack
-    // of another thread. The latter is avoided even in case of overflow
-    // because we have guard pages at the end of all stacks. Hence, if
-    // we go over the stack base and hit the stack of another thread,
-    // this should not be in a writeable area that could contain a
-    // stack lock allocated by that thread. As a consequence, a stack
-    // lock less than page size away from sp is guaranteed to be
-    // owned by the current thread.
-    //
-    // These 3 tests can be done by evaluating the following
-    // expression: ((mark - sp) & (7 - os::vm_page_size())),
-    // assuming both stack pointer and pagesize have their
-    // least significant 3 bits clear.
-    // NOTE: the mark is in swap_reg %r0 as the result of cmpxchg
-    // NOTE2: aarch64 does not like to subtract sp from rn so take a
-    // copy
-    mov(rscratch1, sp);
-    sub(swap_reg, swap_reg, rscratch1);
-    ands(swap_reg, swap_reg, (uint64_t)(7 - os::vm_page_size()));
-
-    // Save the test result, for recursive case, the result is zero
-    str(swap_reg, Address(lock_reg, mark_offset));
-
-    if (PrintBiasedLockingStatistics) {
-      br(Assembler::NE, slow_case);
-      atomic_incw(Address((address)BiasedLocking::fast_path_entry_count_addr()),
-                  rscratch2, rscratch1, tmp);
-    }
-    br(Assembler::EQ, done);
+      // Load (object->mark() | 1) into swap_reg
+      ldr(rscratch1, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
+      orr(swap_reg, rscratch1, 1);
+
+      // Save (object->mark() | 1) into BasicLock's displaced header
+      str(swap_reg, Address(lock_reg, mark_offset));
+
+      assert(lock_offset == 0,
+             "displached header must be first word in BasicObjectLock");
+
+      Label fail;
+      if (PrintBiasedLockingStatistics) {
+        Label fast;
+        cmpxchg_obj_header(swap_reg, lock_reg, obj_reg, rscratch1, fast, &fail);
+        bind(fast);
+        atomic_incw(Address((address)BiasedLocking::fast_path_entry_count_addr()),
+                    rscratch2, rscratch1, tmp);
+        b(done);
+        bind(fail);
+      } else {
+        cmpxchg_obj_header(swap_reg, lock_reg, obj_reg, rscratch1, done, /*fallthrough*/NULL);
+      }
 
+      // Fast check for recursive lock.
+      //
+      // Can apply the optimization only if this is a stack lock
+      // allocated in this thread. For efficiency, we can focus on
+      // recently allocated stack locks (instead of reading the stack
+      // base and checking whether 'mark' points inside the current
+      // thread stack):
+      //  1) (mark & 7) == 0, and
+      //  2) sp <= mark < mark + os::pagesize()
+      //
+      // Warning: sp + os::pagesize can overflow the stack base. We must
+      // neither apply the optimization for an inflated lock allocated
+      // just above the thread stack (this is why condition 1 matters)
+      // nor apply the optimization if the stack lock is inside the stack
+      // of another thread. The latter is avoided even in case of overflow
+      // because we have guard pages at the end of all stacks. Hence, if
+      // we go over the stack base and hit the stack of another thread,
+      // this should not be in a writeable area that could contain a
+      // stack lock allocated by that thread. As a consequence, a stack
+      // lock less than page size away from sp is guaranteed to be
+      // owned by the current thread.
+      //
+      // These 3 tests can be done by evaluating the following
+      // expression: ((mark - sp) & (7 - os::vm_page_size())),
+      // assuming both stack pointer and pagesize have their
+      // least significant 3 bits clear.
+      // NOTE: the mark is in swap_reg %r0 as the result of cmpxchg
+      // NOTE2: aarch64 does not like to subtract sp from rn so take a
+      // copy
+      mov(rscratch1, sp);
+      sub(swap_reg, swap_reg, rscratch1);
+      ands(swap_reg, swap_reg, (uint64_t)(7 - os::vm_page_size()));
+
+      // Save the test result, for recursive case, the result is zero
+      str(swap_reg, Address(lock_reg, mark_offset));
+
+      if (PrintBiasedLockingStatistics) {
+        br(Assembler::NE, slow_case);
+        atomic_incw(Address((address)BiasedLocking::fast_path_entry_count_addr()),
+                    rscratch2, rscratch1, tmp);
+      }
+      br(Assembler::EQ, done);
+    }
     bind(slow_case);
 
     // Call the runtime routine for slow case
-    call_VM(noreg,
-            CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
-            lock_reg);
+    if (LockingMode == LM_LIGHTWEIGHT) {
+      call_VM(noreg,
+              CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter_obj),
+              obj_reg);
+    } else {
+      call_VM(noreg,
+              CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
+              lock_reg);
+    }
 
     bind(done);
   }
 }
 
@@ -849,45 +861,54 @@
 //      rscratch1, rscratch2 (scratch regs)
 void InterpreterMacroAssembler::unlock_object(Register lock_reg)
 {
   assert(lock_reg == c_rarg1, "The argument is only for looks. It must be rarg1");
 
-  if (UseHeavyMonitors) {
+  if (LockingMode == LM_MONITOR) {
     call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit), lock_reg);
   } else {
     Label done;
 
     const Register swap_reg   = r0;
     const Register header_reg = c_rarg2;  // Will contain the old oopMark
     const Register obj_reg    = c_rarg3;  // Will contain the oop
+    const Register tmp_reg    = c_rarg4;  // Temporary used by lightweight_unlock
 
     save_bcp(); // Save in case of exception
 
-    // Convert from BasicObjectLock structure to object and BasicLock
-    // structure Store the BasicLock address into %r0
-    lea(swap_reg, Address(lock_reg, BasicObjectLock::lock_offset_in_bytes()));
+    if (LockingMode != LM_LIGHTWEIGHT) {
+      // Convert from BasicObjectLock structure to object and BasicLock
+      // structure Store the BasicLock address into %r0
+      lea(swap_reg, Address(lock_reg, BasicObjectLock::lock_offset_in_bytes()));
+    }
 
     // Load oop into obj_reg(%c_rarg3)
     ldr(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset_in_bytes()));
 
     // Free entry
     str(zr, Address(lock_reg, BasicObjectLock::obj_offset_in_bytes()));
 
-    if (UseBiasedLocking) {
-      biased_locking_exit(obj_reg, header_reg, done);
-    }
-
-    // Load the old header from BasicLock structure
-    ldr(header_reg, Address(swap_reg,
-                            BasicLock::displaced_header_offset_in_bytes()));
+    if (LockingMode == LM_LIGHTWEIGHT) {
+      Label slow_case;
+      lightweight_unlock(obj_reg, header_reg, swap_reg, tmp_reg, slow_case);
+      b(done);
+      bind(slow_case);
+    } else {
+      if (UseBiasedLocking) {
+        biased_locking_exit(obj_reg, header_reg, done);
+      }
 
-    // Test for recursion
-    cbz(header_reg, done);
+      // Load the old header from BasicLock structure
+      ldr(header_reg, Address(swap_reg,
+                              BasicLock::displaced_header_offset_in_bytes()));
 
-    // Atomic swap back the old header
-    cmpxchg_obj_header(swap_reg, header_reg, obj_reg, rscratch1, done, /*fallthrough*/NULL);
+      // Test for recursion
+      cbz(header_reg, done);
 
+      // Atomic swap back the old header
+      cmpxchg_obj_header(swap_reg, header_reg, obj_reg, rscratch1, done, /*fallthrough*/NULL);
+    }
     // Call the runtime routine for slow case.
     str(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset_in_bytes())); // restore obj
     call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit), lock_reg);
 
     bind(done);
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, 2021, 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 "jvm.h"
 #include "asm/assembler.hpp"
 #include "asm/assembler.inline.hpp"
 #include "ci/ciEnv.hpp"
@@ -48,13 +46,15 @@
 #include "oops/klass.inline.hpp"
 #include "runtime/biasedLocking.hpp"
 #include "runtime/icache.hpp"
 #include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/jniHandles.inline.hpp"
+#include "runtime/objectMonitor.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "runtime/stubRoutines.hpp"
 #include "runtime/thread.hpp"
+#include "utilities/globalDefinitions.hpp"
 #include "utilities/powerOfTwo.hpp"
 #ifdef COMPILER1
 #include "c1/c1_LIRAssembler.hpp"
 #endif
 #ifdef COMPILER2
@@ -62,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
@@ -2487,10 +2489,14 @@
   BLOCK_COMMENT("cmpxchg {");
   if (UseLSE) {
     mov(result, expected);
     lse_cas(result, new_val, addr, size, acquire, release, /*not_pair*/ true);
     compare_eq(result, expected, size);
+#ifdef ASSERT
+    // Poison rscratch1 which is written on !UseLSE branch
+    mov(rscratch1, 0x1f1f1f1f1f1f1f1f);
+#endif
   } else {
     Label retry_load, done;
     if ((VM_Version::features() & VM_Version::CPU_STXR_PREFETCH))
       prfm(Address(addr), PSTL1STRM);
     bind(retry_load);
@@ -3803,13 +3809,50 @@
   ldr(holder, Address(method, Method::const_offset()));                      // ConstMethod*
   ldr(holder, Address(holder, ConstMethod::constants_offset()));             // ConstantPool*
   ldr(holder, Address(holder, ConstantPool::pool_holder_offset_in_bytes())); // InstanceKlass*
 }
 
-void MacroAssembler::load_klass(Register dst, Register src) {
-  if (UseCompressedClassPointers) {
+// Loads the obj's Klass* into dst.
+// src and dst must be distinct registers
+// Preserves all registers (incl src, rscratch1 and rscratch2), but clobbers condition flags
+void MacroAssembler::load_nklass(Register dst, Register src) {
+  assert(UseCompressedClassPointers, "expects UseCompressedClassPointers");
+
+  if (!UseCompactObjectHeaders) {
     ldrw(dst, Address(src, oopDesc::klass_offset_in_bytes()));
+    return;
+  }
+
+  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 and decode Klass*.
+  bind(fast);
+  lsr(dst, dst, markWord::klass_shift);
+}
+
+void MacroAssembler::load_klass(Register dst, Register src, bool null_check_src) {
+  if (null_check_src) {
+    if (UseCompactObjectHeaders) {
+      null_check(src, oopDesc::mark_offset_in_bytes());
+    } else {
+      null_check(src, oopDesc::klass_offset_in_bytes());
+    }
+  }
+
+  if (UseCompressedClassPointers) {
+    if (UseCompactObjectHeaders) {
+      load_nklass(dst, src);
+    } else {
+      ldrw(dst, Address(src, oopDesc::klass_offset_in_bytes()));
+    }
     decode_klass_not_null(dst);
   } else {
     ldr(dst, Address(src, oopDesc::klass_offset_in_bytes()));
   }
 }
@@ -3844,12 +3887,17 @@
   ldr(dst, Address(dst, mirror_offset));
   resolve_oop_handle(dst, tmp);
 }
 
 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(tmp, oop);
+    } else {
+      ldrw(tmp, Address(oop, oopDesc::klass_offset_in_bytes()));
+    }
     if (CompressedKlassPointers::base() == NULL) {
       cmp(trial_klass, tmp, LSL, CompressedKlassPointers::shift());
       return;
     } else if (((uint64_t)CompressedKlassPointers::base() & 0xffffffff) == 0
                && CompressedKlassPointers::shift() == 0) {
@@ -3862,15 +3910,10 @@
     ldr(tmp, Address(oop, oopDesc::klass_offset_in_bytes()));
   }
   cmp(trial_klass, tmp);
 }
 
-void MacroAssembler::load_prototype_header(Register dst, Register src) {
-  load_klass(dst, src);
-  ldr(dst, Address(dst, Klass::prototype_header_offset()));
-}
-
 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.
   if (UseCompressedClassPointers) {
     encode_klass_not_null(src);
@@ -3885,10 +3928,15 @@
     // Store to klass gap in destination
     strw(src, Address(dst, oopDesc::klass_gap_offset_in_bytes()));
   }
 }
 
+void MacroAssembler::load_prototype_header(Register dst, Register src) {
+  load_klass(dst, src);
+  ldr(dst, Address(dst, Klass::prototype_header_offset()));
+}
+
 // Algorithm must match CompressedOops::encode.
 void MacroAssembler::encode_heap_oop(Register d, Register s) {
 #ifdef ASSERT
   verify_heapbase("MacroAssembler::encode_heap_oop: heap base corrupted?");
 #endif
@@ -5377,5 +5425,126 @@
       default:
         ShouldNotReachHere();
     }
   }
 }
+
+// Implements lightweight-locking.
+//
+//  - obj: the object to be locked
+//  - t1, t2, t3: temporary registers, will be destroyed
+//  - slow: branched to if locking fails, absolute offset may larger than 32KB (imm14 encoding).
+void MacroAssembler::lightweight_lock(Register obj, Register t1, Register t2, Register t3, Label& slow) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "only used with new lightweight locking");
+  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);
+
+  // 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.
+//
+// - obj: the object to be unlocked
+// - 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");
+  // cmpxchg clobbers rscratch1.
+  assert_different_registers(obj, t1, t2, t3, rscratch1);
+
+#ifdef ASSERT
+  {
+    // 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::GE, stack_ok);
+    STOP("Lock-stack underflow");
+    bind(stack_ok);
+  }
+#endif
+
+  Label unlocked, push_and_slow;
+  const Register top = t1;
+  const Register mark = t2;
+  const Register t = t3;
+
+  // 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);
+
+  // 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
+  // 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
+
+  // 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, 2021, 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
@@ -821,11 +821,12 @@
 
   void load_method_holder_cld(Register rresult, Register rmethod);
   void load_method_holder(Register holder, Register method);
 
   // oop manipulations
-  void load_klass(Register dst, Register src);
+  void load_nklass(Register dst, Register src);
+  void load_klass(Register dst, Register src, bool null_check = false);
   void store_klass(Register dst, Register src);
   void cmp_klass(Register oop, Register trial_klass, Register tmp);
 
   void resolve_weak_handle(Register result, Register tmp);
   void resolve_oop_handle(Register result, Register tmp = r5);
@@ -848,14 +849,14 @@
   // currently unimplemented
   // Used for storing NULL. All other oop constants should be
   // stored using routines that take a jobject.
   void store_heap_oop_null(Address dst);
 
-  void load_prototype_header(Register dst, Register src);
-
   void store_klass_gap(Register dst, Register src);
 
+  void load_prototype_header(Register dst, Register src);
+
   // This dummy is to prevent a call to store_heap_oop from
   // converting a zero (like NULL) into a Register by giving
   // the compiler two choices it can't resolve
 
   void store_heap_oop(Address dst, void* dummy);
@@ -1421,10 +1422,13 @@
   void cache_wbsync(bool is_pre);
 
   // Code for java.lang.Thread::onSpinWait() intrinsic.
   void spin_wait();
 
+  void lightweight_lock(Register obj, Register t1, Register t2, Register t3, Label& slow);
+  void lightweight_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/methodHandles_aarch64.cpp b/src/hotspot/cpu/aarch64/methodHandles_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/methodHandles_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/methodHandles_aarch64.cpp
@@ -305,12 +305,11 @@
       if (iid == vmIntrinsics::_linkToSpecial) {
         // Don't actually load the klass; just null-check the receiver.
         __ null_check(receiver_reg);
       } else {
         // load receiver klass itself
-        __ null_check(receiver_reg, oopDesc::klass_offset_in_bytes());
-        __ load_klass(temp1_recv_klass, receiver_reg);
+        __ load_klass(temp1_recv_klass, receiver_reg, true);
         __ verify_klass_ptr(temp1_recv_klass);
       }
       BLOCK_COMMENT("check_receiver {");
       // The receiver for the MemberName must be in receiver_reg.
       // Check the receiver against the MemberName.clazz
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, 2021, 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
@@ -1750,10 +1750,11 @@
 
   const Register swap_reg = r0;
   const Register obj_reg  = r19;  // Will contain the oop
   const Register lock_reg = r13;  // Address of compiler lock object (BasicLock)
   const Register old_hdr  = r13;  // value of old header at unlock time
+  const Register lock_tmp = r14;  // Temporary used by lightweight_lock/unlock
   const Register tmp = lr;
 
   Label slow_path_lock;
   Label lock_done;
 
@@ -1770,45 +1771,51 @@
     __ lea(lock_reg, Address(sp, lock_slot_offset * VMRegImpl::stack_slot_size));
 
     // Load the oop from the handle
     __ ldr(obj_reg, Address(oop_handle_reg, 0));
 
-    if (UseBiasedLocking) {
-      __ biased_locking_enter(lock_reg, obj_reg, swap_reg, tmp, false, lock_done, &slow_path_lock);
-    }
-
-    // Load (object->mark() | 1) into swap_reg %r0
-    __ ldr(rscratch1, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
-    __ orr(swap_reg, rscratch1, 1);
+    if (LockingMode == LM_MONITOR) {
+      __ b(slow_path_lock);
+    } else if (LockingMode == LM_LEGACY) {
+      if (UseBiasedLocking) {
+        __ biased_locking_enter(lock_reg, obj_reg, swap_reg, tmp, false, lock_done, &slow_path_lock);
+      }
 
-    // Save (object->mark() | 1) into BasicLock's displaced header
-    __ str(swap_reg, Address(lock_reg, mark_word_offset));
+      // Load (object->mark() | 1) into swap_reg %r0
+      __ ldr(rscratch1, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
+      __ orr(swap_reg, rscratch1, 1);
 
-    // src -> dest iff dest == r0 else r0 <- dest
-    { Label here;
-      __ cmpxchg_obj_header(r0, lock_reg, obj_reg, rscratch1, lock_done, /*fallthrough*/NULL);
-    }
+      // Save (object->mark() | 1) into BasicLock's displaced header
+      __ str(swap_reg, Address(lock_reg, mark_word_offset));
 
-    // Hmm should this move to the slow path code area???
+      // src -> dest iff dest == r0 else r0 <- dest
+      { Label here;
+        __ cmpxchg_obj_header(r0, lock_reg, obj_reg, rscratch1, lock_done, /*fallthrough*/NULL);
+      }
 
-    // Test if the oopMark is an obvious stack pointer, i.e.,
-    //  1) (mark & 3) == 0, and
-    //  2) sp <= mark < mark + os::pagesize()
-    // These 3 tests can be done by evaluating the following
-    // expression: ((mark - sp) & (3 - os::vm_page_size())),
-    // assuming both stack pointer and pagesize have their
-    // least significant 2 bits clear.
-    // NOTE: the oopMark is in swap_reg %r0 as the result of cmpxchg
+      // Hmm should this move to the slow path code area???
 
-    __ sub(swap_reg, sp, swap_reg);
-    __ neg(swap_reg, swap_reg);
-    __ ands(swap_reg, swap_reg, 3 - os::vm_page_size());
+      // Test if the oopMark is an obvious stack pointer, i.e.,
+      //  1) (mark & 3) == 0, and
+      //  2) sp <= mark < mark + os::pagesize()
+      // These 3 tests can be done by evaluating the following
+      // expression: ((mark - sp) & (3 - os::vm_page_size())),
+      // assuming both stack pointer and pagesize have their
+      // least significant 2 bits clear.
+      // NOTE: the oopMark is in swap_reg %r0 as the result of cmpxchg
 
-    // 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);
+      __ sub(swap_reg, sp, swap_reg);
+      __ neg(swap_reg, swap_reg);
+      __ ands(swap_reg, swap_reg, 3 - os::vm_page_size());
 
+      // Save the test result, for recursive case, the result is zero
+      __ str(swap_reg, Address(lock_reg, mark_word_offset));
+      __ br(Assembler::NE, slow_path_lock);
+    } else {
+      assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+      __ lightweight_lock(obj_reg, swap_reg, tmp, lock_tmp, slow_path_lock);
+    }
     // Slow path will re-enter here
 
     __ bind(lock_done);
   }
 
@@ -1926,30 +1933,38 @@
 
     if (UseBiasedLocking) {
       __ biased_locking_exit(obj_reg, old_hdr, done);
     }
 
-    // Simple recursive lock?
-
-    __ ldr(rscratch1, Address(sp, lock_slot_offset * VMRegImpl::stack_slot_size));
-    __ cbz(rscratch1, done);
+    if (LockingMode == LM_LEGACY) {
+      // Simple recursive lock?
+      __ ldr(rscratch1, Address(sp, lock_slot_offset * VMRegImpl::stack_slot_size));
+      __ cbz(rscratch1, done);
+    }
 
     // Must save r0 if if it is live now because cmpxchg must use it
     if (ret_type != T_FLOAT && ret_type != T_DOUBLE && ret_type != T_VOID) {
       save_native_result(masm, ret_type, stack_slots);
     }
 
 
-    // get address of the stack lock
-    __ lea(r0, Address(sp, lock_slot_offset * VMRegImpl::stack_slot_size));
-    //  get old displaced header
-    __ ldr(old_hdr, Address(r0, 0));
+    if (LockingMode == LM_MONITOR) {
+      __ b(slow_path_unlock);
+    } else if (LockingMode == LM_LEGACY) {
+      // get address of the stack lock
+      __ lea(r0, Address(sp, lock_slot_offset * VMRegImpl::stack_slot_size));
+      //  get old displaced header
+      __ ldr(old_hdr, Address(r0, 0));
 
-    // Atomic swap old header if oop still contains the stack lock
-    Label succeed;
-    __ cmpxchg_obj_header(r0, old_hdr, obj_reg, rscratch1, succeed, &slow_path_unlock);
-    __ bind(succeed);
+      // Atomic swap old header if oop still contains the stack lock
+      Label succeed;
+      __ cmpxchg_obj_header(r0, old_hdr, obj_reg, rscratch1, succeed, &slow_path_unlock);
+      __ bind(succeed);
+    } else {
+      assert(LockingMode == LM_LIGHTWEIGHT, "");
+      __ lightweight_unlock(obj_reg, old_hdr, swap_reg, lock_tmp, slow_path_unlock);
+    }
 
     // slow path re-enters here
     __ bind(unlock_done);
     if (ret_type != T_FLOAT && ret_type != T_DOUBLE && ret_type != T_VOID) {
       restore_native_result(masm, ret_type, stack_slots);
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
@@ -3221,12 +3221,11 @@
   __ jump_from_interpreted(method, r0);
 
   __ bind(notFinal);
 
   // get receiver klass
-  __ null_check(recv, oopDesc::klass_offset_in_bytes());
-  __ load_klass(r0, recv);
+  __ load_klass(r0, recv, true);
 
   // profile this call
   __ profile_virtual_call(r0, rlocals, r3);
 
   // get target Method & entry point
@@ -3311,12 +3310,11 @@
   // Check for private method invocation - indicated by vfinal
   Label notVFinal;
   __ tbz(r3, ConstantPoolCacheEntry::is_vfinal_shift, notVFinal);
 
   // Get receiver klass into r3 - also a null check
-  __ null_check(r2, oopDesc::klass_offset_in_bytes());
-  __ load_klass(r3, r2);
+  __ load_klass(r3, r2, true);
 
   Label subtype;
   __ check_klass_subtype(r3, r0, r4, subtype);
   // If we get here the typecheck failed
   __ b(no_such_interface);
@@ -3328,12 +3326,11 @@
 
   __ bind(notVFinal);
 
   // Get receiver klass into r3 - also a null check
   __ restore_locals();
-  __ null_check(r2, oopDesc::klass_offset_in_bytes());
-  __ load_klass(r3, r2);
+  __ load_klass(r3, r2, true);
 
   Label no_such_method;
 
   // Preserve method for throw_AbstractMethodErrorVerbose.
   __ mov(r16, rmethod);
@@ -3524,34 +3521,40 @@
   // there is an initialize need. Otherwise, skip and go to the slow path.
   if (UseTLAB || allow_shared_alloc) {
     // The object is initialized before the header.  If the object size is
     // zero, go directly to the header initialization.
     __ bind(initialize_object);
-    __ sub(r3, r3, sizeof(oopDesc));
+    __ sub(r3, r3, oopDesc::base_offset_in_bytes());
     __ cbz(r3, initialize_header);
 
     // Initialize object fields
     {
-      __ add(r2, r0, sizeof(oopDesc));
+      __ add(r2, r0, oopDesc::base_offset_in_bytes());
+      if (!is_aligned(oopDesc::base_offset_in_bytes(), BytesPerLong)) {
+        __ strw(zr, Address(__ post(r2, BytesPerInt)));
+        __ sub(r3, r3, BytesPerInt);
+        __ cbz(r3, initialize_header);
+      }
       Label loop;
       __ bind(loop);
       __ str(zr, Address(__ post(r2, BytesPerLong)));
       __ sub(r3, r3, BytesPerLong);
       __ cbnz(r3, loop);
     }
 
     // initialize object header only.
     __ bind(initialize_header);
-    if (UseBiasedLocking) {
+    if (UseBiasedLocking || UseCompactObjectHeaders) {
       __ ldr(rscratch1, Address(r4, Klass::prototype_header_offset()));
     } 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
-
+    if (!UseCompactObjectHeaders) {
+      __ 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
@@ -151,10 +151,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/aarch64/vtableStubs_aarch64.cpp b/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp
@@ -210,11 +210,11 @@
                              L_no_such_interface);
 
   const ptrdiff_t lookupSize = __ pc() - start_pc;
 
   // Reduce "estimate" such that "padding" does not drop below 8.
-  const ptrdiff_t estimate = 124;
+  const ptrdiff_t estimate = 128;
   const ptrdiff_t codesize = typecheckSize + lookupSize;
   slop_delta  = (int)(estimate - codesize);
   slop_bytes += slop_delta;
   assert(slop_delta >= 0, "itable #%d: Code size estimate (%d) for lookup_interface_method too small, required: %d", itable_index, (int)estimate, (int)codesize);
 
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
@@ -241,10 +241,13 @@
                              Runtime1::monitorexit_nofpu_id;
   __ call(Runtime1::entry_for(exit_id), relocInfo::runtime_call_type);
   __ b(_continuation);
 }
 
+void LoadKlassStub::emit_code(LIR_Assembler* ce) {
+  Unimplemented();  // Only needed with compact object headers.
+}
 
 // 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/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
@@ -315,10 +315,13 @@
   __ mtctr(R0);
   __ bctrl();
   __ b(_continuation);
 }
 
+void LoadKlassStub::emit_code(LIR_Assembler* ce) {
+  Unimplemented();  // Only needed with compact object headers.
+}
 
 // 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/c2_CodeStubs_ppc.cpp b/src/hotspot/cpu/ppc/c2_CodeStubs_ppc.cpp
--- /dev/null
+++ b/src/hotspot/cpu/ppc/c2_CodeStubs_ppc.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2022, SAP SE. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+#include "opto/c2_MacroAssembler.hpp"
+#include "opto/c2_CodeStubs.hpp"
+#include "runtime/sharedRuntime.hpp"
+
+#define __ masm.
+
+int C2SafepointPollStub::max_size() const {
+  return 56;
+}
+
+void C2SafepointPollStub::emit(C2_MacroAssembler& masm) {
+  assert(SharedRuntime::polling_page_return_handler_blob() != NULL,
+         "polling page return stub not created yet");
+  address stub = SharedRuntime::polling_page_return_handler_blob()->entry_point();
+
+  __ bind(entry());
+  // Using pc relative address computation.
+  {
+    Label next_pc;
+    __ bl(next_pc);
+    __ bind(next_pc);
+  }
+  int current_offset = __ offset();
+  // Code size should not depend on offset: see _stub_size computation in output.cpp
+  __ load_const32(R12, _safepoint_offset - current_offset);
+  __ mflr(R0);
+  __ add(R12, R12, R0);
+  __ std(R12, in_bytes(JavaThread::saved_exception_pc_offset()), R16_thread);
+
+  __ add_const_optimized(R0, R29_TOC, MacroAssembler::offset_to_global_toc(stub));
+  __ mtctr(R0);
+  __ bctr();
+}
+#undef __
diff a/src/hotspot/cpu/ppc/c2_safepointPollStubTable_ppc.cpp b/src/hotspot/cpu/ppc/c2_safepointPollStubTable_ppc.cpp
--- a/src/hotspot/cpu/ppc/c2_safepointPollStubTable_ppc.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2021 SAP SE. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- *
- */
-
-#include "precompiled.hpp"
-#include "macroAssembler_ppc.inline.hpp"
-#include "opto/compile.hpp"
-#include "opto/node.hpp"
-#include "opto/output.hpp"
-#include "runtime/sharedRuntime.hpp"
-
-#define __ masm.
-void C2SafepointPollStubTable::emit_stub_impl(MacroAssembler& masm, C2SafepointPollStub* entry) const {
-  assert(SharedRuntime::polling_page_return_handler_blob() != NULL,
-         "polling page return stub not created yet");
-  address stub = SharedRuntime::polling_page_return_handler_blob()->entry_point();
-
-  __ bind(entry->_stub_label);
-  // Using pc relative address computation.
-  {
-    Label next_pc;
-    __ bl(next_pc);
-    __ bind(next_pc);
-  }
-  int current_offset = __ offset();
-  // Code size should not depend on offset: see _stub_size computation in output.cpp
-  __ load_const32(R12, entry->_safepoint_offset - current_offset);
-  __ mflr(R0);
-  __ add(R12, R12, R0);
-  __ std(R12, in_bytes(JavaThread::saved_exception_pc_offset()), R16_thread);
-
-  __ add_const_optimized(R0, R29_TOC, MacroAssembler::offset_to_global_toc(stub));
-  __ mtctr(R0);
-  __ bctr();
-}
-#undef __
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
@@ -980,10 +980,11 @@
   bool followed_by_acquire(const Node *n);
 %}
 
 source %{
 
+#include "opto/c2_CodeStubs.hpp"
 #include "oops/klass.inline.hpp"
 
 void PhaseOutput::pd_perform_mach_node_analysis() {
 }
 
@@ -1619,11 +1620,13 @@
 
   if (method_needs_polling) {
     Label dummy_label;
     Label* code_stub = &dummy_label;
     if (!UseSIGTRAP && !C->output()->in_scratch_emit_size()) {
-      code_stub = &C->output()->safepoint_poll_table()->add_safepoint(__ offset());
+      C2SafepointPollStub* stub = new (C->comp_arena()) C2SafepointPollStub(__ offset());
+      C->output()->add_stub(stub);
+      code_stub = &stub->entry();
       __ relocate(relocInfo::poll_return_type);
     }
     __ safepoint_poll(*code_stub, temp, true /* at_return */, true /* in_nmethod */);
   }
 }
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
@@ -268,10 +268,14 @@
   ce->emit_call_c(Runtime1::entry_for (exit_id));
   CHECK_BAILOUT();
   __ branch_optimized(Assembler::bcondAlways, _continuation);
 }
 
+void LoadKlassStub::emit_code(LIR_Assembler* ce) {
+  Unimplemented();  // Only needed with compact object headers.
+}
+
 // Implementation of patching:
 // - Copy the code at given offset to an inlined buffer (first the bytes, then the number of bytes).
 // - Replace original code with a call to the stub.
 // At Runtime:
 // - call to stub, jump to runtime.
diff a/src/hotspot/cpu/x86/c1_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"
 
@@ -298,10 +299,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
@@ -1641,11 +1641,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());
@@ -3066,10 +3066,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 != NULL ? default_type->element_type()->basic_type() : T_ILLEGAL;
   if (is_reference_type(basic_type)) basic_type = T_OBJECT;
@@ -3257,17 +3258,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;
@@ -3423,22 +3418,17 @@
 #ifdef _LP64
     if (UseCompressedClassPointers) {
       __ 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);
@@ -3496,15 +3486,15 @@
 
 void LIR_Assembler::emit_lock(LIR_OpLock* op) {
   Register obj = op->obj_opr()->as_register();  // may not be an oop
   Register hdr = op->hdr_opr()->as_register();
   Register lock = op->lock_opr()->as_register();
-  if (!UseFastLocking) {
+  if (LockingMode == LM_MONITOR) {
     __ jmp(*op->stub()->entry());
   } else if (op->code() == lir_lock) {
     Register scratch = noreg;
-    if (UseBiasedLocking) {
+    if (UseBiasedLocking || LockingMode == LM_LIGHTWEIGHT) {
       scratch = op->scratch_opr()->as_register();
     }
     assert(BasicLock::displaced_header_offset_in_bytes() == 0, "lock_reg must point to the displaced header");
     // add debug info for NullPointerException only if one is possible
     int null_check_offset = __ lock_object(hdr, obj, lock, scratch, *op->stub()->entry());
@@ -3529,11 +3519,24 @@
   if (info != NULL) {
     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()));
@@ -3660,16 +3663,13 @@
       // atomic update to prevent overwriting Klass* with 0
       __ lock();
       __ orptr(mdo_addr, TypeEntries::null_seen);
     }
     if (do_update) {
-#ifndef ASSERT
-      __ jmpb(next);
-    }
-#else
       __ jmp(next);
     }
+#ifdef ASSERT
   } else {
     __ testptr(obj, obj);
     __ jcc(Assembler::notZero, update);
     __ stop("unexpect null obj");
 #endif
diff a/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp b/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp
--- a/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp
+++ b/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp
@@ -312,11 +312,11 @@
 
   // "lock" stores the address of the monitor stack slot, so this is not an oop
   LIR_Opr lock = new_register(T_INT);
   // Need a scratch register for biased locking on x86
   LIR_Opr scratch = LIR_OprFact::illegalOpr;
-  if (UseBiasedLocking) {
+  if (UseBiasedLocking || LockingMode == LM_LIGHTWEIGHT) {
     scratch = new_register(T_INT);
   }
 
   CodeEmitInfo* info_for_exception = NULL;
   if (x->needs_null_check()) {
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, 2021, 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,21 +32,22 @@
 #include "interpreter/interpreter.hpp"
 #include "oops/arrayOop.hpp"
 #include "oops/markWord.hpp"
 #include "runtime/basicLock.hpp"
 #include "runtime/biasedLocking.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 scratch, Label& slow_case) {
   const Register rklass_decode_tmp = LP64_ONLY(rscratch1) NOT_LP64(noreg);
   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");
-  assert(hdr != obj && hdr != disp_hdr && obj != disp_hdr, "registers must be different");
-  Label done;
+  assert_different_registers(hdr, obj, disp_hdr, scratch);
   int null_check_offset = -1;
 
   verify_oop(obj);
 
   // save object being locked into the BasicObjectLock
@@ -59,92 +60,116 @@
     movl(hdr, Address(hdr, Klass::access_flags_offset()));
     testl(hdr, JVM_ACC_IS_VALUE_BASED_CLASS);
     jcc(Assembler::notZero, slow_case);
   }
 
-  if (UseBiasedLocking) {
-    assert(scratch != noreg, "should have scratch register at this point");
-    biased_locking_enter(disp_hdr, obj, hdr, scratch, rklass_decode_tmp, false, done, &slow_case);
-  }
+  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, scratch, slow_case);
+  } else {
+    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
-  // displaced header address in the object header - if it is not the same, get the
-  // object header instead
-  MacroAssembler::lock(); // must be immediately before cmpxchg!
-  cmpxchgptr(disp_hdr, Address(obj, hdr_offset));
-  // if the object header was the same, we're done
-  if (PrintBiasedLockingStatistics) {
-    cond_inc32(Assembler::equal,
-               ExternalAddress((address)BiasedLocking::fast_path_entry_count_addr()));
+    if (UseBiasedLocking) {
+      assert(scratch != noreg, "should have scratch register at this point");
+      biased_locking_enter(disp_hdr, obj, hdr, scratch, rklass_decode_tmp, false, done, &slow_case);
+    }
+
+    // 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
+    // displaced header address in the object header - if it is not the same, get the
+    // object header instead
+    MacroAssembler::lock(); // must be immediately before cmpxchg!
+    cmpxchgptr(disp_hdr, Address(obj, hdr_offset));
+    // if the object header was the same, we're done
+    if (PrintBiasedLockingStatistics) {
+      cond_inc32(Assembler::equal,
+                 ExternalAddress((address)BiasedLocking::fast_path_entry_count_addr()));
+    }
+    jcc(Assembler::equal, done);
+    // if the object header was not the same, it is now in the hdr register
+    // => test if it is a stack pointer into the same stack (recursive locking), i.e.:
+    //
+    // 1) (hdr & aligned_mask) == 0
+    // 2) rsp <= hdr
+    // 3) hdr <= rsp + page_size
+    //
+    // these 3 tests can be done by evaluating the following expression:
+    //
+    // (hdr - rsp) & (aligned_mask - page_size)
+    //
+    // assuming both the stack pointer and page_size have their least
+    // significant 2 bits cleared and page_size is a power of 2
+    subptr(hdr, rsp);
+    andptr(hdr, aligned_mask - os::vm_page_size());
+    // for recursive locking, the result is zero => save it in the displaced header
+    // location (NULL in the displaced hdr location indicates recursive locking)
+    movptr(Address(disp_hdr, 0), hdr);
+    // otherwise we don't care about the result and handle locking via runtime call
+    jcc(Assembler::notZero, slow_case);
+    // done
+    bind(done);
   }
-  jcc(Assembler::equal, done);
-  // if the object header was not the same, it is now in the hdr register
-  // => test if it is a stack pointer into the same stack (recursive locking), i.e.:
-  //
-  // 1) (hdr & aligned_mask) == 0
-  // 2) rsp <= hdr
-  // 3) hdr <= rsp + page_size
-  //
-  // these 3 tests can be done by evaluating the following expression:
-  //
-  // (hdr - rsp) & (aligned_mask - page_size)
-  //
-  // assuming both the stack pointer and page_size have their least
-  // significant 2 bits cleared and page_size is a power of 2
-  subptr(hdr, rsp);
-  andptr(hdr, aligned_mask - os::vm_page_size());
-  // for recursive locking, the result is zero => save it in the displaced header
-  // location (NULL in the displaced hdr location indicates recursive locking)
-  movptr(Address(disp_hdr, 0), hdr);
-  // otherwise we don't care about the result and handle locking via runtime call
-  jcc(Assembler::notZero, slow_case);
-  // done
-  bind(done);
   return null_check_offset;
 }
 
 
 void C1_MacroAssembler::unlock_object(Register hdr, Register obj, Register disp_hdr, Label& slow_case) {
   const int aligned_mask = BytesPerWord -1;
   const int hdr_offset = oopDesc::mark_offset_in_bytes();
   assert(disp_hdr == rax, "disp_hdr must be rax, for the cmpxchg instruction");
   assert(hdr != obj && hdr != disp_hdr && obj != disp_hdr, "registers must be different");
-  Label done;
 
-  if (UseBiasedLocking) {
-    // load object
-    movptr(obj, Address(disp_hdr, BasicObjectLock::obj_offset_in_bytes()));
-    biased_locking_exit(obj, hdr, done);
-  }
+  if (LockingMode == LM_LIGHTWEIGHT) {
+#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 {
+    Label done;
 
-  // load displaced header
-  movptr(hdr, Address(disp_hdr, 0));
-  // if the loaded hdr is NULL we had recursive locking
-  testptr(hdr, hdr);
-  // if we had recursive locking, we are done
-  jcc(Assembler::zero, done);
-  if (!UseBiasedLocking) {
-    // load object
-    movptr(obj, Address(disp_hdr, BasicObjectLock::obj_offset_in_bytes()));
+    if (UseBiasedLocking) {
+      // load object
+      movptr(obj, Address(disp_hdr, BasicObjectLock::obj_offset_in_bytes()));
+      biased_locking_exit(obj, hdr, done);
+    }
+
+    // load displaced header
+    movptr(hdr, Address(disp_hdr, 0));
+    // if the loaded hdr is NULL we had recursive locking
+    testptr(hdr, hdr);
+    // if we had recursive locking, we are done
+    jcc(Assembler::zero, done);
+    if (!UseBiasedLocking) {
+      // load object
+      movptr(obj, Address(disp_hdr, BasicObjectLock::obj_offset_in_bytes()));
+    }
+    verify_oop(obj);
+    // 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!
+    cmpxchgptr(hdr, Address(obj, hdr_offset));
+    // if the object header was not pointing to the displaced header,
+    // we do unlocking via runtime call
+    jcc(Assembler::notEqual, slow_case);
+    // done
+    bind(done);
   }
-  verify_oop(obj);
-  // 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!
-  cmpxchgptr(hdr, Address(obj, hdr_offset));
-  // if the object header was not pointing to the displaced header,
-  // we do unlocking via runtime call
-  jcc(Assembler::notEqual, slow_case);
-  // done
-  bind(done);
 }
 
 
 // Defines obj, preserves var_size_in_bytes
 void C1_MacroAssembler::try_allocate(Register obj, Register var_size_in_bytes, int con_size_in_bytes, Register t1, Register t2, Label& slow_case) {
@@ -155,36 +180,35 @@
   }
 }
 
 
 void C1_MacroAssembler::initialize_header(Register obj, Register klass, Register len, Register t1, Register t2) {
-  assert_different_registers(obj, klass, len);
-  Register tmp_encode_klass = LP64_ONLY(rscratch1) NOT_LP64(noreg);
-  if (UseBiasedLocking && !len->is_valid()) {
-    assert_different_registers(obj, klass, len, t1, t2);
+  assert_different_registers(obj, klass, len, t1, t2);
+  if (UseCompactObjectHeaders || (UseBiasedLocking && !len->is_valid())) {
     movptr(t1, Address(klass, Klass::prototype_header_offset()));
     movptr(Address(obj, oopDesc::mark_offset_in_bytes()), t1);
   } else {
-    // This assumes that all prototype bits fit in an int32_t
-    movptr(Address(obj, oopDesc::mark_offset_in_bytes ()), (int32_t)(intptr_t)markWord::prototype().value());
+    movptr(Address(obj, oopDesc::mark_offset_in_bytes()), checked_cast<int32_t>(markWord::prototype().value()));
   }
+
+  if (!UseCompactObjectHeaders) {
 #ifdef _LP64
-  if (UseCompressedClassPointers) { // Take care not to kill klass
-    movptr(t1, klass);
-    encode_klass_not_null(t1, tmp_encode_klass);
-    movl(Address(obj, oopDesc::klass_offset_in_bytes()), t1);
-  } else
+    if (UseCompressedClassPointers) { // Take care not to kill klass
+      movptr(t1, klass);
+      encode_klass_not_null(t1, rscratch1);
+      movl(Address(obj, oopDesc::klass_offset_in_bytes()), t1);
+    } else
 #endif
-  {
-    movptr(Address(obj, oopDesc::klass_offset_in_bytes()), klass);
+    {
+      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
 }
@@ -223,34 +247,35 @@
   if (!(UseTLAB && ZeroTLAB && is_tlab_allocated)) {
     // clear rest of allocated space
     const Register t1_zero = t1;
     const Register index = t2;
     const int threshold = 6 * BytesPerWord;   // approximate break even point for code size (see comments below)
+    int hdr_size_aligned = align_up(hdr_size_in_bytes, BytesPerWord); // klass gap is already cleared by init_header().
     if (var_size_in_bytes != noreg) {
       mov(index, var_size_in_bytes);
-      initialize_body(obj, index, hdr_size_in_bytes, t1_zero);
+      initialize_body(obj, index, hdr_size_aligned, t1_zero);
     } else if (con_size_in_bytes <= threshold) {
       // use explicit null stores
       // code size = 2 + 3*n bytes (n = number of fields to clear)
       xorptr(t1_zero, t1_zero); // use t1_zero reg to clear memory (shorter code)
-      for (int i = hdr_size_in_bytes; i < con_size_in_bytes; i += BytesPerWord)
+      for (int i = hdr_size_aligned; i < con_size_in_bytes; i += BytesPerWord)
         movptr(Address(obj, i), t1_zero);
-    } else if (con_size_in_bytes > hdr_size_in_bytes) {
+    } else if (con_size_in_bytes > hdr_size_aligned) {
       // use loop to null out the fields
       // code size = 16 bytes for even n (n = number of fields to clear)
       // initialize last object field first if odd number of fields
       xorptr(t1_zero, t1_zero); // use t1_zero reg to clear memory (shorter code)
       movptr(index, (con_size_in_bytes - hdr_size_in_bytes) >> 3);
       // initialize last object field if constant size is odd
-      if (((con_size_in_bytes - hdr_size_in_bytes) & 4) != 0)
+      if (((con_size_in_bytes - hdr_size_aligned) & 4) != 0)
         movptr(Address(obj, con_size_in_bytes - (1*BytesPerWord)), t1_zero);
       // initialize remaining object fields: rdx is a multiple of 2
       { Label loop;
         bind(loop);
-        movptr(Address(obj, index, Address::times_8, hdr_size_in_bytes - (1*BytesPerWord)),
+        movptr(Address(obj, index, Address::times_8, hdr_size_aligned - (1*BytesPerWord)),
                t1_zero);
-        NOT_LP64(movptr(Address(obj, index, Address::times_8, hdr_size_in_bytes - (2*BytesPerWord)),
+        NOT_LP64(movptr(Address(obj, index, Address::times_8, hdr_size_aligned - (2*BytesPerWord)),
                t1_zero);)
         decrement(index);
         jcc(Assembler::notZero, loop);
       }
     }
@@ -262,11 +287,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");
@@ -275,21 +300,21 @@
   cmpptr(len, (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, (int32_t)header_size * BytesPerWord + MinObjAlignmentInBytesMask);
+  movptr(arr_size, (int32_t)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 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_in_bytes, len_zero);
 
   if (CURRENT_ENV->dtrace_alloc_probes()) {
     assert(obj == rax, "must be");
     call(RuntimeAddress(Runtime1::entry_for(Runtime1::dtrace_object_alloc_id)));
   }
diff a/src/hotspot/cpu/x86/c2_CodeStubs_x86.cpp b/src/hotspot/cpu/x86/c2_CodeStubs_x86.cpp
--- /dev/null
+++ b/src/hotspot/cpu/x86/c2_CodeStubs_x86.cpp
@@ -0,0 +1,148 @@
+/*
+ * 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.
+ *
+ * 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 "opto/c2_MacroAssembler.hpp"
+#include "opto/c2_CodeStubs.hpp"
+#include "runtime/objectMonitor.hpp"
+#include "runtime/sharedRuntime.hpp"
+#include "runtime/stubRoutines.hpp"
+
+#define __ masm.
+
+int C2SafepointPollStub::max_size() const {
+  return 33;
+}
+
+void C2SafepointPollStub::emit(C2_MacroAssembler& masm) {
+  assert(SharedRuntime::polling_page_return_handler_blob() != NULL,
+         "polling page return stub not created yet");
+  address stub = SharedRuntime::polling_page_return_handler_blob()->entry_point();
+
+  RuntimeAddress callback_addr(stub);
+
+  __ bind(entry());
+  InternalAddress safepoint_pc(masm.pc() - masm.offset() + _safepoint_offset);
+#ifdef _LP64
+  __ lea(rscratch1, safepoint_pc);
+  __ movptr(Address(r15_thread, JavaThread::saved_exception_pc_offset()), rscratch1);
+#else
+  const Register tmp1 = rcx;
+  const Register tmp2 = rdx;
+  __ push(tmp1);
+  __ push(tmp2);
+
+  __ lea(tmp1, safepoint_pc);
+  __ get_thread(tmp2);
+  __ movptr(Address(tmp2, JavaThread::saved_exception_pc_offset()), tmp1);
+
+  __ pop(tmp2);
+  __ pop(tmp1);
+#endif
+  __ jump(callback_addr);
+}
+
+int C2FastUnlockLightweightStub::max_size() const {
+  return 128;
+}
+
+void C2FastUnlockLightweightStub::emit(C2_MacroAssembler& masm) {
+  assert(_t == rax, "must be");
+
+  Label slow_path;
+
+  { // Restore lock-stack and handle the unlock in runtime.
+
+    __ bind(_push_and_slow_path);
+#ifdef ASSERT
+    // 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);
+  }
+
+  { // Slow path.
+
+    __ bind(slow_path);
+    // Clear ZF.
+    __ testptr(_thread, _thread);
+    __ jmp(slow_path_continuation());
+  }
+
+  { // Handle monitor medium path.
+
+    __ bind(_check_successor);
+
+    Label fix_zf_and_unlocked;
+    const Register monitor = _mark;
+
+#ifndef _LP64
+    __ jmpb(slow_path);
+#else // _LP64
+    // successor null check.
+    __ cmpptr(Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(succ)), NULL_WORD);
+    __ jccb(Assembler::equal, 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, 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
@@ -24,17 +24,23 @@
 
 #include "precompiled.hpp"
 #include "asm/assembler.hpp"
 #include "asm/assembler.inline.hpp"
 #include "oops/methodData.hpp"
+#include "opto/c2_CodeStubs.hpp"
 #include "opto/c2_MacroAssembler.hpp"
 #include "opto/intrinsicnode.hpp"
 #include "opto/opcodes.hpp"
+#include "opto/output.hpp"
 #include "opto/subnode.hpp"
 #include "runtime/biasedLocking.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"
 
 inline Assembler::AvxVectorLen C2_MacroAssembler::vector_length_encoding(int vlen_in_bytes) {
   switch (vlen_in_bytes) {
     case  4: // fall-through
     case  8: // fall-through
@@ -444,16 +450,17 @@
 // obj: object to lock
 // box: on-stack box address (displaced header location) - KILLED
 // rax,: tmp -- KILLED
 // scr: tmp -- KILLED
 void C2_MacroAssembler::fast_lock(Register objReg, Register boxReg, Register tmpReg,
-                                 Register scrReg, Register cx1Reg, Register cx2Reg,
+                                 Register scrReg, Register cx1Reg, Register cx2Reg, Register thread,
                                  BiasedLockingCounters* counters,
                                  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);
@@ -511,33 +518,39 @@
   }
 #endif // INCLUDE_RTM_OPT
 
   movptr(tmpReg, Address(objReg, oopDesc::mark_offset_in_bytes()));          // [FETCH]
   testptr(tmpReg, markWord::monitor_value); // inflated vs stack-locked|neutral|biased
-  jccb(Assembler::notZero, IsInflated);
+  jcc(Assembler::notZero, IsInflated);
 
-  // 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
-  if (counters != NULL) {
-    cond_inc32(Assembler::equal,
-               ExternalAddress((address)counters->fast_path_entry_count_addr()));
-  }
-  jcc(Assembler::equal, DONE_LABEL);           // Success
-
-  // Recursive locking.
-  // The object is stack-locked: markword contains stack pointer to BasicLock.
-  // 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 - os::vm_page_size())) );
-  movptr(Address(boxReg, 0), tmpReg);
-  if (counters != NULL) {
-    cond_inc32(Assembler::equal,
-               ExternalAddress((address)counters->fast_path_entry_count_addr()));
+  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 {
+    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
+    if (counters != NULL) {
+      cond_inc32(Assembler::equal,
+                 ExternalAddress((address)counters->fast_path_entry_count_addr()));
+    }
+    jcc(Assembler::equal, DONE_LABEL);           // Success
+
+    // Recursive locking.
+    // The object is stack-locked: markword contains stack pointer to BasicLock.
+    // 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 - os::vm_page_size())) );
+    movptr(Address(boxReg, 0), tmpReg);
+    if (counters != NULL) {
+      cond_inc32(Assembler::equal,
+                 ExternalAddress((address)counters->fast_path_entry_count_addr()));
+    }
   }
   jmp(DONE_LABEL);
 
   bind(IsInflated);
   // The object is inflated. tmpReg contains pointer to ObjectMonitor* + markWord::monitor_value
@@ -657,10 +670,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, CheckSucc;
 
@@ -682,26 +696,31 @@
     jmp(DONE_LABEL);                                                  // ... and we're done
     bind(L_regular_unlock);
   }
 #endif
 
-  cmpptr(Address(boxReg, 0), (int32_t)NULL_WORD);                   // Examine the displaced header
-  jcc   (Assembler::zero, DONE_LABEL);                              // 0 indicates recursive stack-lock
+  if (LockingMode == LM_LEGACY) {
+    cmpptr(Address(boxReg, 0), (int32_t)NULL_WORD);                   // Examine the displaced header
+    jcc   (Assembler::zero, DONE_LABEL);                              // 0 indicates recursive stack-lock
+  }
   movptr(tmpReg, Address(objReg, oopDesc::mark_offset_in_bytes())); // Examine the object's markword
-  testptr(tmpReg, markWord::monitor_value);                         // Inflated?
-  jccb  (Assembler::zero, Stacked);
+  if (LockingMode != LM_MONITOR) {
+    testptr(tmpReg, markWord::monitor_value);                         // Inflated?
+    jcc(Assembler::zero, Stacked);
+  }
 
   // It's inflated.
+
 #if INCLUDE_RTM_OPT
   if (use_rtm) {
     Label L_regular_inflated_unlock;
     int owner_offset = OM_OFFSET_NO_MONITOR_VALUE_TAG(owner);
     movptr(boxReg, Address(tmpReg, owner_offset));
     testptr(boxReg, boxReg);
     jccb(Assembler::notZero, L_regular_inflated_unlock);
     xend();
-    jmpb(DONE_LABEL);
+    jmp(DONE_LABEL);
     bind(L_regular_inflated_unlock);
   }
 #endif
 
   // Despite our balanced locking property we still check that m->_owner == Self
@@ -732,23 +751,14 @@
   xorptr(boxReg, boxReg);
   orptr(boxReg, Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(recursions)));
   jccb  (Assembler::notZero, DONE_LABEL);
   movptr(boxReg, Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(EntryList)));
   orptr(boxReg, Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(cxq)));
-  jccb  (Assembler::notZero, CheckSucc);
+  jccb  (Assembler::notZero, DONE_LABEL);
   movptr(Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)), NULL_WORD);
   jmpb  (DONE_LABEL);
 
-  bind (Stacked);
-  // It's not inflated and it's not recursively stack-locked and it's not biased.
-  // It must be stack-locked.
-  // Try to reset the header to displaced header.
-  // The "box" value on the stack is stable, so we can reload
-  // and be assured we observe the same value as above.
-  movptr(tmpReg, Address(boxReg, 0));
-  lock();
-  cmpxchgptr(tmpReg, Address(objReg, oopDesc::mark_offset_in_bytes())); // Uses RAX which is box
   // Intention fall-thru into DONE_LABEL
 
   // DONE_LABEL is a hot target - we'd really like to place it at the
   // start of cache line by padding with NOPs.
   // See the AMD and Intel software optimization manuals for the
@@ -828,19 +838,259 @@
 
   bind  (LSuccess);
   testl (boxReg, 0);                      // set ICC.ZF=1 to indicate success
   jmpb  (DONE_LABEL);
 
-  bind  (Stacked);
-  movptr(tmpReg, Address (boxReg, 0));      // re-fetch
-  lock();
-  cmpxchgptr(tmpReg, Address(objReg, oopDesc::mark_offset_in_bytes())); // Uses RAX which is box
-
 #endif
+  if (LockingMode == LM_LEGACY) {
+    bind  (Stacked);
+    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);
 }
 
+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);
+  // 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;
+
+  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, Register scr) {
   if (dst != src) {
@@ -3972,5 +4222,27 @@
   vpsllq(xtmp2, xtmp2, 0x1, vlen_enc);
   evpcmpb(ktmp, ktmp, shuffle,  xtmp2, Assembler::lt, true, vlen_enc);
   evshufi64x2(xtmp3, src, src, 0xFF, vlen_enc);
   evpshufb(dst, ktmp, xtmp3, shuffle, true, 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
@@ -35,18 +35,22 @@
   void restorevectmask(KRegister mask);
 
   // Code used by cmpFastLock and cmpFastUnlock mach instructions in .ad file.
   // See full desription in macroAssembler_x86.cpp.
   void fast_lock(Register obj, Register box, Register tmp,
-                 Register scr, Register cx1, Register cx2,
+                 Register scr, Register cx1, Register cx2, Register thread,
                  BiasedLockingCounters* counters,
                  RTMLockingCounters* rtm_counters,
                  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,
@@ -276,6 +280,8 @@
                      XMMRegister vec1, XMMRegister vec2, bool is_char, KRegister mask = knoreg);
 
   void rearrange_bytes(XMMRegister dst, XMMRegister shuffle, XMMRegister src, XMMRegister xtmp1,
                        XMMRegister xtmp2, XMMRegister xtmp3, Register rtmp, KRegister ktmp, 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/c2_safepointPollStubTable_x86.cpp b/src/hotspot/cpu/x86/c2_safepointPollStubTable_x86.cpp
--- a/src/hotspot/cpu/x86/c2_safepointPollStubTable_x86.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (c) 2020, 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 "asm/macroAssembler.hpp"
-#include "opto/compile.hpp"
-#include "opto/node.hpp"
-#include "opto/output.hpp"
-#include "runtime/sharedRuntime.hpp"
-
-#define __ masm.
-void C2SafepointPollStubTable::emit_stub_impl(MacroAssembler& masm, C2SafepointPollStub* entry) const {
-  assert(SharedRuntime::polling_page_return_handler_blob() != NULL,
-         "polling page return stub not created yet");
-  address stub = SharedRuntime::polling_page_return_handler_blob()->entry_point();
-
-  RuntimeAddress callback_addr(stub);
-
-  __ bind(entry->_stub_label);
-  InternalAddress safepoint_pc(masm.pc() - masm.offset() + entry->_safepoint_offset);
-#ifdef _LP64
-  __ lea(rscratch1, safepoint_pc);
-  __ movptr(Address(r15_thread, JavaThread::saved_exception_pc_offset()), rscratch1);
-#else
-  const Register tmp1 = rcx;
-  const Register tmp2 = rdx;
-  __ push(tmp1);
-  __ push(tmp2);
-
-  __ lea(tmp1, safepoint_pc);
-  __ get_thread(tmp2);
-  __ movptr(Address(tmp2, JavaThread::saved_exception_pc_offset()), tmp1);
-
-  __ pop(tmp2);
-  __ pop(tmp1);
-#endif
-  __ jump(callback_addr);
-}
-#undef __
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, 2021, 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.
@@ -65,11 +65,11 @@
   testptr(mdo_addr, TypeEntries::null_seen);
   jccb(Assembler::notZero, next); // null already seen. Nothing to do anymore.
   // atomic update to prevent overwriting Klass* with 0
   lock();
   orptr(mdo_addr, TypeEntries::null_seen);
-  jmpb(next);
+  jmp(next);
 
   bind(update);
   Register tmp_load_klass = LP64_ONLY(rscratch1) NOT_LP64(noreg);
   load_klass(obj, obj, tmp_load_klass);
 #ifdef _LP64
@@ -1215,11 +1215,11 @@
 //      rax, rbx
 void InterpreterMacroAssembler::lock_object(Register lock_reg) {
   assert(lock_reg == LP64_ONLY(c_rarg1) NOT_LP64(rdx),
          "The argument is only for looks. It must be c_rarg1");
 
-  if (UseHeavyMonitors) {
+  if (LockingMode == LM_MONITOR) {
     call_VM(noreg,
             CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
             lock_reg);
   } else {
     Label done;
@@ -1249,78 +1249,93 @@
 
     if (UseBiasedLocking) {
       biased_locking_enter(lock_reg, obj_reg, swap_reg, tmp_reg, rklass_decode_tmp, false, done, &slow_case);
     }
 
-    // Load immediate 1 into swap_reg %rax
-    movl(swap_reg, (int32_t)1);
+    if (LockingMode == LM_LIGHTWEIGHT) {
+#ifdef _LP64
+      const Register thread = r15_thread;
+#else
+      const Register thread = lock_reg;
+      get_thread(thread);
+#endif
+      lightweight_lock(obj_reg, swap_reg, thread, tmp_reg, slow_case);
+      jmp(done);
+    } else {
+      // Load immediate 1 into swap_reg %rax
+      movl(swap_reg, (int32_t)1);
 
-    // Load (object->mark() | 1) into swap_reg %rax
-    orptr(swap_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
+      // Load (object->mark() | 1) into swap_reg %rax
+      orptr(swap_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
 
-    // Save (object->mark() | 1) into BasicLock's displaced header
-    movptr(Address(lock_reg, mark_offset), swap_reg);
+      // Save (object->mark() | 1) into BasicLock's displaced header
+      movptr(Address(lock_reg, mark_offset), swap_reg);
 
-    assert(lock_offset == 0,
-           "displaced header must be first word in BasicObjectLock");
+      assert(lock_offset == 0,
+             "displaced header must be first word in BasicObjectLock");
 
-    lock();
-    cmpxchgptr(lock_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
-    if (PrintBiasedLockingStatistics) {
-      cond_inc32(Assembler::zero,
-                 ExternalAddress((address) BiasedLocking::fast_path_entry_count_addr()));
-    }
-    jcc(Assembler::zero, done);
-
-    const int zero_bits = LP64_ONLY(7) NOT_LP64(3);
-
-    // Fast check for recursive lock.
-    //
-    // Can apply the optimization only if this is a stack lock
-    // allocated in this thread. For efficiency, we can focus on
-    // recently allocated stack locks (instead of reading the stack
-    // base and checking whether 'mark' points inside the current
-    // thread stack):
-    //  1) (mark & zero_bits) == 0, and
-    //  2) rsp <= mark < mark + os::pagesize()
-    //
-    // Warning: rsp + os::pagesize can overflow the stack base. We must
-    // neither apply the optimization for an inflated lock allocated
-    // just above the thread stack (this is why condition 1 matters)
-    // nor apply the optimization if the stack lock is inside the stack
-    // of another thread. The latter is avoided even in case of overflow
-    // because we have guard pages at the end of all stacks. Hence, if
-    // we go over the stack base and hit the stack of another thread,
-    // this should not be in a writeable area that could contain a
-    // stack lock allocated by that thread. As a consequence, a stack
-    // lock less than page size away from rsp is guaranteed to be
-    // owned by the current thread.
-    //
-    // These 3 tests can be done by evaluating the following
-    // expression: ((mark - rsp) & (zero_bits - os::vm_page_size())),
-    // assuming both stack pointer and pagesize have their
-    // least significant bits clear.
-    // NOTE: the mark is in swap_reg %rax as the result of cmpxchg
-    subptr(swap_reg, rsp);
-    andptr(swap_reg, zero_bits - os::vm_page_size());
-
-    // Save the test result, for recursive case, the result is zero
-    movptr(Address(lock_reg, mark_offset), swap_reg);
-
-    if (PrintBiasedLockingStatistics) {
-      cond_inc32(Assembler::zero,
-                 ExternalAddress((address) BiasedLocking::fast_path_entry_count_addr()));
+      lock();
+      cmpxchgptr(lock_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
+      if (PrintBiasedLockingStatistics) {
+        cond_inc32(Assembler::zero,
+                   ExternalAddress((address) BiasedLocking::fast_path_entry_count_addr()));
+      }
+      jcc(Assembler::zero, done);
+
+      const int zero_bits = LP64_ONLY(7) NOT_LP64(3);
+
+      // Fast check for recursive lock.
+      //
+      // Can apply the optimization only if this is a stack lock
+      // allocated in this thread. For efficiency, we can focus on
+      // recently allocated stack locks (instead of reading the stack
+      // base and checking whether 'mark' points inside the current
+      // thread stack):
+      //  1) (mark & zero_bits) == 0, and
+      //  2) rsp <= mark < mark + os::pagesize()
+      //
+      // Warning: rsp + os::pagesize can overflow the stack base. We must
+      // neither apply the optimization for an inflated lock allocated
+      // just above the thread stack (this is why condition 1 matters)
+      // nor apply the optimization if the stack lock is inside the stack
+      // of another thread. The latter is avoided even in case of overflow
+      // because we have guard pages at the end of all stacks. Hence, if
+      // we go over the stack base and hit the stack of another thread,
+      // this should not be in a writeable area that could contain a
+      // stack lock allocated by that thread. As a consequence, a stack
+      // lock less than page size away from rsp is guaranteed to be
+      // owned by the current thread.
+      //
+      // These 3 tests can be done by evaluating the following
+      // expression: ((mark - rsp) & (zero_bits - os::vm_page_size())),
+      // assuming both stack pointer and pagesize have their
+      // least significant bits clear.
+      // NOTE: the mark is in swap_reg %rax as the result of cmpxchg
+      subptr(swap_reg, rsp);
+      andptr(swap_reg, zero_bits - os::vm_page_size());
+
+      // Save the test result, for recursive case, the result is zero
+      movptr(Address(lock_reg, mark_offset), swap_reg);
+
+      if (PrintBiasedLockingStatistics) {
+        cond_inc32(Assembler::zero,
+                   ExternalAddress((address) BiasedLocking::fast_path_entry_count_addr()));
+      }
+      jcc(Assembler::zero, done);
     }
-    jcc(Assembler::zero, done);
-
     bind(slow_case);
 
     // Call the runtime routine for slow case
-    call_VM(noreg,
-            CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
-            lock_reg);
-
+    if (LockingMode == LM_LIGHTWEIGHT) {
+      call_VM(noreg,
+              CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter_obj),
+              obj_reg);
+    } else {
+      call_VM(noreg,
+              CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
+              lock_reg);
+    }
     bind(done);
   }
 }
 
 
@@ -1338,53 +1353,67 @@
 // rax, rbx, rcx, rdx
 void InterpreterMacroAssembler::unlock_object(Register lock_reg) {
   assert(lock_reg == LP64_ONLY(c_rarg1) NOT_LP64(rdx),
          "The argument is only for looks. It must be c_rarg1");
 
-  if (UseHeavyMonitors) {
+  if (LockingMode == LM_MONITOR) {
     call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit), lock_reg);
   } else {
-    Label done;
+    Label done, slow_case;
 
     const Register swap_reg   = rax;  // Must use rax for cmpxchg instruction
     const Register header_reg = LP64_ONLY(c_rarg2) NOT_LP64(rbx);  // Will contain the old oopMark
     const Register obj_reg    = LP64_ONLY(c_rarg3) NOT_LP64(rcx);  // Will contain the oop
 
     save_bcp(); // Save in case of exception
 
-    // Convert from BasicObjectLock structure to object and BasicLock
-    // structure Store the BasicLock address into %rax
-    lea(swap_reg, Address(lock_reg, BasicObjectLock::lock_offset_in_bytes()));
+    if (LockingMode != LM_LIGHTWEIGHT) {
+      // Convert from BasicObjectLock structure to object and BasicLock
+      // structure Store the BasicLock address into %rax
+      lea(swap_reg, Address(lock_reg, BasicObjectLock::lock_offset_in_bytes()));
+    }
 
     // Load oop into obj_reg(%c_rarg3)
     movptr(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset_in_bytes()));
 
     // Free entry
     movptr(Address(lock_reg, BasicObjectLock::obj_offset_in_bytes()), (int32_t)NULL_WORD);
 
-    if (UseBiasedLocking) {
-      biased_locking_exit(obj_reg, header_reg, done);
-    }
-
-    // Load the old header from BasicLock structure
-    movptr(header_reg, Address(swap_reg,
-                               BasicLock::displaced_header_offset_in_bytes()));
+    if (LockingMode == LM_LIGHTWEIGHT) {
+#ifdef _LP64
+      lightweight_unlock(obj_reg, swap_reg, r15_thread, header_reg, 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(swap_reg);
+      lightweight_unlock(obj_reg, swap_reg, swap_reg, header_reg, slow_case);
+#endif
+      jmp(done);
+    } else {
+      if (UseBiasedLocking) {
+        biased_locking_exit(obj_reg, header_reg, done);
+      }
 
-    // Test for recursion
-    testptr(header_reg, header_reg);
+      // Load the old header from BasicLock structure
+      movptr(header_reg, Address(swap_reg,
+                                 BasicLock::displaced_header_offset_in_bytes()));
 
-    // zero for recursive case
-    jcc(Assembler::zero, done);
+      // Test for recursion
+      testptr(header_reg, header_reg);
 
-    // Atomic swap back the old header
-    lock();
-    cmpxchgptr(header_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
+      // zero for recursive case
+      jcc(Assembler::zero, done);
 
-    // zero for simple unlock of a stack-lock case
-    jcc(Assembler::zero, done);
+      // Atomic swap back the old header
+      lock();
+      cmpxchgptr(header_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
 
+      // zero for simple unlock of a stack-lock case
+      jcc(Assembler::zero, done);
+    }
 
+    bind(slow_case);
     // Call the runtime routine for slow case.
     movptr(Address(lock_reg, BasicObjectLock::obj_offset_in_bytes()), obj_reg); // restore obj
     call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit), lock_reg);
 
     bind(done);
diff a/src/hotspot/cpu/x86/macroAssembler_x86.cpp b/src/hotspot/cpu/x86/macroAssembler_x86.cpp
--- a/src/hotspot/cpu/x86/macroAssembler_x86.cpp
+++ b/src/hotspot/cpu/x86/macroAssembler_x86.cpp
@@ -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.
@@ -52,10 +52,16 @@
 #include "runtime/stubRoutines.hpp"
 #include "runtime/thread.hpp"
 #include "utilities/macros.hpp"
 #include "crc32c.h"
 
+#ifdef COMPILER2
+#include "opto/c2_CodeStubs.hpp"
+#include "opto/compile.hpp"
+#include "opto/output.hpp"
+#endif
+
 #ifdef PRODUCT
 #define BLOCK_COMMENT(str) /* nothing */
 #define STOP(error) stop(error)
 #else
 #define BLOCK_COMMENT(str) block_comment(str)
@@ -3769,16 +3775,29 @@
 }
 
 // Preserves the contents of address, destroys the contents length_in_bytes and temp.
 void MacroAssembler::zero_memory(Register address, Register length_in_bytes, int offset_in_bytes, Register temp) {
   assert(address != length_in_bytes && address != temp && temp != length_in_bytes, "registers must be different");
-  assert((offset_in_bytes & (BytesPerWord - 1)) == 0, "offset must be a multiple of BytesPerWord");
+  assert((offset_in_bytes & (BytesPerInt - 1)) == 0, "offset must be a multiple of BytesPerInt");
   Label done;
 
   testptr(length_in_bytes, length_in_bytes);
   jcc(Assembler::zero, done);
 
+  // Emit single 32bit store to clear leading bytes, if necessary.
+  xorptr(temp, temp);    // use _zero reg to clear memory (shorter code)
+#ifdef _LP64
+  if (!is_aligned(offset_in_bytes, BytesPerWord)) {
+    movl(Address(address, offset_in_bytes), temp);
+    offset_in_bytes += BytesPerInt;
+    decrement(length_in_bytes, BytesPerInt);
+  }
+  assert((offset_in_bytes & (BytesPerWord - 1)) == 0, "offset must be a multiple of BytesPerWord");
+  testptr(length_in_bytes, length_in_bytes);
+  jcc(Assembler::zero, done);
+#endif
+
   // initialize topmost word, divide index by 2, check if odd and test if zero
   // note: for the remaining code to work, index must be a multiple of BytesPerWord
 #ifdef ASSERT
   {
     Label L;
@@ -3787,11 +3806,10 @@
     stop("length must be a multiple of BytesPerWord");
     bind(L);
   }
 #endif
   Register index = length_in_bytes;
-  xorptr(temp, temp);    // use _zero reg to clear memory (shorter code)
   if (UseIncDec) {
     shrptr(index, 3);  // divide by 8/16 and set carry flag if bit 2 was set
   } else {
     shrptr(index, 2);  // use 2 instructions to avoid partial flag stall
     shrptr(index, 1);
@@ -4731,16 +4749,45 @@
   movptr(holder, Address(method, Method::const_offset()));                      // ConstMethod*
   movptr(holder, Address(holder, ConstMethod::constants_offset()));             // ConstantPool*
   movptr(holder, Address(holder, ConstantPool::pool_holder_offset_in_bytes())); // InstanceKlass*
 }
 
-void MacroAssembler::load_klass(Register dst, Register src, Register tmp) {
+#ifdef _LP64
+void MacroAssembler::load_nklass(Register dst, Register src) {
+  assert(UseCompressedClassPointers, "expect compressed class pointers");
+
+  if (!UseCompactObjectHeaders) {
+    movl(dst, Address(src, oopDesc::klass_offset_in_bytes()));
+    return;
+  }
+
+ 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, bool null_check_src) {
   assert_different_registers(src, tmp);
   assert_different_registers(dst, tmp);
+  if (null_check_src) {
+    if (UseCompactObjectHeaders) {
+      null_check(src, oopDesc::mark_offset_in_bytes());
+    } else {
+      null_check(src, oopDesc::klass_offset_in_bytes());
+    }
+  }
 #ifdef _LP64
   if (UseCompressedClassPointers) {
-    movl(dst, Address(src, oopDesc::klass_offset_in_bytes()));
+    load_nklass(dst, src);
     decode_klass_not_null(dst, tmp);
   } else
 #endif
     movptr(dst, Address(src, oopDesc::klass_offset_in_bytes()));
 }
@@ -4749,19 +4796,59 @@
   load_klass(dst, src, tmp);
   movptr(dst, Address(dst, Klass::prototype_header_offset()));
 }
 
 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);
     movl(Address(dst, oopDesc::klass_offset_in_bytes()), src);
   } else
 #endif
-    movptr(Address(dst, oopDesc::klass_offset_in_bytes()), src);
+   movptr(Address(dst, oopDesc::klass_offset_in_bytes()), src);
+}
+
+void MacroAssembler::cmp_klass(Register klass, Register obj, Register tmp) {
+#ifdef _LP64
+  if (UseCompactObjectHeaders) {
+    // NOTE: We need to deal with possible ObjectMonitor in object header.
+    // Eventually we might be able to do simple movl & cmpl like in
+    // the CCP path below.
+    load_nklass(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) {
+    // NOTE: We need to deal with possible ObjectMonitor in object header.
+    // Eventually we might be able to do simple movl & cmpl like in
+    // the CCP path below.
+    assert(tmp2 != noreg, "need tmp2");
+    assert_different_registers(src, dst, tmp1, tmp2);
+    load_nklass(tmp1, src);
+    load_nklass(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();
@@ -8684,5 +8771,120 @@
     pop(rax);
   }
 }
 
 #endif // !WIN32 || _LP64
+
+// Implements lightweight-locking.
+//
+// obj: the object to be locked
+// reg_rax: rax
+// thread: the thread which attempts to lock obj
+// tmp: a temporary register
+void MacroAssembler::lightweight_lock(Register obj, Register reg_rax, Register thread, Register tmp, Label& slow) {
+  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);
+
+  // 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.
+//
+// obj: the object to be unlocked
+// reg_rax: rax
+// thread: the thread
+// tmp: a temporary register
+//
+// 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 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
+  // 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, 2022, 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.
@@ -146,10 +146,12 @@
 
   // Support for inc/dec with optimal instruction selection depending on value
 
   void increment(Register reg, int value = 1) { LP64_ONLY(incrementq(reg, value)) NOT_LP64(incrementl(reg, value)) ; }
   void decrement(Register reg, int value = 1) { LP64_ONLY(decrementq(reg, value)) NOT_LP64(decrementl(reg, value)) ; }
+  void increment(Address dst, int value = 1)  { LP64_ONLY(incrementq(dst, value)) NOT_LP64(incrementl(dst, value)) ; }
+  void decrement(Address dst, int value = 1)  { LP64_ONLY(decrementq(dst, value)) NOT_LP64(decrementl(dst, value)) ; }
 
   void decrementl(Address dst, int value = 1);
   void decrementl(Register reg, int value = 1);
 
   void decrementq(Register reg, int value = 1);
@@ -337,13 +339,24 @@
   void load_method_holder_cld(Register rresult, Register rmethod);
 
   void load_method_holder(Register holder, Register method);
 
   // oop manipulations
-  void load_klass(Register dst, Register src, Register tmp);
+  void load_klass(Register dst, Register src, Register tmp, bool null_check_src = false);
+#ifdef _LP64
+  void load_nklass(Register dst, Register src);
+#endif
   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 t1 and t2 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 src,
                        Register tmp1, Register tmp2);
 
@@ -1911,10 +1924,13 @@
 #endif // COMPILER2_OR_JVMCI
 
 #endif // _LP64
 
   void vallones(XMMRegister dst, int vector_len);
+
+  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/methodHandles_x86.cpp b/src/hotspot/cpu/x86/methodHandles_x86.cpp
--- a/src/hotspot/cpu/x86/methodHandles_x86.cpp
+++ b/src/hotspot/cpu/x86/methodHandles_x86.cpp
@@ -362,12 +362,11 @@
       if (iid == vmIntrinsics::_linkToSpecial) {
         // Don't actually load the klass; just null-check the receiver.
         __ null_check(receiver_reg);
       } else {
         // load receiver klass itself
-        __ null_check(receiver_reg, oopDesc::klass_offset_in_bytes());
-        __ load_klass(temp1_recv_klass, receiver_reg, temp2);
+        __ load_klass(temp1_recv_klass, receiver_reg, temp2, true);
         __ verify_klass_ptr(temp1_recv_klass);
       }
       BLOCK_COMMENT("check_receiver {");
       // The receiver for the MemberName must be in receiver_reg.
       // Check the receiver against the MemberName.clazz
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
@@ -23,10 +23,11 @@
  */
 
 #include "precompiled.hpp"
 #include "asm/macroAssembler.hpp"
 #include "runtime/sharedRuntime.hpp"
+#include "utilities/globalDefinitions.hpp"
 #include "vmreg_x86.inline.hpp"
 #ifdef COMPILER1
 #include "c1/c1_Runtime1.hpp"
 #endif //COMPILER1
 
@@ -56,13 +57,20 @@
     __ bind(Continue);
   }
 
   __ movptr(result, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
 
-  // check if locked
-  __ testptr(result, markWord::unlocked_value);
-  __ jcc(Assembler::zero, slowCase);
+
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    // check if monitor
+    __ testptr(result, markWord::monitor_value);
+    __ jcc(Assembler::notZero, slowCase);
+  } else {
+    // check if locked
+    __ testptr(result, markWord::unlocked_value);
+    __ jcc(Assembler::zero, slowCase);
+  }
 
   if (UseBiasedLocking) {
     // Check if biased and fall through to runtime if so
     __ testptr(result, markWord::biased_lock_bit_in_place);
     __ jcc(Assembler::notZero, slowCase);
@@ -71,12 +79,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, 2021, 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.
@@ -1820,45 +1820,52 @@
     __ lea(lock_reg, Address(rbp, lock_slot_rbp_offset));
 
     // Load the oop from the handle
     __ movptr(obj_reg, Address(oop_handle_reg, 0));
 
-    if (UseBiasedLocking) {
-      // Note that oop_handle_reg is trashed during this call
-      __ biased_locking_enter(lock_reg, obj_reg, swap_reg, oop_handle_reg, noreg, false, lock_done, &slow_path_lock);
-    }
+    if (LockingMode == LM_MONITOR) {
+      __ jmp(slow_path_lock);
+    } else if (LockingMode == LM_LEGACY) {
+      if (UseBiasedLocking) {
+        // Note that oop_handle_reg is trashed during this call
+        __ biased_locking_enter(lock_reg, obj_reg, swap_reg, oop_handle_reg, noreg, false, lock_done, &slow_path_lock);
+      }
 
-    // Load immediate 1 into swap_reg %rax,
-    __ movptr(swap_reg, 1);
+      // Load immediate 1 into swap_reg %rax,
+      __ movptr(swap_reg, 1);
 
-    // Load (object->mark() | 1) into swap_reg %rax,
-    __ orptr(swap_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
+      // Load (object->mark() | 1) into swap_reg %rax,
+      __ orptr(swap_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
 
-    // Save (object->mark() | 1) into BasicLock's displaced header
-    __ movptr(Address(lock_reg, mark_word_offset), swap_reg);
+      // Save (object->mark() | 1) into BasicLock's displaced header
+      __ movptr(Address(lock_reg, mark_word_offset), swap_reg);
 
-    // src -> dest iff dest == rax, else rax, <- dest
-    // *obj_reg = lock_reg iff *obj_reg == rax, else rax, = *(obj_reg)
-    __ lock();
-    __ cmpxchgptr(lock_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
-    __ jcc(Assembler::equal, lock_done);
+      // src -> dest iff dest == rax, else rax, <- dest
+      // *obj_reg = lock_reg iff *obj_reg == rax, else rax, = *(obj_reg)
+      __ lock();
+      __ cmpxchgptr(lock_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
+      __ jcc(Assembler::equal, lock_done);
 
-    // Test if the oopMark is an obvious stack pointer, i.e.,
-    //  1) (mark & 3) == 0, and
-    //  2) rsp <= mark < mark + os::pagesize()
-    // These 3 tests can be done by evaluating the following
-    // expression: ((mark - rsp) & (3 - os::vm_page_size())),
-    // assuming both stack pointer and pagesize have their
-    // least significant 2 bits clear.
-    // NOTE: the oopMark is in swap_reg %rax, as the result of cmpxchg
+      // Test if the oopMark is an obvious stack pointer, i.e.,
+      //  1) (mark & 3) == 0, and
+      //  2) rsp <= mark < mark + os::pagesize()
+      // These 3 tests can be done by evaluating the following
+      // expression: ((mark - rsp) & (3 - os::vm_page_size())),
+      // assuming both stack pointer and pagesize have their
+      // least significant 2 bits clear.
+      // NOTE: the oopMark is in swap_reg %rax, as the result of cmpxchg
 
-    __ subptr(swap_reg, rsp);
-    __ andptr(swap_reg, 3 - os::vm_page_size());
+      __ subptr(swap_reg, rsp);
+      __ andptr(swap_reg, 3 - os::vm_page_size());
 
-    // 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);
+      // Save the test result, for recursive case, the result is zero
+      __ movptr(Address(lock_reg, mark_word_offset), swap_reg);
+      __ jcc(Assembler::notEqual, slow_path_lock);
+    } else {
+      assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+      __ lightweight_lock(obj_reg, swap_reg, thread, lock_reg, slow_path_lock);
+    }
     // Slow path will re-enter here
     __ bind(lock_done);
 
     if (UseBiasedLocking) {
       // Re-fetch oop_handle_reg as we trashed it above
@@ -1994,32 +2001,41 @@
 
     if (UseBiasedLocking) {
       __ biased_locking_exit(obj_reg, rbx, done);
     }
 
-    // Simple recursive lock?
+    if (LockingMode == LM_LEGACY) {
+      // Simple recursive lock?
 
-    __ cmpptr(Address(rbp, lock_slot_rbp_offset), (int32_t)NULL_WORD);
-    __ jcc(Assembler::equal, done);
+      __ cmpptr(Address(rbp, lock_slot_rbp_offset), (int32_t)NULL_WORD);
+      __ jcc(Assembler::equal, done);
+    }
 
     // Must save rax, if if it is live now because cmpxchg must use it
     if (ret_type != T_FLOAT && ret_type != T_DOUBLE && ret_type != T_VOID) {
       save_native_result(masm, ret_type, stack_slots);
     }
 
-    //  get old displaced header
-    __ movptr(rbx, Address(rbp, lock_slot_rbp_offset));
-
-    // get address of the stack lock
-    __ lea(rax, Address(rbp, lock_slot_rbp_offset));
-
-    // Atomic swap old header if oop still contains the stack lock
-    // src -> dest iff dest == rax, else rax, <- dest
-    // *obj_reg = rbx, iff *obj_reg == rax, else rax, = *(obj_reg)
-    __ lock();
-    __ cmpxchgptr(rbx, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
-    __ jcc(Assembler::notEqual, slow_path_unlock);
+    if (LockingMode == LM_MONITOR) {
+      __ jmp(slow_path_unlock);
+    } else if (LockingMode == LM_LEGACY) {
+      //  get old displaced header
+      __ movptr(rbx, Address(rbp, lock_slot_rbp_offset));
+
+      // get address of the stack lock
+      __ lea(rax, Address(rbp, lock_slot_rbp_offset));
+
+      // Atomic swap old header if oop still contains the stack lock
+      // src -> dest iff dest == rax, else rax, <- dest
+      // *obj_reg = rbx, iff *obj_reg == rax, else rax, = *(obj_reg)
+      __ lock();
+      __ cmpxchgptr(rbx, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
+      __ jcc(Assembler::notEqual, slow_path_unlock);
+    } else {
+      assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+      __ lightweight_unlock(obj_reg, swap_reg, thread, lock_reg, slow_path_unlock);
+    }
 
     // slow path re-enters here
     __ bind(unlock_done);
     if (ret_type != T_FLOAT && ret_type != T_DOUBLE && ret_type != T_VOID) {
       restore_native_result(masm, ret_type, stack_slots);
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, 2022, 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.
@@ -2069,45 +2069,52 @@
     __ lea(lock_reg, Address(rsp, lock_slot_offset * VMRegImpl::stack_slot_size));
 
     // Load the oop from the handle
     __ movptr(obj_reg, Address(oop_handle_reg, 0));
 
-    if (UseBiasedLocking) {
-      __ biased_locking_enter(lock_reg, obj_reg, swap_reg, rscratch1, rscratch2, false, lock_done, &slow_path_lock);
-    }
+    if (LockingMode == LM_MONITOR) {
+      __ jmp(slow_path_lock);
+    } else if (LockingMode == LM_LEGACY) {
+      if (UseBiasedLocking) {
+        __ biased_locking_enter(lock_reg, obj_reg, swap_reg, rscratch1, rscratch2, false, lock_done, &slow_path_lock);
+      }
 
-    // Load immediate 1 into swap_reg %rax
-    __ movl(swap_reg, 1);
+      // Load immediate 1 into swap_reg %rax
+      __ movl(swap_reg, 1);
 
-    // Load (object->mark() | 1) into swap_reg %rax
-    __ orptr(swap_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
+      // Load (object->mark() | 1) into swap_reg %rax
+      __ orptr(swap_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
 
-    // Save (object->mark() | 1) into BasicLock's displaced header
-    __ movptr(Address(lock_reg, mark_word_offset), swap_reg);
+      // Save (object->mark() | 1) into BasicLock's displaced header
+      __ movptr(Address(lock_reg, mark_word_offset), swap_reg);
 
-    // src -> dest iff dest == rax else rax <- dest
-    __ lock();
-    __ cmpxchgptr(lock_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
-    __ jcc(Assembler::equal, lock_done);
+      // src -> dest iff dest == rax else rax <- dest
+      __ lock();
+      __ cmpxchgptr(lock_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
+      __ jcc(Assembler::equal, lock_done);
 
-    // Hmm should this move to the slow path code area???
+      // Hmm should this move to the slow path code area???
 
-    // Test if the oopMark is an obvious stack pointer, i.e.,
-    //  1) (mark & 3) == 0, and
-    //  2) rsp <= mark < mark + os::pagesize()
-    // These 3 tests can be done by evaluating the following
-    // expression: ((mark - rsp) & (3 - os::vm_page_size())),
-    // assuming both stack pointer and pagesize have their
-    // least significant 2 bits clear.
-    // NOTE: the oopMark is in swap_reg %rax as the result of cmpxchg
+      // Test if the oopMark is an obvious stack pointer, i.e.,
+      //  1) (mark & 3) == 0, and
+      //  2) rsp <= mark < mark + os::pagesize()
+      // These 3 tests can be done by evaluating the following
+      // expression: ((mark - rsp) & (3 - os::vm_page_size())),
+      // assuming both stack pointer and pagesize have their
+      // least significant 2 bits clear.
+      // NOTE: the oopMark is in swap_reg %rax as the result of cmpxchg
 
-    __ subptr(swap_reg, rsp);
-    __ andptr(swap_reg, 3 - os::vm_page_size());
+      __ subptr(swap_reg, rsp);
+      __ andptr(swap_reg, 3 - os::vm_page_size());
 
-    // 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);
+      // Save the test result, for recursive case, the result is zero
+      __ movptr(Address(lock_reg, mark_word_offset), swap_reg);
+      __ jcc(Assembler::notEqual, slow_path_lock);
+    } else {
+      assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+      __ lightweight_lock(obj_reg, swap_reg, r15_thread, rscratch1, slow_path_lock);
+    }
 
     // Slow path will re-enter here
 
     __ bind(lock_done);
   }
@@ -2228,30 +2235,38 @@
 
     if (UseBiasedLocking) {
       __ biased_locking_exit(obj_reg, old_hdr, done);
     }
 
-    // Simple recursive lock?
+    if (LockingMode == LM_LEGACY) {
+      // Simple recursive lock?
 
-    __ cmpptr(Address(rsp, lock_slot_offset * VMRegImpl::stack_slot_size), (int32_t)NULL_WORD);
-    __ jcc(Assembler::equal, done);
+      __ cmpptr(Address(rsp, lock_slot_offset * VMRegImpl::stack_slot_size), (int32_t)NULL_WORD);
+      __ jcc(Assembler::equal, done);
+    }
 
     // Must save rax if if it is live now because cmpxchg must use it
     if (ret_type != T_FLOAT && ret_type != T_DOUBLE && ret_type != T_VOID) {
       save_native_result(masm, ret_type, stack_slots);
     }
 
-
-    // get address of the stack lock
-    __ lea(rax, Address(rsp, lock_slot_offset * VMRegImpl::stack_slot_size));
-    //  get old displaced header
-    __ movptr(old_hdr, Address(rax, 0));
-
-    // Atomic swap old header if oop still contains the stack lock
-    __ lock();
-    __ cmpxchgptr(old_hdr, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
-    __ jcc(Assembler::notEqual, slow_path_unlock);
+    if (LockingMode == LM_MONITOR) {
+      __ jmp(slow_path_unlock);
+    } else if (LockingMode == LM_LEGACY) {
+      // get address of the stack lock
+      __ lea(rax, Address(rsp, lock_slot_offset * VMRegImpl::stack_slot_size));
+      //  get old displaced header
+      __ movptr(old_hdr, Address(rax, 0));
+
+      // Atomic swap old header if oop still contains the stack lock
+      __ lock();
+      __ cmpxchgptr(old_hdr, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
+      __ jcc(Assembler::notEqual, slow_path_unlock);
+    } else {
+      assert(LockingMode == LM_LIGHTWEIGHT, "must be");
+      __ lightweight_unlock(obj_reg, swap_reg, r15_thread, lock_reg, slow_path_unlock);
+    }
 
     // slow path re-enters here
     __ bind(unlock_done);
     if (ret_type != T_FLOAT && ret_type != T_DOUBLE && ret_type != T_VOID) {
       restore_native_result(masm, ret_type, stack_slots);
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
@@ -3655,13 +3655,12 @@
   __ jump_from_interpreted(method, rax);
 
   __ bind(notFinal);
 
   // get receiver klass
-  __ null_check(recv, oopDesc::klass_offset_in_bytes());
   Register tmp_load_klass = LP64_ONLY(rscratch1) NOT_LP64(noreg);
-  __ load_klass(rax, recv, tmp_load_klass);
+  __ load_klass(rax, recv, tmp_load_klass, true);
 
   // profile this call
   __ profile_virtual_call(rax, rlocals, rdx);
   // get target Method* & entry point
   __ lookup_virtual_method(rax, index, method);
@@ -3748,13 +3747,12 @@
   __ movl(rlocals, rdx);
   __ andl(rlocals, (1 << ConstantPoolCacheEntry::is_vfinal_shift));
   __ jcc(Assembler::zero, notVFinal);
 
   // Get receiver klass into rlocals - also a null check
-  __ null_check(rcx, oopDesc::klass_offset_in_bytes());
   Register tmp_load_klass = LP64_ONLY(rscratch1) NOT_LP64(noreg);
-  __ load_klass(rlocals, rcx, tmp_load_klass);
+  __ load_klass(rlocals, rcx, tmp_load_klass, true);
 
   Label subtype;
   __ check_klass_subtype(rlocals, rax, rbcp, subtype);
   // If we get here the typecheck failed
   recvKlass = rdx;
@@ -3772,12 +3770,11 @@
   // no return from above
   __ bind(notVFinal);
 
   // Get receiver klass into rdx - also a null check
   __ restore_locals();  // restore r14
-  __ null_check(rcx, oopDesc::klass_offset_in_bytes());
-  __ load_klass(rdx, rcx, tmp_load_klass);
+  __ load_klass(rdx, rcx, tmp_load_klass, true);
 
   Label no_such_method;
 
   // Preserve method for throw_AbstractMethodErrorVerbose.
   __ mov(rcx, rbx);
@@ -3991,11 +3988,12 @@
   // there is an initialize need. Otherwise, skip and go to the slow path.
   if (UseTLAB || allow_shared_alloc) {
     // The object is initialized before the header.  If the object size is
     // zero, go directly to the header initialization.
     __ bind(initialize_object);
-    __ decrement(rdx, sizeof(oopDesc));
+    int header_size = align_up(oopDesc::base_offset_in_bytes(), BytesPerLong);
+    __ decrement(rdx, header_size);
     __ 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)
@@ -4013,33 +4011,34 @@
 #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));
+    __ 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));
     __ decrement(rdx);
     __ jcc(Assembler::notZero, loop);
     }
 
     // initialize object header only.
     __ bind(initialize_header);
-    if (UseBiasedLocking) {
+    if (UseBiasedLocking || 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.
     }
+    if (!UseCompactObjectHeaders) {
 #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
-    Register tmp_store_klass = LP64_ONLY(rscratch1) NOT_LP64(noreg);
-    __ store_klass(rax, rcx, tmp_store_klass);  // klass
+      __ store_klass(rax, rcx, rscratch1);  // klass
+    }
 
     {
       SkipIfEqual skip_if(_masm, &DTraceAllocProbes, 0);
       // 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, 2022, 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.
@@ -1008,10 +1008,14 @@
     return LP64_ONLY(true) NOT_LP64(false); // not implemented on x86_32
   }
 
   constexpr static bool supports_stack_watermark_barrier() {
     return true;
+  }
+
+  constexpr static bool supports_recursive_lightweight_locking() {
+    return true;
   }
 
   // there are several insns to force cache line sync to memory which
   // we can use to ensure mapped non-volatile memory is up to date with
   // pending in-cache changes.
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
@@ -719,11 +719,13 @@
     MacroAssembler masm(&cbuf);
     __ get_thread(thread);
     Label dummy_label;
     Label* code_stub = &dummy_label;
     if (!C->output()->in_scratch_emit_size()) {
-      code_stub = &C->output()->safepoint_poll_table()->add_safepoint(__ offset());
+      C2SafepointPollStub* stub = new (C->comp_arena()) C2SafepointPollStub(__ offset());
+      C->output()->add_stub(stub);
+      code_stub = &stub->entry();
     }
     __ relocate(relocInfo::poll_return_type);
     __ safepoint_poll(*code_stub, thread, true /* at_return */, true /* in_nmethod */);
   }
 }
@@ -13693,51 +13695,78 @@
   ins_pipe( pipe_jmp );
 %}
 
 // inlined locking and unlocking
 
-instruct cmpFastLockRTM(eFlagsReg cr, eRegP object, eBXRegP box, eAXRegI tmp, eDXRegI scr, rRegI cx1, rRegI cx2) %{
+instruct cmpFastLockRTM(eFlagsReg cr, eRegP object, eBXRegP box, eAXRegI tmp, eDXRegI scr, rRegI cx1, rRegI cx2, eRegP thread) %{
   predicate(Compile::current()->use_rtm());
   match(Set cr (FastLock object box));
-  effect(TEMP tmp, TEMP scr, TEMP cx1, TEMP cx2, USE_KILL box);
+  effect(TEMP tmp, TEMP scr, TEMP cx1, TEMP cx2, USE_KILL box, TEMP thread);
   ins_cost(300);
   format %{ "FASTLOCK $object,$box\t! kills $box,$tmp,$scr,$cx1,$cx2" %}
   ins_encode %{
+    __ get_thread($thread$$Register);
     __ fast_lock($object$$Register, $box$$Register, $tmp$$Register,
-                 $scr$$Register, $cx1$$Register, $cx2$$Register,
+                 $scr$$Register, $cx1$$Register, $cx2$$Register, $thread$$Register,
                  _counters, _rtm_counters, _stack_rtm_counters,
                  ((Method*)(ra_->C->method()->constant_encoding()))->method_data(),
                  true, ra_->C->profile_rtm());
   %}
   ins_pipe(pipe_slow);
 %}
 
-instruct cmpFastLock(eFlagsReg cr, eRegP object, eBXRegP box, eAXRegI tmp, eRegP scr) %{
-  predicate(!Compile::current()->use_rtm());
+instruct cmpFastLock(eFlagsReg cr, eRegP object, eBXRegP box, eAXRegI tmp, eRegP scr, eRegP thread) %{
+  predicate(LockingMode != LM_LIGHTWEIGHT && !Compile::current()->use_rtm());
   match(Set cr (FastLock object box));
-  effect(TEMP tmp, TEMP scr, USE_KILL 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 %{
+    __ get_thread($thread$$Register);
     __ fast_lock($object$$Register, $box$$Register, $tmp$$Register,
-                 $scr$$Register, noreg, noreg, _counters, NULL, NULL, NULL, false, false);
+                 $scr$$Register, noreg, noreg, $thread$$Register, _counters, NULL, NULL, NULL, false, false);
   %}
   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);
+%}
 
 // ============================================================================
 // Safepoint Instruction
 instruct safePoint_poll_tls(eFlagsReg cr, eRegP_no_EBP poll) %{
   match(SafePoint poll);
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
@@ -996,11 +996,13 @@
   if (do_polling() && C->is_method_compilation()) {
     MacroAssembler _masm(&cbuf);
     Label dummy_label;
     Label* code_stub = &dummy_label;
     if (!C->output()->in_scratch_emit_size()) {
-      code_stub = &C->output()->safepoint_poll_table()->add_safepoint(__ offset());
+      C2SafepointPollStub* stub = new (C->comp_arena()) C2SafepointPollStub(__ offset());
+      C->output()->add_stub(stub);
+      code_stub = &stub->entry();
     }
     __ relocate(relocInfo::poll_return_type);
     __ safepoint_poll(*code_stub, r15_thread, true /* at_return */, true /* in_nmethod */);
   }
 }
@@ -5188,20 +5190,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));
 
@@ -12223,10 +12241,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);
@@ -12921,42 +12940,67 @@
   effect(TEMP tmp, TEMP scr, TEMP cx1, TEMP cx2, USE_KILL box);
   ins_cost(300);
   format %{ "fastlock $object,$box\t! kills $box,$tmp,$scr,$cx1,$cx2" %}
   ins_encode %{
     __ fast_lock($object$$Register, $box$$Register, $tmp$$Register,
-                 $scr$$Register, $cx1$$Register, $cx2$$Register,
+                 $scr$$Register, $cx1$$Register, $cx2$$Register, r15_thread,
                  _counters, _rtm_counters, _stack_rtm_counters,
                  ((Method*)(ra_->C->method()->constant_encoding()))->method_data(),
                  true, ra_->C->profile_rtm());
   %}
   ins_pipe(pipe_slow);
 %}
 
 instruct cmpFastLock(rFlagsReg cr, rRegP object, rbx_RegP box, rax_RegI tmp, rRegP scr, rRegP cx1) %{
-  predicate(!Compile::current()->use_rtm());
+  predicate(LockingMode != LM_LIGHTWEIGHT && !Compile::current()->use_rtm());
   match(Set cr (FastLock object box));
   effect(TEMP tmp, TEMP scr, TEMP cx1, USE_KILL box);
   ins_cost(300);
   format %{ "fastlock $object,$box\t! kills $box,$tmp,$scr" %}
   ins_encode %{
     __ fast_lock($object$$Register, $box$$Register, $tmp$$Register,
-                 $scr$$Register, $cx1$$Register, noreg, _counters, NULL, NULL, NULL, false, false);
+                 $scr$$Register, $cx1$$Register, noreg, r15_thread, _counters, NULL, NULL, NULL, false, false);
   %}
   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/cpu/zero/vm_version_zero.cpp b/src/hotspot/cpu/zero/vm_version_zero.cpp
--- a/src/hotspot/cpu/zero/vm_version_zero.cpp
+++ b/src/hotspot/cpu/zero/vm_version_zero.cpp
@@ -113,10 +113,15 @@
   if (UseVectorizedMismatchIntrinsic) {
     warning("vectorizedMismatch intrinsic is not available on this CPU.");
     FLAG_SET_DEFAULT(UseVectorizedMismatchIntrinsic, false);
   }
 
+  if ((LockingMode != LM_LEGACY) && (LockingMode != LM_MONITOR)) {
+    warning("Unsupported locking mode for this CPU.");
+    FLAG_SET_DEFAULT(LockingMode, LM_LEGACY);
+  }
+
   // Not implemented
   UNSUPPORTED_OPTION(CriticalJNINatives);
   UNSUPPORTED_OPTION(UseCompiler);
 #ifdef ASSERT
   UNSUPPORTED_OPTION(CountCompiledCalls);
diff a/src/hotspot/cpu/zero/zeroInterpreter_zero.cpp b/src/hotspot/cpu/zero/zeroInterpreter_zero.cpp
--- a/src/hotspot/cpu/zero/zeroInterpreter_zero.cpp
+++ b/src/hotspot/cpu/zero/zeroInterpreter_zero.cpp
@@ -331,11 +331,11 @@
   if (method->is_synchronized()) {
     monitor = (BasicObjectLock*) istate->stack_base();
     oop lockee = monitor->obj();
     markWord disp = lockee->mark().set_unlocked();
     monitor->lock()->set_displaced_header(disp);
-    bool call_vm = UseHeavyMonitors;
+    bool call_vm = (LockingMode == LM_MONITOR);
     if (call_vm || lockee->cas_set_mark(markWord::from_pointer(monitor), disp) != disp) {
       // Is it simple recursive case?
       if (!call_vm && thread->is_lock_owned((address) disp.clear_lock_bits().to_pointer())) {
         monitor->lock()->set_displaced_header(markWord::from_pointer(NULL));
       } else {
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
@@ -559,6 +559,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
@@ -926,10 +926,11 @@
       LIR_OpLoadKlass* opLoadKlass = op->as_OpLoadKlass();
       assert(opLoadKlass != NULL, "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;
     }
 
 
@@ -1108,10 +1109,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);
@@ -2092,10 +2096,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
@@ -1916,17 +1916,19 @@
 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;
 };
@@ -2395,11 +2397,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
@@ -635,11 +635,11 @@
 void LIRGenerator::monitor_exit(LIR_Opr object, LIR_Opr lock, LIR_Opr new_hdr, LIR_Opr scratch, int monitor_no) {
   if (!GenerateSynchronizationCode) return;
   // setup registers
   LIR_Opr hdr = lock;
   lock = new_hdr;
-  CodeStub* slow_path = new MonitorExitStub(lock, UseFastLocking, monitor_no);
+  CodeStub* slow_path = new MonitorExitStub(lock, LockingMode != LM_MONITOR, monitor_no);
   __ load_stack_address_monitor(monitor_no, lock);
   __ unlock_object(hdr, object, lock, scratch, slow_path);
 }
 
 #ifndef PRODUCT
@@ -1256,11 +1256,12 @@
                                      NULL); // 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) : NULL;
+  __ 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/c1/c1_Runtime1.cpp b/src/hotspot/share/c1/c1_Runtime1.cpp
--- a/src/hotspot/share/c1/c1_Runtime1.cpp
+++ b/src/hotspot/share/c1/c1_Runtime1.cpp
@@ -697,15 +697,15 @@
 JRT_END
 
 
 JRT_BLOCK_ENTRY(void, Runtime1::monitorenter(JavaThread* current, oopDesc* obj, BasicObjectLock* lock))
   NOT_PRODUCT(_monitorenter_slowcase_cnt++;)
-  if (!UseFastLocking) {
+  if (LockingMode == LM_MONITOR) {
     lock->set_obj(obj);
   }
-  assert(obj == lock->obj(), "must match");
-  SharedRuntime::monitor_enter_helper(obj, lock->lock(), current);
+  assert(LockingMode == LM_LIGHTWEIGHT || obj == lock->obj(), "must match");
+  SharedRuntime::monitor_enter_helper(obj, LockingMode == LM_LIGHTWEIGHT ? NULL : lock->lock(), current);
 JRT_END
 
 
 JRT_LEAF(void, Runtime1::monitorexit(JavaThread* current, BasicObjectLock* lock))
   NOT_PRODUCT(_monitorexit_slowcase_cnt++;)
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
@@ -37,10 +37,11 @@
 #include "logging/logStream.hpp"
 #include "memory/allStatic.hpp"
 #include "memory/memRegion.hpp"
 #include "memory/resourceArea.hpp"
 #include "oops/instanceKlass.hpp"
+#include "oops/klass.inline.hpp"
 #include "oops/objArrayKlass.hpp"
 #include "oops/oopHandle.inline.hpp"
 #include "runtime/arguments.hpp"
 #include "runtime/globals_extension.hpp"
 #include "runtime/sharedRuntime.hpp"
@@ -729,10 +730,17 @@
 
 void ArchiveBuilder::make_klasses_shareable() {
   for (int i = 0; i < klasses()->length(); i++) {
     Klass* k = 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.
     } else if (k->is_typeArray_klass()) {
       k->remove_unshareable_info();
@@ -770,15 +778,21 @@
 }
 
 // Update a Java object to point its Klass* to the new location after
 // shared archive has been compacted.
 void ArchiveBuilder::relocate_klass_ptr(oop o) {
+#ifdef _LP64
   assert(DumpSharedSpaces, "sanity");
   Klass* k = get_relocated_klass(o->klass());
   Klass* requested_k = to_requested(k);
   narrowKlass nk = CompressedKlassPointers::encode_not_null(requested_k, _requested_static_archive_bottom);
-  o->set_narrow_klass(nk);
+  if (UseCompactObjectHeaders) {
+    o->set_mark(o->mark().set_narrow_klass(nk));
+  } else {
+    o->set_narrow_klass(nk);
+  }
+#endif // _LP64
 }
 
 // RelocateBufferToRequested --- Relocate all the pointers in rw/ro,
 // so that the archive can be mapped to the "requested" location without runtime relocation.
 //
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
@@ -215,10 +215,11 @@
   }
   _version = CURRENT_CDS_ARCHIVE_VERSION;
   _core_region_alignment = core_region_alignment;
   _obj_alignment = ObjectAlignmentInBytes;
   _compact_strings = CompactStrings;
+  _compact_headers = UseCompactObjectHeaders;
   if (HeapShared::is_heap_object_archiving_allowed()) {
     _narrow_oop_mode = CompressedOops::mode();
     _narrow_oop_base = CompressedOops::base();
     _narrow_oop_shift = CompressedOops::shift();
     _heap_begin = CompressedOops::begin();
@@ -277,10 +278,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);
@@ -2256,10 +2258,18 @@
     FileMapInfo::fail_continue("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
@@ -186,10 +186,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
@@ -251,10 +252,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/cds/heapShared.cpp b/src/hotspot/share/cds/heapShared.cpp
--- a/src/hotspot/share/cds/heapShared.cpp
+++ b/src/hotspot/share/cds/heapShared.cpp
@@ -56,10 +56,11 @@
 #include "runtime/fieldDescriptor.inline.hpp"
 #include "runtime/globals_extension.hpp"
 #include "runtime/init.hpp"
 #include "runtime/java.hpp"
 #include "runtime/javaCalls.hpp"
+#include "runtime/safepoint.hpp"
 #include "runtime/safepointVerifiers.hpp"
 #include "utilities/bitMap.inline.hpp"
 #include "utilities/copy.hpp"
 #if INCLUDE_G1GC
 #include "gc/g1/g1CollectedHeap.hpp"
@@ -273,11 +274,20 @@
     // We need to retain the identity_hash, because it may have been used by some hashtables
     // in the shared heap. This also has the side effect of pre-initializing the
     // identity_hash for all shared objects, so they are less likely to be written
     // into during run time, increasing the potential of memory sharing.
     int hash_original = obj->identity_hash();
-    archived_oop->set_mark(markWord::prototype().copy_set_hash(hash_original));
+    if (UseCompactObjectHeaders) {
+      markWord mark = obj->mark();
+      if (mark.has_displaced_mark_helper()) {
+        mark = mark.displaced_mark_helper();
+      }
+      narrowKlass nklass = mark.narrow_klass();
+      archived_oop->set_mark(markWord::prototype().copy_set_hash(hash_original) LP64_ONLY(.set_narrow_klass(nklass)));
+    } else {
+      archived_oop->set_mark(markWord::prototype().copy_set_hash(hash_original));
+    }
     assert(archived_oop->mark().is_unlocked(), "sanity");
 
     DEBUG_ONLY(int hash_archived = archived_oop->identity_hash());
     assert(hash_original == hash_archived, "Different hash codes: original %x, archived %x", hash_original, hash_archived);
 
@@ -416,14 +426,18 @@
   memset(mem, 0, size * BytesPerWord);
   {
     // This is copied from MemAllocator::finish
     if (UseBiasedLocking) {
       oopDesc::set_mark(mem, k->prototype_header());
+    } else if (UseCompactObjectHeaders) {
+      oopDesc::release_set_mark(mem, k->prototype_header());
     } else {
       oopDesc::set_mark(mem, markWord::prototype());
     }
-    oopDesc::release_set_klass(mem, k);
+    if (!UseCompactObjectHeaders) {
+      oopDesc::release_set_klass(mem, k);
+    }
   }
   {
     // This is copied from ObjArrayAllocator::initialize
     arrayOopDesc::set_length(mem, length);
   }
diff a/src/hotspot/share/ci/ciKlass.cpp b/src/hotspot/share/ci/ciKlass.cpp
--- a/src/hotspot/share/ci/ciKlass.cpp
+++ b/src/hotspot/share/ci/ciKlass.cpp
@@ -247,5 +247,25 @@
 const char* ciKlass::external_name() const {
   GUARDED_VM_ENTRY(
     return get_Klass()->external_name();
   )
 }
+
+// ------------------------------------------------------------------
+// ciKlass::prototype_header_offset
+juint ciKlass::prototype_header_offset() {
+  assert(is_loaded(), "must be loaded");
+
+  VM_ENTRY_MARK;
+  Klass* this_klass = get_Klass();
+  return in_bytes(this_klass->prototype_header_offset());
+}
+
+// ------------------------------------------------------------------
+// ciKlass::prototype_header
+uintptr_t ciKlass::prototype_header() {
+  assert(is_loaded(), "must be loaded");
+
+  VM_ENTRY_MARK;
+  Klass* this_klass = get_Klass();
+  return (uintptr_t)this_klass->prototype_header().to_pointer();
+}
diff a/src/hotspot/share/ci/ciKlass.hpp b/src/hotspot/share/ci/ciKlass.hpp
--- a/src/hotspot/share/ci/ciKlass.hpp
+++ b/src/hotspot/share/ci/ciKlass.hpp
@@ -127,8 +127,11 @@
   virtual ciKlass* exact_klass() = 0;
 
   void print_name_on(outputStream* st);
 
   const char* external_name() const;
+
+  juint prototype_header_offset();
+  uintptr_t prototype_header();
 };
 
 #endif // SHARE_CI_CIKLASS_HPP
diff a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp
--- a/src/hotspot/share/classfile/classFileParser.cpp
+++ b/src/hotspot/share/classfile/classFileParser.cpp
@@ -2077,11 +2077,15 @@
   }
   if (has_annotation(_jdk_internal_ValueBased)) {
     ik->set_has_value_based_class_annotation();
     if (DiagnoseSyncOnValueBasedClasses) {
       ik->set_is_value_based();
-      ik->set_prototype_header(markWord::prototype());
+      if (UseCompactObjectHeaders) {
+        ik->set_prototype_header(markWord::prototype() LP64_ONLY(.set_klass(ik)));
+      } else {
+        ik->set_prototype_header(markWord::prototype());
+      }
     }
   }
 }
 
 #define MAX_ARGS_SIZE 255
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
@@ -80,10 +80,11 @@
 #include "gc/shared/generationSpec.hpp"
 #include "gc/shared/isGCActiveMark.hpp"
 #include "gc/shared/locationPrinter.inline.hpp"
 #include "gc/shared/oopStorageParState.hpp"
 #include "gc/shared/preservedMarks.inline.hpp"
+#include "gc/shared/slidingForwarding.hpp"
 #include "gc/shared/suspendibleThreadSet.hpp"
 #include "gc/shared/referenceProcessor.inline.hpp"
 #include "gc/shared/taskTerminator.hpp"
 #include "gc/shared/taskqueue.inline.hpp"
 #include "gc/shared/tlab_globals.hpp"
@@ -1772,10 +1773,12 @@
 
   _regions_failed_evacuation = NEW_C_HEAP_ARRAY(volatile bool, max_regions(), mtGC);
 
   G1InitLogger::print();
 
+  SlidingForwarding::initialize(heap_rs.region(), HeapRegion::GrainWords);
+
   return JNI_OK;
 }
 
 void G1CollectedHeap::stop() {
   // Stop all concurrent threads. We do this to make sure these threads
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/g1Policy.hpp"
 #include "gc/g1/g1RegionMarkStatsCache.inline.hpp"
 #include "gc/shared/gcTraceTime.inline.hpp"
 #include "gc/shared/preservedMarks.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/biasedLocking.hpp"
@@ -199,15 +200,19 @@
   verify_after_marking();
 
   // Don't add any more derived pointers during later phases
   deactivate_derived_pointers();
 
+  SlidingForwarding::begin();
+
   phase2_prepare_compaction();
 
   phase3_adjust_pointers();
 
   phase4_do_compaction();
+
+  SlidingForwarding::end();
 }
 
 void G1FullCollector::complete_collection() {
   // Restore all marks.
   restore_marks();
@@ -310,10 +315,11 @@
   }
 }
 
 void G1FullCollector::phase2_prepare_compaction() {
   GCTraceTime(Info, gc, phases) info("Phase 2: Prepare for compaction", scope()->timer());
+
   G1FullGCPrepareTask task(this);
   run_task(&task);
 
   // To avoid OOM when there is memory left.
   if (!task.has_freed_regions()) {
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
@@ -37,14 +37,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);
   }
@@ -59,21 +60,31 @@
     _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_closed_archive() && !r->is_free()) {
       // Closed archive regions never change references and only contain
       // references into other closed regions and are always live. Free
       // regions do not contain objects to iterate. So skip both.
-      G1AdjustLiveClosure adjust(&cl);
+      G1AdjustLiveClosure<ALT_FWD> adjust(&cl);
       r->apply_to_marked_objects(_bitmap, &adjust);
     }
     return false;
   }
 };
@@ -81,36 +92,45 @@
 G1FullGCAdjustTask::G1FullGCAdjustTask(G1FullCollector* collector) :
     G1FullGCTask("G1 Adjust", collector),
     _root_processor(G1CollectedHeap::heap(), collector->workers()),
     _references_done(false),
     _weak_proc_task(collector->workers()),
-    _hrclaimer(collector->workers()),
-    _adjust(collector) {
+    _hrclaimer(collector->workers()) {
   // Need cleared claim bits for the roots processing
   ClassLoaderDataGraph::clear_claimed_marks();
 }
 
-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.
   if (!Atomic::cmpxchg(&_references_done, false, true)) {
-    G1CollectedHeap::heap()->ref_processor_stw()->weak_oops_do(&_adjust);
+    G1CollectedHeap::heap()->ref_processor_stw()->weak_oops_do(&adjust);
   }
 
   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_strong);
-  CodeBlobToOopClosure adjust_code(&_adjust, CodeBlobToOopClosure::FixRelocations);
-  _root_processor.process_all_roots(&_adjust, &adjust_cld, &adjust_code);
+  CLDToOopClosure adjust_cld(&adjust, ClassLoaderData::_claim_strong);
+  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
@@ -37,12 +37,13 @@
 class G1FullGCAdjustTask : public G1FullGCTask {
   G1RootProcessor          _root_processor;
   volatile bool            _references_done;
   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
@@ -29,10 +29,11 @@
 #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"
 
 // Do work for all skip-compacting regions.
@@ -56,18 +57,20 @@
     r->reset_skip_compacting_after_full_gc();
     return false;
   }
 };
 
-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();
-  HeapWord* destination = cast_from_oop<HeapWord*>(obj->forwardee());
-  if (destination == NULL) {
+  if (!SlidingForwarding::is_forwarded(obj)) {
     // Object not moving
     return size;
   }
 
+  HeapWord* destination = cast_from_oop<HeapWord*>(SlidingForwarding::forwardee<ALT_FWD>(obj));
+
   // copy object and reinit its mark
   HeapWord* obj_addr = cast_from_oop<HeapWord*>(obj);
   assert(obj_addr != destination, "everything in this pass should be moving");
   Copy::aligned_conjoint_words(obj_addr, destination, size);
   cast_to_oop(destination)->init_mark();
@@ -77,12 +80,17 @@
 }
 
 void G1FullGCCompactTask::compact_region(HeapRegion* hr) {
   assert(!hr->is_pinned(), "Should be no pinned region in compaction queue");
   assert(!hr->is_humongous(), "Should be no humongous regions in compaction queue");
-  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);
+  }
   // Clear the liveness information for this region if necessary i.e. if we actually look at it
   // for bitmap verification. Otherwise it is sufficient that we move the TAMS to bottom().
   if (G1VerifyBitmaps) {
     collector()->mark_bitmap()->clear_region(hr);
   }
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
@@ -46,10 +46,11 @@
     G1FullGCTask("G1 Compact Task", collector),
     _claimer(collector->workers()) { }
   void work(uint worker_id);
   void serial_compaction();
 
+  template <bool ALT_FWD>
   class G1CompactRegionClosure : public StackObj {
     G1CMBitMap* _bitmap;
 
   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
@@ -23,10 +23,11 @@
  */
 
 #include "precompiled.hpp"
 #include "gc/g1/g1FullGCCompactionPoint.hpp"
 #include "gc/g1/heapRegion.hpp"
+#include "gc/shared/slidingForwarding.inline.hpp"
 #include "oops/oop.inline.hpp"
 #include "utilities/debug.hpp"
 
 G1FullGCCompactionPoint::G1FullGCCompactionPoint() :
     _current_region(NULL),
@@ -91,22 +92,25 @@
   // Get the next region and re-initialize the values.
   _current_region = next_region();
   initialize_values(true);
 }
 
+template <bool ALT_FWD>
 void G1FullGCCompactionPoint::forward(oop object, size_t size) {
   assert(_current_region != NULL, "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));
+    SlidingForwarding::forward_to<ALT_FWD>(object, cast_to_oop(_compaction_top));
   } else {
+    assert(!SlidingForwarding::is_forwarded(object), "should not be forwarded");
+    /*
     if (object->forwardee() != NULL) {
       // Object should not move but mark-word is used so it looks like the
       // object is forwarded. Need to clear the mark and it's no problem
       // since it will be restored by preserved marks. There is an exception
       // with BiasedLocking, in this case forwardee() will return NULL
@@ -121,19 +125,23 @@
              (UseBiasedLocking && object->has_bias_pattern()), // Will be restored by BiasedLocking
              "should have correct prototype obj: " PTR_FORMAT " mark: " PTR_FORMAT " prototype: " PTR_FORMAT,
              p2i(object), object->mark().value(), markWord::prototype_for_klass(object->klass()).value());
     }
     assert(object->forwardee() == NULL, "should be forwarded to NULL");
+    */
   }
 
   // Update compaction values.
   _compaction_top += size;
   if (_compaction_top > _threshold) {
     _threshold = _current_region->cross_threshold(_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);
 }
 
 HeapRegion* G1FullGCCompactionPoint::remove_last() {
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
@@ -49,10 +49,11 @@
 
   bool has_regions();
   bool is_initialized();
   void initialize(HeapRegion* hr, bool init_threshold);
   void update();
+  template <bool ALT_FWD>
   void forward(oop object, size_t size);
   void add(HeapRegion* hr);
 
   HeapRegion* remove_last();
   HeapRegion* current_region();
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
@@ -75,10 +75,11 @@
   virtual bool do_metadata();
   virtual void do_klass(Klass* k);
   virtual void do_cld(ClassLoaderData* cld);
 };
 
+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
@@ -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.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"
@@ -61,11 +62,12 @@
 
 inline void G1MarkAndPushClosure::do_cld(ClassLoaderData* cld) {
   _marker->follow_cld(cld);
 }
 
-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;
   }
 
@@ -75,28 +77,32 @@
     // We never forward objects in non-compacting regions so there is no need to
     // process them further.
     return;
   }
 
-  oop forwardee = obj->forwardee();
-  if (forwardee == NULL) {
+  if (!SlidingForwarding::is_forwarded(obj)) {
     // Not forwarded, return current reference.
+    /*
     assert(obj->mark() == markWord::prototype_for_klass(obj->klass()) || // Correct mark
            obj->mark_must_be_preserved() || // Will be restored by PreservedMarksSet
            (UseBiasedLocking && obj->has_bias_pattern()), // Will be restored by BiasedLocking
            "Must have correct prototype or be preserved, obj: " PTR_FORMAT ", mark: " PTR_FORMAT ", prototype: " PTR_FORMAT,
            p2i(obj), obj->mark().value(), markWord::prototype_for_klass(obj->klass()).value());
+    */
     return;
   }
 
   // Forwarded, just update.
+  oop forwardee = SlidingForwarding::forwardee<ALT_FWD>(obj);
   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) || _collector->is_skip_marking(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
@@ -32,10 +32,11 @@
 #include "gc/g1/g1FullGCPrepareTask.hpp"
 #include "gc/g1/g1HotCardCache.hpp"
 #include "gc/g1/heapRegion.inline.hpp"
 #include "gc/shared/gcTraceTime.inline.hpp"
 #include "gc/shared/referenceProcessor.hpp"
+#include "gc/shared/slidingForwarding.inline.hpp"
 #include "logging/log.hpp"
 #include "memory/iterator.inline.hpp"
 #include "oops/oop.inline.hpp"
 #include "utilities/ticks.hpp"
 
@@ -154,39 +155,50 @@
   if (hcc->use_cache()) {
     hcc->reset_card_counts(hr);
   }
 }
 
-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;
 }
 
-size_t G1FullGCPrepareTask::G1RePrepareClosure::apply(oop obj) {
+template <bool ALT_FWD>
+size_t G1FullGCPrepareTask::G1RePrepareClosure<ALT_FWD>::apply(oop obj) {
   // We only re-prepare objects forwarded within the current region, so
   // skip objects that are already forwarded to another region.
-  oop forwarded_to = obj->forwardee();
-  if (forwarded_to != NULL && !_current->is_in(forwarded_to)) {
-    return obj->size();
+  if (SlidingForwarding::is_forwarded(obj)) {
+    oop forwarded_to = SlidingForwarding::forwardee<ALT_FWD>(obj);
+    assert(forwarded_to != NULL, "must have forwardee");
+    if (!_current->is_in(forwarded_to)) {
+      return obj->size();
+    }
   }
-
   // Get size and forward.
   size_t size = obj->size();
-  _cp->forward(obj, size);
+  _cp->forward<ALT_FWD>(obj, size);
 
   return size;
 }
 
 void G1FullGCPrepareTask::G1CalculatePointersClosure::prepare_for_compaction_work(G1FullGCCompactionPoint* cp,
                                                                                   HeapRegion* hr) {
-  G1PrepareCompactLiveClosure prepare_compact(cp);
-  hr->set_compaction_top(hr->bottom());
-  hr->apply_to_marked_objects(_bitmap, &prepare_compact);
+  if (UseAltGCForwarding) {
+    G1PrepareCompactLiveClosure<true> prepare_compact(cp);
+    hr->set_compaction_top(hr->bottom());
+    hr->apply_to_marked_objects(_bitmap, &prepare_compact);
+  } else {
+    G1PrepareCompactLiveClosure<false> prepare_compact(cp);
+    hr->set_compaction_top(hr->bottom());
+    hr->apply_to_marked_objects(_bitmap, &prepare_compact);
+  }
 }
 
 void G1FullGCPrepareTask::G1CalculatePointersClosure::prepare_for_compaction(HeapRegion* hr) {
   if (!_cp->is_initialized()) {
     hr->set_compaction_top(hr->bottom());
@@ -195,11 +207,12 @@
   // Add region to the compaction queue and prepare it.
   _cp->add(hr);
   prepare_for_compaction_work(_cp, hr);
 }
 
-void G1FullGCPrepareTask::prepare_serial_compaction() {
+template <bool ALT_FWD>
+void G1FullGCPrepareTask::prepare_serial_compaction_impl() {
   GCTraceTime(Debug, gc, phases) debug("Phase 2: Prepare Serial Compaction", collector()->scope()->timer());
   // At this point we know that no regions were completely freed by
   // the parallel compaction. That means that the last region of
   // all compaction queues still have data in them. We try to compact
   // these regions in serial to avoid a premature OOM.
@@ -219,18 +232,26 @@
       // Initialize the compaction point. Nothing more is needed for the first heap region
       // since it is already prepared for compaction.
       cp->initialize(current, false);
     } else {
       assert(!current->is_humongous(), "Should be no humongous regions in compaction queue");
-      G1RePrepareClosure re_prepare(cp, current);
+      G1RePrepareClosure<ALT_FWD> re_prepare(cp, current);
       current->set_compaction_top(current->bottom());
       current->apply_to_marked_objects(collector()->mark_bitmap(), &re_prepare);
     }
   }
   cp->update();
 }
 
+void G1FullGCPrepareTask::prepare_serial_compaction() {
+  if (UseAltGCForwarding) {
+    prepare_serial_compaction_impl<true>();
+  } else {
+    prepare_serial_compaction_impl<false>();
+  }
+}
+
 bool G1FullGCPrepareTask::G1CalculatePointersClosure::freed_regions() {
   if (_regions_freed) {
     return true;
   }
 
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
@@ -40,10 +40,14 @@
   volatile bool     _freed_regions;
   HeapRegionClaimer _hrclaimer;
 
   void set_freed_regions();
 
+private:
+  template <bool ALT_FWD>
+  void prepare_serial_compaction_impl();
+
 public:
   G1FullGCPrepareTask(G1FullCollector* collector);
   void work(uint worker_id);
   void prepare_serial_compaction();
   bool has_freed_regions();
@@ -72,18 +76,20 @@
 
     bool do_heap_region(HeapRegion* hr);
     bool freed_regions();
   };
 
+  template <bool ALT_FWD>
   class G1PrepareCompactLiveClosure : public StackObj {
     G1FullGCCompactionPoint* _cp;
 
   public:
     G1PrepareCompactLiveClosure(G1FullGCCompactionPoint* cp);
     size_t apply(oop object);
   };
 
+  template <bool ALT_FWD>
   class G1RePrepareClosure : public StackObj {
     G1FullGCCompactionPoint* _cp;
     HeapRegion* _current;
 
   public:
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
@@ -232,11 +232,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 != NULL, "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
@@ -195,11 +195,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);
 
@@ -209,11 +209,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(UseCompactObjectHeaders || from_obj->is_objArray(), "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");
@@ -239,11 +239,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(UseCompactObjectHeaders || from_obj->is_objArray(), "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");
 
@@ -358,26 +358,26 @@
   }
   return dest(region_attr);
 }
 
 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 = NULL;
   // Try slow-path allocation unless we're allocating old and old is already full.
@@ -396,11 +396,11 @@
   }
   if (obj_ptr != NULL) {
     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;
 }
 
@@ -421,11 +421,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);
@@ -434,11 +440,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 == NULL) {
-    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 == NULL) {
       // This will either forward-to-self, or detect that someone else has
       // installed a forwarding pointer.
       return handle_evacuation_failure_par(old, old_mark);
     }
@@ -591,11 +597,11 @@
 
 NOINLINE
 oop G1ParScanThreadState::handle_evacuation_failure_par(oop old, markWord m) {
   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 == NULL) {
     // Forward-to-self succeeded. We are the "owner" of the object.
     HeapRegion* r = _g1h->heap_region_containing(old);
 
     if (_g1h->notify_region_failed_evacuation(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
@@ -170,11 +170,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,
@@ -203,11 +203,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/g1/heapRegion.cpp b/src/hotspot/share/gc/g1/heapRegion.cpp
--- a/src/hotspot/share/gc/g1/heapRegion.cpp
+++ b/src/hotspot/share/gc/g1/heapRegion.cpp
@@ -858,14 +858,23 @@
 HeapWord* HeapRegion::cross_threshold(HeapWord* start, HeapWord* end) {
   _bot_part.alloc_block(start, end);
   return _bot_part.threshold();
 }
 
-void HeapRegion::object_iterate(ObjectClosure* blk) {
+template<bool RESOLVE>
+void HeapRegion::object_iterate_impl(ObjectClosure* blk) {
   HeapWord* p = bottom();
   while (p < top()) {
     if (block_is_obj(p)) {
       blk->do_object(cast_to_oop(p));
     }
-    p += block_size(p);
+    p += block_size<RESOLVE>(p);
+  }
+}
+
+void HeapRegion::object_iterate(ObjectClosure* blk) {
+  if (!UseCompactObjectHeaders || G1CollectedHeap::heap()->collector_state()->in_full_gc()) {
+    object_iterate_impl<false>(blk);
+  } else {
+    object_iterate_impl<true>(blk);
   }
 }
diff a/src/hotspot/share/gc/g1/heapRegion.hpp b/src/hotspot/share/gc/g1/heapRegion.hpp
--- a/src/hotspot/share/gc/g1/heapRegion.hpp
+++ b/src/hotspot/share/gc/g1/heapRegion.hpp
@@ -143,10 +143,13 @@
   // Returns NULL if not possible, otherwise sets actual_word_size to the amount of
   // space allocated.
   // This version synchronizes with other calls to par_allocate_impl().
   inline HeapWord* par_allocate_impl(size_t min_word_size, size_t desired_word_size, size_t* actual_word_size);
 
+  template<bool RESOLVE>
+  void object_iterate_impl(ObjectClosure* blk);
+
 public:
   HeapWord* block_start(const void* p);
 
   void object_iterate(ObjectClosure* blk);
 
@@ -181,10 +184,11 @@
   // is not marked (bitmap).
   bool is_obj_dead(const oop obj, const G1CMBitMap* const prev_bitmap) const;
 
   // Returns the object size for all valid block starts
   // and the amount of unallocated words if called on top()
+  template<bool RESOLVE = false>
   size_t block_size(const HeapWord* p) const;
 
   // Scans through the region using the bitmap to determine what
   // objects to call size_t ApplyToMarkedClosure::apply(oop) for.
   template<typename ApplyToMarkedClosure>
diff a/src/hotspot/share/gc/g1/heapRegion.inline.hpp b/src/hotspot/share/gc/g1/heapRegion.inline.hpp
--- a/src/hotspot/share/gc/g1/heapRegion.inline.hpp
+++ b/src/hotspot/share/gc/g1/heapRegion.inline.hpp
@@ -175,17 +175,31 @@
   return !obj_allocated_since_prev_marking(obj) &&
          !prev_bitmap->is_marked(obj) &&
          !is_closed_archive();
 }
 
+template <bool RESOLVE>
 inline size_t HeapRegion::block_size(const HeapWord *addr) const {
   if (addr == top()) {
     return pointer_delta(end(), addr);
   }
 
   if (block_is_obj(addr)) {
-    return cast_to_oop(addr)->size();
+    oop obj = cast_to_oop(addr);
+#ifdef _LP64
+#ifdef ASSERT
+    if (RESOLVE) {
+      assert(UseCompactObjectHeaders && !G1CollectedHeap::heap()->collector_state()->in_full_gc(), "Illegal/excessive resolve during full-GC");
+    } else {
+      assert(!UseCompactObjectHeaders || G1CollectedHeap::heap()->collector_state()->in_full_gc() || !obj->is_forwarded(), "Missing resolve when forwarded during normal GC");
+    }
+#endif
+    if (RESOLVE && obj->is_forwarded()) {
+      obj = obj->forwardee();
+    }
+#endif
+    return obj->size();
   }
 
   return block_size_using_bitmap(addr, G1CollectedHeap::heap()->concurrent_mark()->prev_mark_bitmap());
 }
 
diff a/src/hotspot/share/gc/parallel/mutableNUMASpace.cpp b/src/hotspot/share/gc/parallel/mutableNUMASpace.cpp
--- a/src/hotspot/share/gc/parallel/mutableNUMASpace.cpp
+++ b/src/hotspot/share/gc/parallel/mutableNUMASpace.cpp
@@ -107,11 +107,11 @@
           CollectedHeap::fill_with_object(cur_top, words_to_fill);
           if (!os::numa_has_static_binding()) {
             size_t touched_words = words_to_fill;
 #ifndef ASSERT
             if (!ZapUnusedHeapArea) {
-              touched_words = MIN2((size_t)align_object_size(typeArrayOopDesc::header_size(T_INT)),
+              touched_words = MIN2((size_t)align_object_size(align_up(typeArrayOopDesc::base_offset_in_bytes(T_INT), HeapWordSize) / HeapWordSize),
                 touched_words);
             }
 #endif
             MemRegion invalid;
             HeapWord *crossing_start = align_up(cur_top, os::vm_page_size());
diff a/src/hotspot/share/gc/parallel/mutableSpace.cpp b/src/hotspot/share/gc/parallel/mutableSpace.cpp
--- a/src/hotspot/share/gc/parallel/mutableSpace.cpp
+++ b/src/hotspot/share/gc/parallel/mutableSpace.cpp
@@ -231,15 +231,40 @@
   while (obj_addr < t) {
     obj_addr += cast_to_oop(obj_addr)->oop_iterate_size(cl);
   }
 }
 
-void MutableSpace::object_iterate(ObjectClosure* cl) {
+template<bool COMPACT_HEADERS>
+void MutableSpace::object_iterate_impl(ObjectClosure* cl) {
   HeapWord* p = bottom();
   while (p < top()) {
-    cl->do_object(cast_to_oop(p));
-    p += cast_to_oop(p)->size();
+    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);
+      p += obj->size();
+    } else {
+      assert(obj->forwardee() != obj, "must not be self-forwarded");
+      if (COMPACT_HEADERS) {
+        // It is safe to use the forwardee here. Parallel GC only uses
+        // header-based forwarding during promotion. Full GC doesn't
+        // use the object header for forwarding at all.
+        p += obj->forwardee()->size();
+      } else {
+        p += obj->size();
+      }
+    }
+  }
+}
+
+void MutableSpace::object_iterate(ObjectClosure* cl) {
+  if (UseCompactObjectHeaders) {
+    object_iterate_impl<true>(cl);
+  } else {
+    object_iterate_impl<false>(cl);
   }
 }
 
 void MutableSpace::print_short() const { print_short_on(tty); }
 void MutableSpace::print_short_on( outputStream* st) const {
diff a/src/hotspot/share/gc/parallel/mutableSpace.hpp b/src/hotspot/share/gc/parallel/mutableSpace.hpp
--- a/src/hotspot/share/gc/parallel/mutableSpace.hpp
+++ b/src/hotspot/share/gc/parallel/mutableSpace.hpp
@@ -65,10 +65,13 @@
   void numa_setup_pages(MemRegion mr, bool clear_space);
 
   void set_last_setup_region(MemRegion mr) { _last_setup_region = mr;   }
   MemRegion last_setup_region() const      { return _last_setup_region; }
 
+  template<bool COMPACT_HEADERS>
+  void object_iterate_impl(ObjectClosure* cl);
+
  public:
   virtual ~MutableSpace();
   MutableSpace(size_t page_size);
 
   // Accessors
diff a/src/hotspot/share/gc/parallel/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
@@ -406,11 +406,11 @@
  public:
   VerifyObjectStartArrayClosure(PSOldGen* old_gen, ObjectStartArray* start_array) :
     _old_gen(old_gen), _start_array(start_array) { }
 
   virtual void do_object(oop obj) {
-    HeapWord* test_addr = cast_from_oop<HeapWord*>(obj) + 1;
+    HeapWord* test_addr = cast_from_oop<HeapWord*>(obj);
     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/psPromotionLAB.cpp b/src/hotspot/share/gc/parallel/psPromotionLAB.cpp
--- a/src/hotspot/share/gc/parallel/psPromotionLAB.cpp
+++ b/src/hotspot/share/gc/parallel/psPromotionLAB.cpp
@@ -45,11 +45,11 @@
   set_end(end);
   set_top(bottom);
 
   // Initialize after VM starts up because header_size depends on compressed
   // oops.
-  filler_header_size = align_object_size(typeArrayOopDesc::header_size(T_INT));
+  filler_header_size = align_object_size((arrayOopDesc::base_offset_in_bytes(T_INT) + BytesPerWord) / BytesPerWord);
 
   // We can be initialized to a zero size!
   if (free() > 0) {
     if (ZapUnusedHeapArea) {
       debug_only(Copy::fill_to_words(top(), free()/HeapWordSize, badHeapWord));
@@ -81,21 +81,26 @@
 
   // PLAB's never allocate the last aligned_header_size
   // so they can always fill with an array.
   HeapWord* tlab_end = end() + filler_header_size;
   typeArrayOop filler_oop = (typeArrayOop) cast_to_oop(top());
-  filler_oop->set_mark(markWord::prototype());
-  filler_oop->set_klass(Universe::intArrayKlassObj());
-  const size_t array_length =
-    pointer_delta(tlab_end, top()) - typeArrayOopDesc::header_size(T_INT);
-  assert( (array_length * (HeapWordSize/sizeof(jint))) < (size_t)max_jint, "array too big in PSPromotionLAB");
-  filler_oop->set_length((int)(array_length * (HeapWordSize/sizeof(jint))));
+  if (UseCompactObjectHeaders) {
+    filler_oop->set_mark(Universe::intArrayKlassObj()->prototype_header());
+  } else {
+    filler_oop->set_mark(markWord::prototype());
+    filler_oop->set_klass(Universe::intArrayKlassObj());
+  }
+  int header_size = arrayOopDesc::base_offset_in_bytes(T_INT);
+  const size_t array_length_bytes = pointer_delta(tlab_end, top(), 1) - header_size;
+  assert((array_length_bytes / sizeof(jint)) < (size_t)max_jint, "array too big in PSPromotionLAB");
+  filler_oop->set_length((int)(array_length_bytes / sizeof(jint)));
 
 #ifdef ASSERT
   // Note that we actually DO NOT want to use the aligned header size!
-  HeapWord* elt_words = cast_from_oop<HeapWord*>(filler_oop) + typeArrayOopDesc::header_size(T_INT);
-  Copy::fill_to_words(elt_words, array_length, 0xDEAABABE);
+  const size_t array_length_words = pointer_delta(tlab_end, top()) - heap_word_size(header_size);
+  HeapWord* elt_words = cast_from_oop<HeapWord*>(filler_oop) + heap_word_size(header_size);
+  Copy::fill_to_words(elt_words, array_length_words, 0xDEAABABE);
 #endif
 
   set_bottom(NULL);
   set_end(NULL);
   set_top(NULL);
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
@@ -312,11 +312,11 @@
 
 void PSPromotionManager::process_array_chunk(PartialArrayScanTask task) {
   assert(PSChunkLargeArrays, "invariant");
 
   oop old = task.to_source_array();
-  assert(old->is_objArray(), "invariant");
+  assert(UseCompactObjectHeaders || old->is_objArray(), "invariant");
   assert(old->is_forwarded(), "invariant");
 
   TASKQUEUE_STATS_ONLY(++_array_chunks_processed);
 
   oop const obj = old->forwardee();
@@ -350,11 +350,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->cas_forward_to(obj, obj_mark)) {
+  if (obj->forward_to_self_atomic(obj_mark) == NULL) {
     // 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
@@ -102,11 +102,12 @@
                                                     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, oop old_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
@@ -59,11 +59,11 @@
   Prefetch::write(obj->mark_addr(), 0);
   push_depth(ScannerTask(p));
 }
 
 inline void PSPromotionManager::promotion_trace_event(oop new_obj, oop old_obj,
-                                                      size_t obj_size,
+                                                      Klass* klass, size_t obj_size,
                                                       uint age, bool tenured,
                                                       const PSPromotionLAB* lab) {
   // Skip if memory allocation failed
   if (new_obj != NULL) {
     const ParallelScavengeTracer* gc_tracer = PSScavenge::gc_tracer();
@@ -71,18 +71,18 @@
     if (lab != NULL) {
       // 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);
       }
     }
   }
 }
@@ -142,11 +142,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
@@ -158,11 +158,18 @@
                                                                markWord test_mark) {
   assert(should_scavenge(&o), "Sanity");
 
   oop new_obj = NULL;
   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();
 
@@ -173,21 +180,21 @@
       if (new_obj == NULL && !_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, NULL);
+          promotion_trace_event(new_obj, o, klass, new_obj_size, age, false, NULL);
         } else {
           // Flush and fill
           _young_lab.flush();
 
           HeapWord* lab_base = young_space()->cas_allocate(YoungPLABSize);
           if (lab_base != NULL) {
             _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, o, klass, new_obj_size, age, false, &_young_lab);
           } else {
             _young_gen_is_full = true;
           }
         }
       }
@@ -209,11 +216,11 @@
       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, NULL);
+          promotion_trace_event(new_obj, o, klass, new_obj_size, age, true, NULL);
         } else {
           // Flush and fill
           _old_lab.flush();
 
           HeapWord* lab_base = old_gen()->allocate(OldPLABSize);
@@ -226,11 +233,11 @@
             }
 #endif
             _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, o, klass, new_obj_size, age, true, &_old_lab);
           }
         }
       }
 
       // This is the promotion failed test, and code handling.
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
@@ -684,11 +684,11 @@
 
   _promotion_failed = true;
   _promotion_failed_info.register_copy_failure(old->size());
   _preserved_marks_set.get()->push_if_necessary(old, old->mark());
   // 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
@@ -43,10 +43,11 @@
 #include "gc/shared/generation.hpp"
 #include "gc/shared/genOopClosures.inline.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"
@@ -91,10 +92,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");
@@ -109,10 +112,12 @@
 
   // Set saved marks for allocation profiler (and other things? -- dld)
   // (Should this be in general part?)
   gch->save_marks();
 
+  SlidingForwarding::end();
+
   deallocate_stacks();
 
   // If compaction completely evacuated the young generation then we
   // can clear the card table.  Otherwise, we must invalidate
   // it (consider all cards dirty).  In the future, we might consider doing
@@ -272,22 +277,35 @@
   GCTraceTime(Info, gc, phases) tm("Phase 3: Adjust pointers", gc_timer());
 
   // Need new claim bits for the pointer adjustment tracing.
   ClassLoaderDataGraph::clear_claimed_marks();
 
-  {
-    StrongRootsScope srs(0);
-
-    gch->full_process_roots(true,  // this is the adjust phase
-                            GenCollectedHeap::SO_AllCodeCache,
-                            false, // all roots
-                            &adjust_pointer_closure,
-                            &adjust_cld_closure);
+  if (UseAltGCForwarding) {
+    AdjustPointerClosure<true> adjust_pointer_closure;
+    CLDToOopClosure adjust_cld_closure(&adjust_pointer_closure, ClassLoaderData::_claim_strong);
+    {
+      StrongRootsScope srs(0);
+      gch->full_process_roots(true,  // this is the adjust phase
+                              GenCollectedHeap::SO_AllCodeCache,
+                              false, // all roots
+                              &adjust_pointer_closure,
+                              &adjust_cld_closure);
+    }
+    gch->gen_process_weak_roots(&adjust_pointer_closure);
+  } else {
+    AdjustPointerClosure<false> adjust_pointer_closure;
+    CLDToOopClosure adjust_cld_closure(&adjust_pointer_closure, ClassLoaderData::_claim_strong);
+    {
+      StrongRootsScope srs(0);
+      gch->full_process_roots(true,  // this is the adjust phase
+                              GenCollectedHeap::SO_AllCodeCache,
+                              false, // all roots
+                              &adjust_pointer_closure,
+                              &adjust_cld_closure);
+    }
+    gch->gen_process_weak_roots(&adjust_pointer_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
@@ -60,11 +60,10 @@
 
 MarkSweep::FollowRootClosure  MarkSweep::follow_root_closure;
 
 MarkAndPushClosure MarkSweep::mark_and_push_closure;
 CLDToOopClosure    MarkSweep::follow_cld_closure(&mark_and_push_closure, ClassLoaderData::_claim_strong);
-CLDToOopClosure    MarkSweep::adjust_cld_closure(&adjust_pointer_closure, ClassLoaderData::_claim_strong);
 
 template <class T> inline void MarkSweep::KeepAliveClosure::do_oop_work(T* p) {
   mark_and_push(p);
 }
 
@@ -142,12 +141,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);
 }
@@ -171,26 +171,33 @@
 void MarkSweep::set_ref_processor(ReferenceProcessor* rp) {
   _ref_processor = rp;
   mark_and_push_closure.set_ref_discoverer(_ref_processor);
 }
 
-AdjustPointerClosure MarkSweep::adjust_pointer_closure;
-
-void MarkSweep::adjust_marks() {
+template <bool ALT_FWD>
+void MarkSweep::adjust_marks_impl() {
   assert( _preserved_oop_stack.size() == _preserved_mark_stack.size(),
          "inconsistent preserved oop stacks");
 
   // 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<oop, mtGC> iter(_preserved_oop_stack);
   while (!iter.is_empty()) {
     oop* p = iter.next_addr();
-    adjust_pointer(p);
+    adjust_pointer<ALT_FWD>(p);
+  }
+}
+
+void MarkSweep::adjust_marks() {
+  if (UseAltGCForwarding) {
+    adjust_marks_impl<true>();
+  } else {
+    adjust_marks_impl<false>();
   }
 }
 
 void MarkSweep::restore_marks() {
   assert(_preserved_oop_stack.size() == _preserved_mark_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;
   friend class VM_MarkSweep;
 
   //
   // 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
@@ -137,22 +133,26 @@
   static STWGCTimer* gc_timer() { return _gc_timer; }
   static SerialOldTracer* gc_tracer() { return _gc_tracer; }
 
   static void preserve_mark(oop p, markWord mark);
                                 // Save the mark word so it can be restored later
+  template <bool ALT_FWD>
+  static void adjust_marks_impl();   // Adjust the pointers in the preserved marks table
   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 int adjust_pointers(oop obj);
 
   static void follow_stack();   // Empty marking stack.
 
   static void follow_klass(Klass* klass);
 
   static void follow_cld(ClassLoaderData* cld);
 
-  template <class T> static inline void adjust_pointer(T* p);
+  template <bool ALT_FWD, class T>
+  static inline void adjust_pointer(T* p);
 
   // Check mark and maybe push on marking stack
   template <class T> static void mark_and_push(T* p);
 
  private:
@@ -183,10 +183,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);
@@ -202,10 +203,11 @@
   void init(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
@@ -26,10 +26,11 @@
 #define SHARE_GC_SERIAL_MARKSWEEP_INLINE_HPP
 
 #include "gc/serial/markSweep.hpp"
 
 #include "classfile/classLoaderData.inline.hpp"
+#include "gc/shared/slidingForwarding.inline.hpp"
 #include "memory/universe.hpp"
 #include "oops/markWord.inline.hpp"
 #include "oops/access.inline.hpp"
 #include "oops/compressedOops.inline.hpp"
 #include "oops/oop.inline.hpp"
@@ -38,11 +39,11 @@
 
 inline void MarkSweep::mark_object(oop 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());
+  obj->set_mark(obj->prototype_mark().set_marked());
 
   if (obj->mark_must_be_preserved(mark)) {
     preserve_mark(obj, mark);
   }
 }
@@ -72,37 +73,38 @@
 inline void MarkAndPushClosure::do_oop(oop* p)               { do_oop_work(p); }
 inline void MarkAndPushClosure::do_oop(narrowOop* p)         { do_oop_work(p); }
 inline void MarkAndPushClosure::do_klass(Klass* k)           { MarkSweep::follow_klass(k); }
 inline void MarkAndPushClosure::do_cld(ClassLoaderData* cld) { MarkSweep::follow_cld(cld); }
 
-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");
 
-    oop new_obj = cast_to_oop(obj->mark().decode_pointer());
-
-    assert(new_obj != NULL ||                      // is forwarding ptr?
-           obj->mark() == markWord::prototype() || // not gc marked?
-           (UseBiasedLocking && obj->mark().has_bias_pattern()),
-           // not gc marked?
-           "should be forwarded");
-
-    if (new_obj != NULL) {
+    if (SlidingForwarding::is_forwarded(obj)) {
+      oop new_obj = SlidingForwarding::forwardee<ALT_FWD>(obj);
+      assert(new_obj != NULL, "must be forwarded");
       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 int 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
@@ -218,14 +218,10 @@
 
   if (!is_in(object)) {
     return false;
   }
 
-  if (is_in(object->klass_or_null())) {
-    return false;
-  }
-
   return true;
 }
 
 // Memory state functions.
 
@@ -240,12 +236,14 @@
   _gc_cause(GCCause::_no_gc),
   _gc_lastcause(GCCause::_no_gc)
 {
   const size_t max_len = size_t(arrayOopDesc::max_array_length(T_INT));
   const size_t elements_per_word = HeapWordSize / sizeof(jint);
-  _filler_array_max_size = align_object_size(filler_array_hdr_size() +
-                                             max_len / elements_per_word);
+  int header_size_in_bytes = arrayOopDesc::base_offset_in_bytes(T_INT);
+  assert(header_size_in_bytes % sizeof(jint) == 0, "must be aligned to int");
+  int header_size_in_ints = header_size_in_bytes / sizeof(jint);
+  _filler_array_max_size = align_object_size((header_size_in_ints + max_len) / elements_per_word);
 
   NOT_PRODUCT(_promotion_failure_alot_count = 0;)
   NOT_PRODUCT(_promotion_failure_alot_gc_number = 0;)
 
   if (UsePerfData) {
@@ -390,22 +388,21 @@
   //    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) +
+  int header_size_in_bytes = typeArrayOopDesc::base_offset_in_bytes(T_INT);
+  assert(header_size_in_bytes % sizeof(jint) == 0, "header size must align to int");
+  size_t max_int_size = header_size_in_bytes / HeapWordSize +
               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
-}
-
 size_t CollectedHeap::filler_array_min_size() {
-  return align_object_size(filler_array_hdr_size()); // align to MinObjAlignment
+  int aligned_header_size_words = align_up(arrayOopDesc::base_offset_in_bytes(T_INT), HeapWordSize) / HeapWordSize;
+  return align_object_size(aligned_header_size_words); // align to MinObjAlignment
 }
 
 #ifdef ASSERT
 void CollectedHeap::fill_args_check(HeapWord* start, size_t words)
 {
@@ -414,24 +411,26 @@
 }
 
 void CollectedHeap::zap_filler_array(HeapWord* start, size_t words, bool zap)
 {
   if (ZapFillerObjects && zap) {
-    Copy::fill_to_words(start + filler_array_hdr_size(),
-                        words - filler_array_hdr_size(), 0XDEAFBABE);
+  int payload_start = align_up(arrayOopDesc::base_offset_in_bytes(T_INT), HeapWordSize) / HeapWordSize;
+  Copy::fill_to_words(start + payload_start,
+                      words - payload_start, 0XDEAFBABE);
   }
 }
 #endif // ASSERT
 
 void
 CollectedHeap::fill_with_array(HeapWord* start, size_t words, bool zap)
 {
   assert(words >= filler_array_min_size(), "too small for an array");
   assert(words <= filler_array_max_size(), "too big for a single object");
 
-  const size_t payload_size = words - filler_array_hdr_size();
-  const size_t len = payload_size * HeapWordSize / sizeof(jint);
+  const size_t payload_size_bytes = words * HeapWordSize - arrayOopDesc::base_offset_in_bytes(T_INT);
+  assert(payload_size_bytes % sizeof(jint) == 0, "must be int aligned");
+  const size_t len = payload_size_bytes / sizeof(jint);
   assert((int)len >= 0, "size too large " SIZE_FORMAT " becomes %d", words, (int)len);
 
   ObjArrayAllocator allocator(Universe::intArrayKlassObj(), words, (int)len, /* do_zero */ false);
   allocator.initialize(start);
   DEBUG_ONLY(zap_filler_array(start, words, zap);)
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
@@ -152,11 +152,10 @@
   // called to allocate TLABs, only individual objects.
   virtual HeapWord* mem_allocate(size_t size,
                                  bool* gc_overhead_limit_was_exceeded) = 0;
 
   // Filler object utilities.
-  static inline size_t filler_array_hdr_size();
   static inline size_t filler_array_min_size();
 
   DEBUG_ONLY(static void fill_args_check(HeapWord* start, size_t words);)
   DEBUG_ONLY(static void zap_filler_array(HeapWord* start, size_t words, bool zap = true);)
 
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
@@ -691,11 +691,14 @@
           "Delay between expansion and allocation (in milliseconds)")       \
                                                                             \
   product(uintx, GCDrainStackTargetSize, 64,                                \
           "Number of entries we will try to leave on the stack "            \
           "during parallel gc")                                             \
-          range(0, max_juint)
+          range(0, max_juint)                                               \
+                                                                            \
+  product(bool, UseAltGCForwarding, false, EXPERIMENTAL,                    \
+          "Use alternative GC forwarding that preserves object headers")    \
 
 // end of GC_FLAGS
 
 DECLARE_FLAGS(GC_FLAGS)
 
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
@@ -50,10 +50,11 @@
 #include "gc/shared/locationPrinter.inline.hpp"
 #include "gc/shared/oopStorage.inline.hpp"
 #include "gc/shared/oopStorageSet.inline.hpp"
 #include "gc/shared/oopStorageParState.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/workgroup.hpp"
 #include "memory/iterator.hpp"
@@ -135,10 +136,12 @@
   old_rs = old_rs.first_part(_old_gen_spec->max_size());
   _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
@@ -355,26 +355,32 @@
 
 void MemAllocator::mem_clear(HeapWord* mem) const {
   assert(mem != NULL, "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 != NULL, "NULL object pointer");
   if (UseBiasedLocking) {
     oopDesc::set_mark(mem, _klass->prototype_header());
+  } else if (UseCompactObjectHeaders) {
+    oopDesc::release_set_mark(mem, _klass->prototype_header());
   } else {
     // 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_klass(mem, _klass);
+  }
   return cast_to_oop(mem);
 }
 
 oop ObjAllocator::initialize(HeapWord* mem) const {
   mem_clear(mem);
diff a/src/hotspot/share/gc/shared/oopStorage.inline.hpp b/src/hotspot/share/gc/shared/oopStorage.inline.hpp
--- a/src/hotspot/share/gc/shared/oopStorage.inline.hpp
+++ b/src/hotspot/share/gc/shared/oopStorage.inline.hpp
@@ -29,10 +29,11 @@
 
 #include "memory/allocation.hpp"
 #include "metaprogramming/conditional.hpp"
 #include "metaprogramming/isConst.hpp"
 #include "oops/oop.hpp"
+#include "runtime/objectMonitor.hpp"
 #include "runtime/safepoint.hpp"
 #include "utilities/align.hpp"
 #include "utilities/count_trailing_zeros.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/globalDefinitions.hpp"
@@ -260,10 +261,11 @@
     oop v = *ptr;
     if (v != NULL) {
       if (_is_alive->do_object_b(v)) {
         result = _f(ptr);
       } else {
+        ObjectMonitor::maybe_deflate_dead(ptr);
         *ptr = NULL;            // Clear dead value.
       }
     }
     return result;
   }
diff a/src/hotspot/share/gc/shared/preservedMarks.cpp b/src/hotspot/share/gc/shared/preservedMarks.cpp
--- a/src/hotspot/share/gc/shared/preservedMarks.cpp
+++ b/src/hotspot/share/gc/shared/preservedMarks.cpp
@@ -22,10 +22,11 @@
  *
  */
 
 #include "precompiled.hpp"
 #include "gc/shared/preservedMarks.inline.hpp"
+#include "gc/shared/slidingForwarding.inline.hpp"
 #include "gc/shared/workgroup.hpp"
 #include "memory/allocation.inline.hpp"
 #include "memory/resourceArea.hpp"
 #include "oops/oop.inline.hpp"
 #include "runtime/atomic.hpp"
@@ -37,22 +38,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());
+    if (SlidingForwarding::is_forwarded(obj)) {
+      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(oop obj, markWord m);
   inline void push_if_necessary(oop obj, markWord m);
   inline void push_always(oop obj, markWord m);
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
@@ -52,11 +52,11 @@
   OopAndMarkWord elem(obj, m);
   _stack.push(elem);
 }
 
 inline void PreservedMarks::init_forwarded_mark(oop obj) {
-  obj->init_mark();
+  obj->forward_safe_init_mark();
 }
 
 inline PreservedMarks::PreservedMarks()
     : _stack(OopAndMarkWordStack::default_segment_size(),
              // This stack should be used very infrequently so there's
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,173 @@
+/*
+ * 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/fastHash.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 FallbackTable();
+  }
+  _fallback_table->forward_to(from, to);
+}
+
+HeapWord* SlidingForwarding::fallback_forwardee(HeapWord* from) {
+  assert(_fallback_table != nullptr, "fallback table must be present");
+  return _fallback_table->forwardee(from);
+}
+
+SlidingForwarding::FallbackTable::FallbackTable() {
+  for (uint i = 0; i < TABLE_SIZE; i++) {
+    _table[i]._next = nullptr;
+    _table[i]._from = nullptr;
+    _table[i]._to   = nullptr;
+  }
+}
+
+SlidingForwarding::FallbackTable::~FallbackTable() {
+  for (uint i = 0; i < TABLE_SIZE; i++) {
+    FallbackTableEntry* entry = _table[i]._next;
+    while (entry != nullptr) {
+      FallbackTableEntry* next = entry->_next;
+      FREE_C_HEAP_OBJ(entry);
+      entry = next;
+    }
+  }
+}
+
+size_t SlidingForwarding::FallbackTable::home_index(HeapWord* from) {
+  uint64_t val = reinterpret_cast<uint64_t>(from);
+  uint64_t hash = FastHash::get_hash64(val, UCONST64(0xAAAAAAAAAAAAAAAA));
+  return hash >> (64 - log2i_exact(TABLE_SIZE));
+}
+
+void SlidingForwarding::FallbackTable::forward_to(HeapWord* from, HeapWord* to) {
+  size_t idx = home_index(from);
+  FallbackTableEntry* head = &_table[idx];
+#ifdef ASSERT
+  // Search existing entry in chain starting at idx.
+  for (FallbackTableEntry* entry = head; entry != nullptr; entry = entry->_next) {
+    assert(entry->_from != from, "Don't re-forward entries into the fallback-table");
+  }
+#endif
+  // No entry found, create new one and insert after head.
+  FallbackTableEntry* new_entry = NEW_C_HEAP_OBJ(FallbackTableEntry, mtGC);
+  *new_entry = *head;
+  head->_next = new_entry;
+  head->_from = from;
+  head->_to   = to;
+}
+
+HeapWord* SlidingForwarding::FallbackTable::forwardee(HeapWord* from) const {
+  size_t idx = home_index(from);
+  const FallbackTableEntry* entry = &_table[idx];
+  while (entry != nullptr) {
+    if (entry->_from == from) {
+      return entry->_to;
+    }
+    entry = entry->_next;
+  }
+  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,188 @@
+/*
+ * 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"
+
+/**
+ * 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.
+   */
+  class FallbackTable : public CHeapObj<mtGC>{
+  private:
+    struct FallbackTableEntry {
+      FallbackTableEntry* _next;
+      HeapWord* _from;
+      HeapWord* _to;
+    };
+
+    static const uint TABLE_SIZE = 1024;
+    FallbackTableEntry _table[TABLE_SIZE];
+
+    static size_t home_index(HeapWord* from);
+
+  public:
+    FallbackTable();
+    ~FallbackTable();
+
+    void forward_to(HeapWord* from, HeapWord* to);
+    HeapWord* forwardee(HeapWord* from) const;
+  };
+
+  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
@@ -27,10 +27,11 @@
 #include "classfile/vmSymbols.hpp"
 #include "gc/shared/blockOffsetTable.inline.hpp"
 #include "gc/shared/collectedHeap.inline.hpp"
 #include "gc/shared/genCollectedHeap.hpp"
 #include "gc/shared/genOopClosures.inline.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"
@@ -344,10 +345,11 @@
 void CompactibleSpace::clear(bool mangle_space) {
   Space::clear(mangle_space);
   _compaction_top = bottom();
 }
 
+template <bool ALT_FWD>
 HeapWord* CompactibleSpace::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.");
@@ -368,17 +370,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->forwardee() == NULL, "should be forwarded to NULL");
+    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
@@ -391,24 +393,36 @@
 }
 
 #if INCLUDE_SERIALGC
 
 void ContiguousSpace::prepare_for_compaction(CompactPoint* cp) {
-  scan_and_forward(this, cp);
+  if (UseAltGCForwarding) {
+    scan_and_forward<true>(this, cp);
+  } else {
+    scan_and_forward<false>(this, cp);
+  }
 }
 
 void CompactibleSpace::adjust_pointers() {
   // Check first is there is any work to do.
   if (used() == 0) {
     return;   // Nothing to do.
   }
 
-  scan_and_adjust_pointers(this);
+  if (UseAltGCForwarding) {
+    scan_and_adjust_pointers<true>(this);
+  } else {
+    scan_and_adjust_pointers<false>(this);
+  }
 }
 
 void CompactibleSpace::compact() {
-  scan_and_compact(this);
+  if (UseAltGCForwarding) {
+    scan_and_compact<true>(this);
+  } else {
+    scan_and_compact<false>(this);
+  }
 }
 
 #endif // INCLUDE_SERIALGC
 
 void Space::print_short() const { print_short_on(tty); }
@@ -584,26 +598,34 @@
   if (factor > 0) {
     size -= size/factor;
   }
   size = align_object_size(size);
 
-  const size_t array_header_size = typeArrayOopDesc::header_size(T_INT);
+  const size_t array_header_size = (arrayOopDesc::base_offset_in_bytes(T_INT) + BytesPerWord) / BytesPerWord;
   if (size >= align_object_size(array_header_size)) {
     size_t length = (size - array_header_size) * (HeapWordSize / sizeof(jint));
     // allocate uninitialized int array
     typeArrayOop t = (typeArrayOop) cast_to_oop(allocate(size));
     assert(t != NULL, "allocation should succeed");
-    t->set_mark(markWord::prototype());
-    t->set_klass(Universe::intArrayKlassObj());
+    if (UseCompactObjectHeaders) {
+      t->set_mark(Universe::intArrayKlassObj()->prototype_header());
+    } else {
+      t->set_mark(markWord::prototype());
+      t->set_klass(Universe::intArrayKlassObj());
+    }
     t->set_length((int)length);
   } else {
     assert(size == CollectedHeap::min_fill_size(),
            "size for smallest fake object doesn't match");
     instanceOop obj = (instanceOop) cast_to_oop(allocate(size));
-    obj->set_mark(markWord::prototype());
-    obj->set_klass_gap(0);
-    obj->set_klass(vmClasses::Object_klass());
+    if (UseCompactObjectHeaders) {
+      obj->set_mark(vmClasses::Object_klass()->prototype_header());
+    } else {
+      obj->set_mark(markWord::prototype());
+      obj->set_klass_gap(0);
+      obj->set_klass(vmClasses::Object_klass());
+    }
   }
 }
 
 HeapWord* OffsetTableContigSpace::initialize_threshold() {
   return _offsets.initialize_threshold();
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
@@ -429,11 +429,12 @@
   // the previous generation if necessary, updating "cp"), reset compact_top
   // and then forward.  In either case, returns the new value of "compact_top".
   // If the forwarding crosses "cp->threshold", invokes the "cross_threshold"
   // function of the then-current compaction space, and updates "cp->threshold
   // accordingly".
-  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);
 
   // Return a size with adjustments as required of the space.
   virtual size_t adjust_object_size_v(size_t size) const { return size; }
 
@@ -458,32 +459,32 @@
   // and possibly also overriding obj_size(), and adjust_obj_size().
   // These functions should avoid virtual calls whenever possible.
 
 #if INCLUDE_SERIALGC
   // Frequently calls adjust_obj_size().
-  template <class SpaceType>
+  template <bool ALT_FWD, class SpaceType>
   static inline void scan_and_adjust_pointers(SpaceType* space);
 #endif
 
   // Frequently calls obj_size().
-  template <class SpaceType>
+  template <bool ALT_FWD, class SpaceType>
   static inline void scan_and_compact(SpaceType* space);
 
   // Frequently calls scanned_block_is_obj() and scanned_block_size().
   // Requires the scan_limit() function.
-  template <class SpaceType>
+  template <bool ALT_FWD, class SpaceType>
   static inline void scan_and_forward(SpaceType* space, CompactPoint* cp);
 };
 
 class GenSpaceMangler;
 
 // A space in which the free area is contiguous.  It therefore supports
 // faster allocation, and compaction.
 class ContiguousSpace: public CompactibleSpace {
   friend class VMStructs;
   // Allow scan_and_forward function to call (private) overrides for auxiliary functions on this class
-  template <typename SpaceType>
+  template <bool ALT_FWD, typename SpaceType>
   friend void CompactibleSpace::scan_and_forward(SpaceType* space, CompactPoint* cp);
 
  private:
   // Auxiliary functions for scan_and_forward support.
   // See comments for CompactibleSpace for more information.
diff a/src/hotspot/share/gc/shared/space.inline.hpp b/src/hotspot/share/gc/shared/space.inline.hpp
--- a/src/hotspot/share/gc/shared/space.inline.hpp
+++ b/src/hotspot/share/gc/shared/space.inline.hpp
@@ -131,11 +131,11 @@
     }
   }
 
 };
 
-template <class SpaceType>
+template <bool ALT_FWD, class SpaceType>
 inline void CompactibleSpace::scan_and_forward(SpaceType* space, 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
@@ -166,11 +166,11 @@
   while (cur_obj < scan_limit) {
     if (space->scanned_block_is_obj(cur_obj) && cast_to_oop(cur_obj)->is_gc_marked()) {
       // prefetch beyond cur_obj
       Prefetch::write(cur_obj, interval);
       size_t size = space->scanned_block_size(cur_obj);
-      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;
@@ -182,11 +182,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.
@@ -213,11 +213,11 @@
 
   // save the compaction_top of the compaction space.
   cp->space->set_compaction_top(compact_top);
 }
 
-template <class SpaceType>
+template <bool ALT_FWD, class SpaceType>
 inline void CompactibleSpace::scan_and_adjust_pointers(SpaceType* space) {
   // adjust all the interior pointers to point at the new locations of objects
   // Used by MarkSweep::mark_sweep_phase3()
 
   HeapWord* cur_obj = space->bottom();
@@ -232,11 +232,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));
       size = space->adjust_obj_size(size);
       debug_only(prev_obj = cur_obj);
       cur_obj += size;
     } else {
       debug_only(prev_obj = cur_obj);
@@ -286,11 +286,11 @@
   } else {
     if (ZapUnusedHeapArea) space->mangle_unused_area();
   }
 }
 
-template <class SpaceType>
+template <bool ALT_FWD, class SpaceType>
 inline void CompactibleSpace::scan_and_compact(SpaceType* space) {
   // Copy all live objects to their new location
   // Used by MarkSweep::mark_sweep_phase4()
 
   verify_up_to_first_dead(space);
@@ -327,11 +327,11 @@
       // prefetch beyond q
       Prefetch::read(cur_obj, scan_interval);
 
       // size and destination
       size_t size = space->obj_size(cur_obj);
-      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
diff a/src/hotspot/share/gc/shared/weakProcessor.inline.hpp b/src/hotspot/share/gc/shared/weakProcessor.inline.hpp
--- a/src/hotspot/share/gc/shared/weakProcessor.inline.hpp
+++ b/src/hotspot/share/gc/shared/weakProcessor.inline.hpp
@@ -63,10 +63,11 @@
       ++_old_dead;
     } else if (_is_alive->do_object_b(obj)) {
       _keep_alive->do_oop(p);
       ++_live;
     } else {
+      ObjectMonitor::maybe_deflate_dead(p);
       *p = NULL;
       ++_new_dead;
     }
   }
 
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, NULL, "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 == NULL) {
     print_failure(_safe_unknown, obj, interior_loc, NULL, "Shenandoah assert_correct failed",
                   "Object klass pointer should not be NULL",
                   file,line);
   }
@@ -227,11 +227,11 @@
       print_failure(_safe_oop, obj, interior_loc, NULL, "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, NULL, "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
@@ -25,10 +25,11 @@
 #include "precompiled.hpp"
 
 #include "compiler/oopMap.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/shenandoah/heuristics/shenandoahHeuristics.hpp"
 #include "gc/shenandoah/shenandoahConcurrentGC.hpp"
 #include "gc/shenandoah/shenandoahCollectionSet.hpp"
 #include "gc/shenandoah/shenandoahFreeSet.hpp"
@@ -220,10 +221,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();
@@ -234,10 +237,11 @@
   {
     // Epilogue
     _preserved_marks->restore(heap->workers());
     BiasedLocking::restore_marks();
     _preserved_marks->reclaim();
+    SlidingForwarding::end();
   }
 
   // Resize metaspace
   MetaspaceGC::compute_new_size();
 
@@ -295,10 +299,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;
@@ -363,11 +368,11 @@
 
     // Object fits into current region, record new location:
     assert(_compact_point + obj_size <= _to_region->end(), "must fit");
     shenandoah_assert_not_forwarded(NULL, 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;
   }
 };
 
 class ShenandoahPrepareForCompactionTask : public AbstractGangTask {
@@ -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 != NULL) {
       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);
       }
     }
   }
 
@@ -747,14 +772,15 @@
 
   void do_oop(oop* p)       { do_oop_work(p); }
   void do_oop(narrowOop* p) { do_oop_work(p); }
 };
 
+template <bool ALT_FWD>
 class ShenandoahAdjustPointersObjectClosure : public ObjectClosure {
 private:
   ShenandoahHeap* const _heap;
-  ShenandoahAdjustPointersClosure _cl;
+  ShenandoahAdjustPointersClosure<ALT_FWD> _cl;
 
 public:
   ShenandoahAdjustPointersObjectClosure() :
     _heap(ShenandoahHeap::heap()) {
   }
@@ -773,39 +799,62 @@
   ShenandoahAdjustPointersTask() :
     AbstractGangTask("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 != NULL) {
       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 AbstractGangTask {
 private:
   ShenandoahRootAdjuster* _rp;
   PreservedMarksSet* _preserved_marks;
+
 public:
   ShenandoahAdjustRootPointersTask(ShenandoahRootAdjuster* rp, PreservedMarksSet* preserved_marks) :
     AbstractGangTask("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);
@@ -828,10 +877,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;
 
@@ -840,13 +890,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 = (size_t)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));
       Copy::aligned_conjoint_words(compact_from, compact_to, size);
       oop new_obj = cast_to_oop(compact_to);
       new_obj->init_mark();
     }
   }
@@ -862,25 +912,36 @@
     AbstractGangTask("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 != NULL) {
       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;
@@ -932,11 +993,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.
@@ -945,20 +1007,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(heap->get_region(old_start)->bottom(),
@@ -996,10 +1058,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
@@ -30,10 +30,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"
@@ -399,10 +400,12 @@
 
   _control_thread = new ShenandoahControlThread();
 
   ShenandoahInitLogger::print();
 
+  SlidingForwarding::initialize(_heap_region, ShenandoahHeapRegion::region_size_words());
+
   return JNI_OK;
 }
 
 void ShenandoahHeap::initialize_mode() {
   if (ShenandoahGCMode != NULL) {
@@ -951,11 +954,11 @@
   ShenandoahConcurrentEvacuateRegionObjectClosure(ShenandoahHeap* heap) :
     _heap(heap), _thread(Thread::current()) {}
 
   void do_object(oop p) {
     shenandoah_assert_marked(NULL, p);
-    if (!p->is_forwarded()) {
+    if (!ShenandoahForwarding::is_forwarded(p)) {
       _heap->evacuate_object(p, _thread);
     }
   }
 };
 
@@ -1299,10 +1302,11 @@
 
   // Work through the oop stack to traverse heap
   while (! oop_stack.is_empty()) {
     oop obj = oop_stack.pop();
     assert(oopDesc::is_oop(obj), "must be a valid oop");
+    shenandoah_assert_not_in_cset_except(NULL, obj, cancelled_gc());
     cl->do_object(obj);
     obj->oop_iterate(&oops);
   }
 
   assert(oop_stack.is_empty(), "should be empty");
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
@@ -281,11 +281,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 = NULL;
@@ -498,11 +498,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");
-    int 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
@@ -95,11 +95,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.
       //
@@ -122,11 +122,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 != NULL,
@@ -137,15 +137,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");
         }
       }
@@ -158,11 +158,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;
@@ -199,11 +199,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");
@@ -302,11 +302,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 = NULL;
   }
 
   virtual void do_oop(oop* p) { do_oop_work(p); }
   virtual void do_oop(narrowOop* p) { do_oop_work(p); }
@@ -582,11 +583,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/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
@@ -294,11 +294,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 or with compact object headers");
         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
@@ -41,18 +41,20 @@
   ZThreadLocalData::set_invisible_root(_thread, (oop*)&mem);
 
   // 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 size_t skip = arrayOopDesc::header_size(ArrayKlass::cast(_klass)->element_type());
-  size_t remaining = _word_size - skip;
+  const size_t segment_max = 64 * K;
+  const size_t skip = arrayOopDesc::base_offset_in_bytes(ArrayKlass::cast(_klass)->element_type());
+  size_t byte_size = _word_size * BytesPerWord;
+  size_t remaining = byte_size - skip;
 
+  char* const start = reinterpret_cast<char*>(mem);
   while (remaining > 0) {
     // Clear segment
     const size_t segment = MIN2(remaining, segment_max);
-    Copy::zero_to_words(mem + (_word_size - remaining), segment);
+    Copy::zero_to_bytes(start + (byte_size - remaining), segment);
     remaining -= segment;
 
     if (remaining > 0) {
       // Safepoint
       ThreadBlockInVM tbivm(_thread->as_Java_thread());
diff a/src/hotspot/share/interpreter/interpreterRuntime.cpp b/src/hotspot/share/interpreter/interpreterRuntime.cpp
--- a/src/hotspot/share/interpreter/interpreterRuntime.cpp
+++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp
@@ -722,10 +722,11 @@
 // be shared by method invocation and synchronized blocks.
 //%note synchronization_3
 
 //%note monitor_1
 JRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* current, BasicObjectLock* elem))
+  assert(LockingMode != LM_LIGHTWEIGHT, "Should call monitorenter_obj() when using the new lightweight locking");
 #ifdef ASSERT
   current->last_frame().interpreter_frame_verify_monitor(elem);
 #endif
   if (PrintBiasedLockingStatistics) {
     Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
@@ -739,10 +740,26 @@
 #ifdef ASSERT
   current->last_frame().interpreter_frame_verify_monitor(elem);
 #endif
 JRT_END
 
+// NOTE: We provide a separate implementation for the new lightweight locking to workaround a limitation
+// of registers in x86_32. This entry point accepts an oop instead of a BasicObjectLock*.
+// The problem is that we would need to preserve the register that holds the BasicObjectLock,
+// but we are using that register to hold the thread. We don't have enough registers to
+// also keep the BasicObjectLock, but we don't really need it anyway, we only need
+// the object. See also InterpreterMacroAssembler::lock_object().
+// As soon as legacy stack-locking goes away we could remove the other monitorenter() entry
+// point, and only use oop-accepting entries (same for monitorexit() below).
+JRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter_obj(JavaThread* current, oopDesc* obj))
+  assert(LockingMode == LM_LIGHTWEIGHT, "Should call monitorenter() when not using the new lightweight locking");
+  Handle h_obj(current, cast_to_oop(obj));
+  assert(Universe::heap()->is_in_or_null(h_obj()),
+         "must be null or an object");
+  ObjectSynchronizer::enter(h_obj, NULL, current);
+  return;
+JRT_END
 
 JRT_LEAF(void, InterpreterRuntime::monitorexit(BasicObjectLock* elem))
   oop obj = elem->obj();
   assert(Universe::heap()->is_in(obj), "must be an object");
   // The object could become unlocked through a JNI call, which we have no other checks for.
diff a/src/hotspot/share/interpreter/interpreterRuntime.hpp b/src/hotspot/share/interpreter/interpreterRuntime.hpp
--- a/src/hotspot/share/interpreter/interpreterRuntime.hpp
+++ b/src/hotspot/share/interpreter/interpreterRuntime.hpp
@@ -104,10 +104,11 @@
   static void resolve_invokedynamic(JavaThread* current);
 
  public:
   // Synchronization
   static void    monitorenter(JavaThread* current, BasicObjectLock* elem);
+  static void    monitorenter_obj(JavaThread* current, oopDesc* obj);
   static void    monitorexit (BasicObjectLock* elem);
 
   static void    throw_illegal_monitor_state_exception(JavaThread* current);
   static void    new_illegal_monitor_state_exception(JavaThread* current);
 
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
@@ -625,11 +625,11 @@
         assert(!UseBiasedLocking, "Not implemented");
 
         // Traditional lightweight locking.
         markWord displaced = rcvr->mark().set_unlocked();
         mon->lock()->set_displaced_header(displaced);
-        bool call_vm = UseHeavyMonitors;
+        bool call_vm = (LockingMode == LM_MONITOR);
         if (call_vm || rcvr->cas_set_mark(markWord::from_pointer(mon), displaced) != displaced) {
           // Is it simple recursive case?
           if (!call_vm && THREAD->is_lock_owned((address) displaced.clear_lock_bits().to_pointer())) {
             mon->lock()->set_displaced_header(markWord::from_pointer(NULL));
           } else {
@@ -721,11 +721,11 @@
       assert(!UseBiasedLocking, "Not implemented");
 
       // traditional lightweight locking
       markWord displaced = lockee->mark().set_unlocked();
       entry->lock()->set_displaced_header(displaced);
-      bool call_vm = UseHeavyMonitors;
+      bool call_vm = (LockingMode == LM_MONITOR);
       if (call_vm || lockee->cas_set_mark(markWord::from_pointer(entry), displaced) != displaced) {
         // Is it simple recursive case?
         if (!call_vm && THREAD->is_lock_owned((address) displaced.clear_lock_bits().to_pointer())) {
           entry->lock()->set_displaced_header(markWord::from_pointer(NULL));
         } else {
@@ -1631,11 +1631,11 @@
           assert(!UseBiasedLocking, "Not implemented");
 
           // traditional lightweight locking
           markWord displaced = lockee->mark().set_unlocked();
           entry->lock()->set_displaced_header(displaced);
-          bool call_vm = UseHeavyMonitors;
+          bool call_vm = (LockingMode == LM_MONITOR);
           if (call_vm || lockee->cas_set_mark(markWord::from_pointer(entry), displaced) != displaced) {
             // Is it simple recursive case?
             if (!call_vm && THREAD->is_lock_owned((address) displaced.clear_lock_bits().to_pointer())) {
               entry->lock()->set_displaced_header(markWord::from_pointer(NULL));
             } else {
@@ -1663,11 +1663,11 @@
             most_recent->set_obj(NULL);
 
             assert(!UseBiasedLocking, "Not implemented");
 
             // If it isn't recursive we either must swap old header or call the runtime
-            bool call_vm = UseHeavyMonitors;
+            bool call_vm = (LockingMode == LM_MONITOR);
             if (header.to_pointer() != NULL || call_vm) {
               markWord old_header = markWord::encode(lock);
               if (call_vm || lockee->cas_set_mark(header, old_header) != old_header) {
                 // restore object for the slow case
                 most_recent->set_obj(lockee);
@@ -1953,14 +1953,16 @@
 
               oop obj = cast_to_oop(result);
 
               // Initialize header
               assert(!UseBiasedLocking, "Not implemented");
-              obj->set_mark(markWord::prototype());
-              obj->set_klass_gap(0);
-              obj->set_klass(ik);
-
+              if (UseCompactObjectHeaders) {
+                oopDesc::release_set_mark(result, ik->prototype_header());
+              } else {
+                obj->set_mark(markWord::prototype());
+                obj->set_klass(ik);
+              }
               // Must prevent reordering of stores for object initialization
               // with stores that publish the new object.
               OrderAccess::storestore();
               SET_STACK_OBJECT(obj, 0);
               UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);
@@ -3144,11 +3146,11 @@
             if (!suppress_error) {
               VM_JAVA_ERROR_NO_JUMP(vmSymbols::java_lang_NullPointerException(), "");
               illegal_state_oop = Handle(THREAD, THREAD->pending_exception());
               THREAD->clear_pending_exception();
             }
-          } else if (UseHeavyMonitors) {
+          } else if (LockingMode == LM_MONITOR) {
             InterpreterRuntime::monitorexit(base);
             if (THREAD->has_pending_exception()) {
               if (!suppress_error) illegal_state_oop = Handle(THREAD, THREAD->pending_exception());
               THREAD->clear_pending_exception();
             }
diff a/src/hotspot/share/jfr/leakprofiler/chains/bfsClosure.cpp b/src/hotspot/share/jfr/leakprofiler/chains/bfsClosure.cpp
--- a/src/hotspot/share/jfr/leakprofiler/chains/bfsClosure.cpp
+++ b/src/hotspot/share/jfr/leakprofiler/chains/bfsClosure.cpp
@@ -20,26 +20,26 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  *
  */
 #include "precompiled.hpp"
-#include "jfr/leakprofiler/chains/bitset.inline.hpp"
 #include "jfr/leakprofiler/chains/bfsClosure.hpp"
 #include "jfr/leakprofiler/chains/dfsClosure.hpp"
 #include "jfr/leakprofiler/chains/edge.hpp"
 #include "jfr/leakprofiler/chains/edgeStore.hpp"
 #include "jfr/leakprofiler/chains/edgeQueue.hpp"
+#include "jfr/leakprofiler/chains/jfrbitset.hpp"
 #include "jfr/leakprofiler/utilities/granularTimer.hpp"
 #include "jfr/leakprofiler/utilities/unifiedOopRef.inline.hpp"
 #include "logging/log.hpp"
 #include "memory/iterator.inline.hpp"
 #include "memory/resourceArea.hpp"
 #include "oops/access.inline.hpp"
 #include "oops/oop.inline.hpp"
 #include "utilities/align.hpp"
 
-BFSClosure::BFSClosure(EdgeQueue* edge_queue, EdgeStore* edge_store, BitSet* mark_bits) :
+BFSClosure::BFSClosure(EdgeQueue* edge_queue, EdgeStore* edge_store, JFRBitSet* mark_bits) :
   _edge_queue(edge_queue),
   _edge_store(edge_store),
   _mark_bits(mark_bits),
   _current_parent(NULL),
   _current_frontier_level(0),
diff a/src/hotspot/share/jfr/leakprofiler/chains/bfsClosure.hpp b/src/hotspot/share/jfr/leakprofiler/chains/bfsClosure.hpp
--- a/src/hotspot/share/jfr/leakprofiler/chains/bfsClosure.hpp
+++ b/src/hotspot/share/jfr/leakprofiler/chains/bfsClosure.hpp
@@ -23,24 +23,24 @@
  */
 
 #ifndef SHARE_JFR_LEAKPROFILER_CHAINS_BFSCLOSURE_HPP
 #define SHARE_JFR_LEAKPROFILER_CHAINS_BFSCLOSURE_HPP
 
+#include "jfr/leakprofiler/chains/jfrbitset.hpp"
 #include "jfr/leakprofiler/utilities/unifiedOopRef.hpp"
 #include "memory/iterator.hpp"
 
-class BitSet;
 class Edge;
 class EdgeStore;
 class EdgeQueue;
 
 // Class responsible for iterating the heap breadth-first
 class BFSClosure : public BasicOopIterateClosure {
  private:
   EdgeQueue* _edge_queue;
   EdgeStore* _edge_store;
-  BitSet* _mark_bits;
+  JFRBitSet* _mark_bits;
   const Edge* _current_parent;
   mutable size_t _current_frontier_level;
   mutable size_t _next_frontier_idx;
   mutable size_t _prev_frontier_idx;
   size_t _dfs_fallback_idx;
@@ -63,11 +63,11 @@
   void process_queue();
 
  public:
   virtual ReferenceIterationMode reference_iteration_mode() { return DO_FIELDS_EXCEPT_REFERENT; }
 
-  BFSClosure(EdgeQueue* edge_queue, EdgeStore* edge_store, BitSet* mark_bits);
+  BFSClosure(EdgeQueue* edge_queue, EdgeStore* edge_store, JFRBitSet* mark_bits);
   void process();
   void do_root(UnifiedOopRef ref);
 
   virtual void do_oop(oop* ref);
   virtual void do_oop(narrowOop* ref);
diff a/src/hotspot/share/jfr/leakprofiler/chains/bitset.cpp b/src/hotspot/share/jfr/leakprofiler/chains/bitset.cpp
--- a/src/hotspot/share/jfr/leakprofiler/chains/bitset.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (c) 2014, 2022, 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 "jfr/leakprofiler/chains/bitset.inline.hpp"
-
-BitSet::BitMapFragment::BitMapFragment(uintptr_t granule, BitMapFragment* next) :
-    _bits(_bitmap_granularity_size >> LogMinObjAlignmentInBytes, mtTracing, true /* clear */),
-    _next(next) {
-}
-
-BitSet::BitMapFragmentTable::~BitMapFragmentTable() {
-  for (int index = 0; index < table_size(); index ++) {
-    Entry* e = bucket(index);
-    while (e != nullptr) {
-      Entry* tmp = e;
-      e = e->next();
-      free_entry(tmp);
-    }
-  }
-}
-
-BitSet::BitSet() :
-    _bitmap_fragments(32),
-    _fragment_list(NULL),
-    _last_fragment_bits(NULL),
-    _last_fragment_granule(UINTPTR_MAX) {
-}
-
-BitSet::~BitSet() {
-  BitMapFragment* current = _fragment_list;
-  while (current != NULL) {
-    BitMapFragment* next = current->next();
-    delete current;
-    current = next;
-  }
-}
diff a/src/hotspot/share/jfr/leakprofiler/chains/bitset.hpp b/src/hotspot/share/jfr/leakprofiler/chains/bitset.hpp
--- a/src/hotspot/share/jfr/leakprofiler/chains/bitset.hpp
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (c) 2014, 2022, 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_JFR_LEAKPROFILER_CHAINS_BITSET_HPP
-#define SHARE_JFR_LEAKPROFILER_CHAINS_BITSET_HPP
-
-#include "memory/allocation.hpp"
-#include "oops/oop.hpp"
-#include "oops/oopsHierarchy.hpp"
-#include "utilities/bitMap.hpp"
-#include "utilities/hashtable.hpp"
-
-class JfrVirtualMemory;
-class MemRegion;
-
-class BitSet : public CHeapObj<mtTracing> {
-  const static size_t _bitmap_granularity_shift = 26; // 64M
-  const static size_t _bitmap_granularity_size = (size_t)1 << _bitmap_granularity_shift;
-  const static size_t _bitmap_granularity_mask = _bitmap_granularity_size - 1;
-
-  class BitMapFragment;
-
-  class BitMapFragmentTable : public BasicHashtable<mtTracing> {
-    class Entry : public BasicHashtableEntry<mtTracing> {
-    public:
-      uintptr_t _key;
-      CHeapBitMap* _value;
-
-      Entry* next() {
-        return (Entry*)BasicHashtableEntry<mtTracing>::next();
-      }
-    };
-
-  protected:
-    Entry* bucket(int i) const;
-
-    Entry* new_entry(unsigned int hashValue, uintptr_t key, CHeapBitMap* value);
-
-    unsigned hash_segment(uintptr_t key) {
-      unsigned hash = (unsigned)key;
-      return hash ^ (hash >> 3);
-    }
-
-    unsigned hash_to_index(unsigned hash) {
-      return hash & (BasicHashtable<mtTracing>::table_size() - 1);
-    }
-
-  public:
-    BitMapFragmentTable(int table_size) : BasicHashtable<mtTracing>(table_size, sizeof(Entry)) {}
-    ~BitMapFragmentTable();
-    void add(uintptr_t key, CHeapBitMap* value);
-    CHeapBitMap** lookup(uintptr_t key);
-  };
-
-  CHeapBitMap* get_fragment_bits(uintptr_t addr);
-
-  BitMapFragmentTable _bitmap_fragments;
-  BitMapFragment* _fragment_list;
-  CHeapBitMap* _last_fragment_bits;
-  uintptr_t _last_fragment_granule;
-
- public:
-  BitSet();
-  ~BitSet();
-
-  BitMap::idx_t addr_to_bit(uintptr_t addr) const;
-
-  void mark_obj(uintptr_t addr);
-
-  void mark_obj(oop obj) {
-    return mark_obj(cast_from_oop<uintptr_t>(obj));
-  }
-
-  bool is_marked(uintptr_t addr);
-
-  bool is_marked(oop obj) {
-    return is_marked(cast_from_oop<uintptr_t>(obj));
-  }
-};
-
-class BitSet::BitMapFragment : public CHeapObj<mtTracing> {
-  CHeapBitMap _bits;
-  BitMapFragment* _next;
-
-public:
-  BitMapFragment(uintptr_t granule, BitMapFragment* next);
-
-  BitMapFragment* next() const {
-    return _next;
-  }
-
-  CHeapBitMap* bits() {
-    return &_bits;
-  }
-};
-
-#endif // SHARE_JFR_LEAKPROFILER_CHAINS_BITSET_HPP
diff a/src/hotspot/share/jfr/leakprofiler/chains/bitset.inline.hpp b/src/hotspot/share/jfr/leakprofiler/chains/bitset.inline.hpp
--- a/src/hotspot/share/jfr/leakprofiler/chains/bitset.inline.hpp
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (c) 2019, 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_JFR_LEAKPROFILER_CHAINS_BITSET_INLINE_HPP
-#define SHARE_JFR_LEAKPROFILER_CHAINS_BITSET_INLINE_HPP
-
-#include "jfr/leakprofiler/chains/bitset.hpp"
-
-#include "jfr/recorder/storage/jfrVirtualMemory.hpp"
-#include "memory/memRegion.hpp"
-#include "utilities/bitMap.inline.hpp"
-#include "utilities/hashtable.inline.hpp"
-
-inline BitSet::BitMapFragmentTable::Entry* BitSet::BitMapFragmentTable::bucket(int i) const {
-  return (Entry*)BasicHashtable<mtTracing>::bucket(i);
-}
-
-inline BitSet::BitMapFragmentTable::Entry* BitSet::BitMapFragmentTable::new_entry(unsigned int hash,
-                                                                                  uintptr_t key,
-                                                                                  CHeapBitMap* value) {
-  Entry* entry = (Entry*)BasicHashtable<mtTracing>::new_entry(hash);
-  entry->_key = key;
-  entry->_value = value;
-  return entry;
-}
-
-inline void BitSet::BitMapFragmentTable::add(uintptr_t key, CHeapBitMap* value) {
-  unsigned hash = hash_segment(key);
-  Entry* entry = new_entry(hash, key, value);
-  BasicHashtable<mtTracing>::add_entry(hash_to_index(hash), entry);
-}
-
-inline CHeapBitMap** BitSet::BitMapFragmentTable::lookup(uintptr_t key) {
-  unsigned hash = hash_segment(key);
-  int index = hash_to_index(hash);
-  for (Entry* e = bucket(index); e != NULL; e = e->next()) {
-    if (e->hash() == hash && e->_key == key) {
-      return &(e->_value);
-    }
-  }
-  return NULL;
-}
-
-inline BitMap::idx_t BitSet::addr_to_bit(uintptr_t addr) const {
-  return (addr & _bitmap_granularity_mask) >> LogMinObjAlignmentInBytes;
-}
-
-inline CHeapBitMap* BitSet::get_fragment_bits(uintptr_t addr) {
-  uintptr_t granule = addr >> _bitmap_granularity_shift;
-  if (granule == _last_fragment_granule) {
-    return _last_fragment_bits;
-  }
-  CHeapBitMap* bits = NULL;
-
-  CHeapBitMap** found = _bitmap_fragments.lookup(granule);
-  if (found != NULL) {
-    bits = *found;
-  } else {
-    BitMapFragment* fragment = new BitMapFragment(granule, _fragment_list);
-    bits = fragment->bits();
-    _fragment_list = fragment;
-    if (_bitmap_fragments.number_of_entries() * 100 / _bitmap_fragments.table_size() > 25) {
-      _bitmap_fragments.resize(_bitmap_fragments.table_size() * 2);
-    }
-    _bitmap_fragments.add(granule, bits);
-  }
-
-  _last_fragment_bits = bits;
-  _last_fragment_granule = granule;
-
-  return bits;
-}
-
-inline void BitSet::mark_obj(uintptr_t addr) {
-  CHeapBitMap* bits = get_fragment_bits(addr);
-  const BitMap::idx_t bit = addr_to_bit(addr);
-  bits->set_bit(bit);
-}
-
-inline bool BitSet::is_marked(uintptr_t addr) {
-  CHeapBitMap* bits = get_fragment_bits(addr);
-  const BitMap::idx_t bit = addr_to_bit(addr);
-  return bits->at(bit);
-}
-
-#endif // SHARE_JFR_LEAKPROFILER_CHAINS_BITSET_INLINE_HPP
diff a/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.cpp b/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.cpp
--- a/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.cpp
+++ b/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.cpp
@@ -21,14 +21,14 @@
  * questions.
  *
  */
 
 #include "precompiled.hpp"
-#include "jfr/leakprofiler/chains/bitset.inline.hpp"
 #include "jfr/leakprofiler/chains/dfsClosure.hpp"
 #include "jfr/leakprofiler/chains/edge.hpp"
 #include "jfr/leakprofiler/chains/edgeStore.hpp"
+#include "jfr/leakprofiler/chains/jfrbitset.hpp"
 #include "jfr/leakprofiler/chains/rootSetClosure.hpp"
 #include "jfr/leakprofiler/utilities/granularTimer.hpp"
 #include "jfr/leakprofiler/utilities/rootType.hpp"
 #include "jfr/leakprofiler/utilities/unifiedOopRef.inline.hpp"
 #include "memory/iterator.inline.hpp"
@@ -38,11 +38,11 @@
 #include "utilities/align.hpp"
 
 UnifiedOopRef DFSClosure::_reference_stack[max_dfs_depth];
 
 void DFSClosure::find_leaks_from_edge(EdgeStore* edge_store,
-                                      BitSet* mark_bits,
+                                      JFRBitSet* mark_bits,
                                       const Edge* start_edge) {
   assert(edge_store != NULL, "invariant");
   assert(mark_bits != NULL," invariant");
   assert(start_edge != NULL, "invariant");
 
@@ -50,11 +50,11 @@
   DFSClosure dfs(edge_store, mark_bits, start_edge);
   start_edge->pointee()->oop_iterate(&dfs);
 }
 
 void DFSClosure::find_leaks_from_root_set(EdgeStore* edge_store,
-                                          BitSet* mark_bits) {
+                                          JFRBitSet* mark_bits) {
   assert(edge_store != NULL, "invariant");
   assert(mark_bits != NULL, "invariant");
 
   // Mark root set, to avoid going sideways
   DFSClosure dfs(edge_store, mark_bits, NULL);
@@ -66,11 +66,11 @@
   dfs._max_depth = max_dfs_depth;
   dfs._ignore_root_set = true;
   rs.process();
 }
 
-DFSClosure::DFSClosure(EdgeStore* edge_store, BitSet* mark_bits, const Edge* start_edge)
+DFSClosure::DFSClosure(EdgeStore* edge_store, JFRBitSet* mark_bits, const Edge* start_edge)
   :_edge_store(edge_store), _mark_bits(mark_bits), _start_edge(start_edge),
   _max_depth(max_dfs_depth), _depth(0), _ignore_root_set(false) {
 }
 
 void DFSClosure::closure_impl(UnifiedOopRef reference, const oop pointee) {
diff a/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.hpp b/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.hpp
--- a/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.hpp
+++ b/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.hpp
@@ -23,14 +23,14 @@
  */
 
 #ifndef SHARE_JFR_LEAKPROFILER_CHAINS_DFSCLOSURE_HPP
 #define SHARE_JFR_LEAKPROFILER_CHAINS_DFSCLOSURE_HPP
 
+#include "jfr/leakprofiler/chains/jfrbitset.hpp"
 #include "jfr/leakprofiler/utilities/unifiedOopRef.hpp"
 #include "memory/iterator.hpp"
 
-class BitSet;
 class Edge;
 class EdgeStore;
 class EdgeQueue;
 
 // Class responsible for iterating the heap depth-first
@@ -39,26 +39,26 @@
   // max dfs depth should not exceed size of stack
   static const size_t max_dfs_depth = 4000;
   static UnifiedOopRef _reference_stack[max_dfs_depth];
 
   EdgeStore* _edge_store;
-  BitSet* _mark_bits;
+  JFRBitSet* _mark_bits;
   const Edge*_start_edge;
   size_t _max_depth;
   size_t _depth;
   bool _ignore_root_set;
 
-  DFSClosure(EdgeStore* edge_store, BitSet* mark_bits, const Edge* start_edge);
+  DFSClosure(EdgeStore* edge_store, JFRBitSet* mark_bits, const Edge* start_edge);
 
   void add_chain();
   void closure_impl(UnifiedOopRef reference, const oop pointee);
 
  public:
   virtual ReferenceIterationMode reference_iteration_mode() { return DO_FIELDS_EXCEPT_REFERENT; }
 
-  static void find_leaks_from_edge(EdgeStore* edge_store, BitSet* mark_bits, const Edge* start_edge);
-  static void find_leaks_from_root_set(EdgeStore* edge_store, BitSet* mark_bits);
+  static void find_leaks_from_edge(EdgeStore* edge_store, JFRBitSet* mark_bits, const Edge* start_edge);
+  static void find_leaks_from_root_set(EdgeStore* edge_store, JFRBitSet* mark_bits);
   void do_root(UnifiedOopRef ref);
 
   virtual void do_oop(oop* ref);
   virtual void do_oop(narrowOop* ref);
 };
diff a/src/hotspot/share/jfr/leakprofiler/chains/jfrbitset.hpp b/src/hotspot/share/jfr/leakprofiler/chains/jfrbitset.hpp
--- /dev/null
+++ b/src/hotspot/share/jfr/leakprofiler/chains/jfrbitset.hpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022, 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_JFR_LEAKPROFILER_JFRBITSET_HPP
+#define SHARE_JFR_LEAKPROFILER_JFRBITSET_HPP
+
+#include "memory/allocation.hpp"
+#include "utilities/objectBitSet.inline.hpp"
+
+typedef ObjectBitSet<mtTracing> JFRBitSet;
+
+#endif // SHARE_JFR_LEAKPROFILER_JFRBITSET_HPP
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
@@ -64,15 +64,26 @@
   }
 
   void mark(oop obj) {
     assert(obj != NULL, "invariant");
     // save the original markWord
-    _store->push(ObjectSampleMarkWord(obj, obj->mark()));
+    markWord mark = obj->mark();
+    _store->push(ObjectSampleMarkWord(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());
+#ifdef _LP64
+    if (UseCompactObjectHeaders) {
+      if (mark.has_displaced_mark_helper()) {
+        mark = mark.displaced_mark_helper();
+      }
+      obj->set_mark(markWord::prototype().set_marked().set_narrow_klass(mark.narrow_klass()));
+    } else
+#endif
+    {
+      obj->set_mark(markWord::prototype().set_marked());
+    }
     assert(obj->mark().is_marked(), "invariant");
   }
 };
 
 #endif // SHARE_JFR_LEAKPROFILER_CHAINS_OBJECTSAMPLEMARKER_HPP
diff a/src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.cpp b/src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.cpp
--- a/src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.cpp
+++ b/src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.cpp
@@ -25,15 +25,15 @@
 #include "precompiled.hpp"
 #include "gc/shared/collectedHeap.hpp"
 #include "gc/shared/gc_globals.hpp"
 #include "jfr/leakprofiler/leakProfiler.hpp"
 #include "jfr/leakprofiler/chains/bfsClosure.hpp"
-#include "jfr/leakprofiler/chains/bitset.inline.hpp"
 #include "jfr/leakprofiler/chains/dfsClosure.hpp"
 #include "jfr/leakprofiler/chains/edge.hpp"
 #include "jfr/leakprofiler/chains/edgeQueue.hpp"
 #include "jfr/leakprofiler/chains/edgeStore.hpp"
+#include "jfr/leakprofiler/chains/jfrbitset.hpp"
 #include "jfr/leakprofiler/chains/objectSampleMarker.hpp"
 #include "jfr/leakprofiler/chains/rootSetClosure.hpp"
 #include "jfr/leakprofiler/chains/edgeStore.hpp"
 #include "jfr/leakprofiler/chains/objectSampleMarker.hpp"
 #include "jfr/leakprofiler/chains/pathToGcRootsOperation.hpp"
@@ -82,11 +82,11 @@
 void PathToGcRootsOperation::doit() {
   assert(SafepointSynchronize::is_at_safepoint(), "invariant");
   assert(_cutoff_ticks > 0, "invariant");
 
   // The bitset used for marking is dimensioned as a function of the heap size
-  BitSet mark_bits;
+  JFRBitSet mark_bits;
 
   // The edge queue is dimensioned as a fraction of the heap size
   const size_t edge_queue_reservation_size = edge_queue_memory_reservation();
   EdgeQueue edge_queue(edge_queue_reservation_size, edge_queue_memory_commit_size(edge_queue_reservation_size));
 
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
@@ -2115,11 +2115,11 @@
 C2V_VMENTRY_0(jint, arrayBaseOffset, (JNIEnv* env, jobject, jobject kind))
   if (kind == NULL) {
     JVMCI_THROW_0(NullPointerException);
   }
   BasicType type = JVMCIENV->kindToBasicType(JVMCIENV->wrap(kind), 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, jobject kind))
   if (kind == NULL) {
     JVMCI_THROW_0(NullPointerException);
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
@@ -215,11 +215,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_intx_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
@@ -99,10 +99,14 @@
     if (BootstrapJVMCI && (TieredStopAtLevel < CompLevel_full_optimization)) {
       jio_fprintf(defaultStream::error_stream(),
           "-XX:+BootstrapJVMCI is not compatible with -XX:TieredStopAtLevel=%d\n", TieredStopAtLevel);
       return false;
     }
+    if (UseCompactObjectHeaders) {
+      log_warning(jvmci)("-XX:+UseCompactObjectHeaders not supported by JVMCI, disabling UseCompactObjectHeaders");
+      FLAG_SET_DEFAULT(UseCompactObjectHeaders, false);
+    }
   }
 
   if (!EnableJVMCI) {
     // Switch off eager JVMCI initialization if JVMCI is disabled.
     // Don't throw error if EagerJVMCI is set to allow testing.
@@ -114,11 +118,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(UseJVMCINativeLibrary,        EnableJVMCI)
   CHECK_NOT_SET(JVMCILibPath,                 EnableJVMCI)
   CHECK_NOT_SET(JVMCILibDumpJNIConfig,        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
@@ -101,13 +101,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, NULL, 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
@@ -188,10 +188,12 @@
   nonstatic_field(JavaThread,                  _jvmci_reserved_oop0,                          oop)                                   \
   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,                  _lock_stack,                                   LockStack)                             \
+  nonstatic_field(LockStack,                   _top,                                          uint32_t)                              \
                                                                                                                                      \
   static_field(java_lang_Class,                _klass_offset,                                 int)                                   \
   static_field(java_lang_Class,                _array_klass_offset,                           int)                                   \
                                                                                                                                      \
   nonstatic_field(InvocationCounter,           _counter,                                      unsigned int)                          \
@@ -450,10 +452,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)                \
@@ -578,10 +581,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(InstanceKlass::_misc_has_nonstatic_concrete_methods)   \
@@ -626,10 +633,13 @@
   declare_constant(Method::invalid_vtable_index)                          \
                                                                           \
   declare_constant(MultiBranchData::per_case_cell_count)                  \
                                                                           \
   declare_constant(ReceiverTypeData::nonprofiled_count_off_set)           \
+                                                                          \
+  declare_constant(ObjectMonitor::ANONYMOUS_OWNER)                        \
+                                                                          \
   declare_constant(ReceiverTypeData::receiver_type_row_cell_count)        \
   declare_constant(ReceiverTypeData::receiver0_offset)                    \
   declare_constant(ReceiverTypeData::count0_offset)                       \
                                                                           \
   declare_constant(vmIntrinsics::_invokeBasic)                            \
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
@@ -48,12 +48,11 @@
   // 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);
+    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");
@@ -69,25 +68,32 @@
 
   // 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.
   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:
   // 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);
+    return oopDesc::base_offset_in_bytes();
   }
 
   // Returns the offset of the first element.
   static int base_offset_in_bytes(BasicType type) {
-    return header_size(type) * HeapWordSize;
+    size_t typesize_in_bytes = header_size_in_bytes();
+    return (int)(element_type_should_be_aligned(type)
+                 ? align_up(typesize_in_bytes, BytesPerLong)
+                 : typesize_in_bytes);
   }
 
   // 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 {
@@ -125,10 +131,11 @@
   // 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) {
+    assert(!UseCompactObjectHeaders, "Don't use this with compact headers");
     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);
   }
@@ -139,20 +146,19 @@
   // 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");
 
-    const size_t max_element_words_per_size_t =
-      align_down((SIZE_MAX/HeapWordSize - header_size(type)), MinObjAlignment);
-    const size_t max_elements_per_size_t =
-      HeapWordSize * max_element_words_per_size_t / type2aelembytes(type);
+    const size_t max_size_bytes = align_down(SIZE_MAX - base_offset_in_bytes(type), MinObjAlignmentInBytes);
+    const size_t max_elements_per_size_t = max_size_bytes / 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);
+      int header_size_words = align_up(base_offset_in_bytes(type), HeapWordSize) / HeapWordSize;
+      return align_down(max_jint - header_size_words, MinObjAlignment);
     }
     return (int32_t)max_elements_per_size_t;
   }
 
 };
diff a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp
--- a/src/hotspot/share/oops/instanceKlass.cpp
+++ b/src/hotspot/share/oops/instanceKlass.cpp
@@ -2586,11 +2586,17 @@
   }
 
   // Initialize @ValueBased class annotation
   if (DiagnoseSyncOnValueBasedClasses && has_value_based_class_annotation()) {
     set_is_value_based();
-    set_prototype_header(markWord::prototype());
+    markWord prototype = markWord::prototype();
+#ifdef _LP64
+    if (UseCompactObjectHeaders) {
+      prototype = prototype.set_klass(this);
+    }
+#endif
+    set_prototype_header(prototype);
   }
 }
 
 // Check if a class or any of its supertypes has a version older than 50.
 // CDS will not perform verification of old classes during dump time because
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
@@ -30,18 +30,12 @@
 // 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);
-
+    return oopDesc::base_offset_in_bytes();
   }
 };
 
 #endif // SHARE_OOPS_INSTANCEOOP_HPP
diff a/src/hotspot/share/oops/instanceRefKlass.hpp b/src/hotspot/share/oops/instanceRefKlass.hpp
--- a/src/hotspot/share/oops/instanceRefKlass.hpp
+++ b/src/hotspot/share/oops/instanceRefKlass.hpp
@@ -124,11 +124,11 @@
   // Apply the closure to all fields, except the referent field. No reference discovery is done.
   template <typename T, class OopClosureType, class Contains>
   static void oop_oop_iterate_fields_except_referent(oop obj, OopClosureType* closure, Contains& contains);
 
   template <typename T>
-  static void trace_reference_gc(const char *s, oop obj) NOT_DEBUG_RETURN;
+  void trace_reference_gc(const char *s, oop obj) NOT_DEBUG_RETURN;
 
  public:
   // Update non-static oop maps so 'referent', 'nextPending' and
   // 'discovered' will look like non-oops
   static void update_nonstatic_oop_maps(Klass* k);
diff a/src/hotspot/share/oops/instanceRefKlass.inline.hpp b/src/hotspot/share/oops/instanceRefKlass.inline.hpp
--- a/src/hotspot/share/oops/instanceRefKlass.inline.hpp
+++ b/src/hotspot/share/oops/instanceRefKlass.inline.hpp
@@ -183,11 +183,11 @@
 void InstanceRefKlass::trace_reference_gc(const char *s, oop obj) {
   T* referent_addr   = (T*) java_lang_ref_Reference::referent_addr_raw(obj);
   T* discovered_addr = (T*) java_lang_ref_Reference::discovered_addr_raw(obj);
 
   log_develop_trace(gc, ref)("InstanceRefKlass %s for obj " PTR_FORMAT, s, p2i(obj));
-  if (java_lang_ref_Reference::is_phantom(obj)) {
+  if (reference_type() == REF_PHANTOM) {
     log_develop_trace(gc, ref)("     referent_addr/* " PTR_FORMAT " / " PTR_FORMAT,
                                p2i(referent_addr), p2i((oop)HeapAccess<ON_PHANTOM_OOP_REF | AS_NO_KEEPALIVE>::oop_load(referent_addr)));
   } else {
     log_develop_trace(gc, ref)("     referent_addr/* " PTR_FORMAT " / " PTR_FORMAT,
                                p2i(referent_addr), p2i((oop)HeapAccess<ON_WEAK_OOP_REF | AS_NO_KEEPALIVE>::oop_load(referent_addr)));
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
@@ -191,16 +191,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 preceeded 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(KlassID id) : _id(id),
-                           _prototype_header(markWord::prototype()),
+                           _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()));
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,11 +50,11 @@
 inline bool Klass::is_loader_alive() const {
   return class_loader_data()->is_alive();
 }
 
 inline void Klass::set_prototype_header(markWord header) {
-  assert(!header.has_bias_pattern() || is_instance_klass(), "biased locking currently only supported for Java instances");
+  assert(UseCompactObjectHeaders || !header.has_bias_pattern() || is_instance_klass(), "biased locking currently only supported for Java instances");
   _prototype_header = header;
 }
 
 inline oop Klass::java_mirror() const {
   return _java_mirror.resolve();
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/integralConstant.hpp"
 #include "metaprogramming/primitiveConversions.hpp"
 #include "oops/oopsHierarchy.hpp"
 #include "runtime/globals.hpp"
 
@@ -42,10 +43,14 @@
 //  64 bits:
 //  --------
 //  unused:25 hash:31 -->| unused_gap:1   age:4    biased_lock:1 lock:2 (normal object)
 //  JavaThread*:54 epoch:2 unused_gap:1   age:4    biased_lock:1 lock:2 (biased 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
 //
@@ -79,23 +84,24 @@
 //
 //  - the two lock bits are used to describe three states: locked/unlocked and monitor.
 //
 //    [ptr             | 00]  locked             ptr points to real header on stack
 //    [header      | 0 | 01]  unlocked           regular object header
-//    [ptr             | 10]  monitor            inflated lock (header is wapped out)
+//    [ptr             | 10]  monitor            inflated lock (header is swapped out)
 //    [ptr             | 11]  marked             used to mark an object
-//    [0 ............ 0| 00]  inflating          inflation in progress
+//    [0 ............ 0| 00]  inflating          inflation in progress (stack-locking in use)
 //
 //    We assume that stack/thread pointers have the lowest two bits cleared.
 //
 //  - INFLATING() is a distinguished markword value of all zeros that is
 //    used when inflating an existing stack-lock into an ObjectMonitor.
 //    See below for is_being_inflated() and INFLATING().
 
 class BasicLock;
 class ObjectMonitor;
 class JavaThread;
+class Klass;
 class outputStream;
 
 class markWord {
  private:
   uintptr_t _value;
@@ -127,36 +133,58 @@
 
   // Constants
   static const int age_bits                       = 4;
   static const int lock_bits                      = 2;
   static const int biased_lock_bits               = 1;
-  static const int max_hash_bits                  = BitsPerWord - age_bits - lock_bits - biased_lock_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 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);
   static const int epoch_bits                     = 2;
+#ifdef _LP64
+  // Used only with compact headers.
+  static const int klass_bits                     = 32;
+#endif
 
   // The biased locking code currently requires that the age bits be
   // contiguous to the lock bits.
   static const int lock_shift                     = 0;
   static const int biased_lock_shift              = lock_bits;
-  static const int age_shift                      = lock_bits + biased_lock_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 unused_gap_shift               = age_shift + age_bits;
   static const int hash_shift                     = unused_gap_shift + 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 int epoch_shift                    = hash_shift;
 
   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 biased_lock_mask         = right_n_bits(lock_bits + biased_lock_bits);
   static const uintptr_t biased_lock_mask_in_place= biased_lock_mask << lock_shift;
   static const uintptr_t biased_lock_bit_in_place = 1 << biased_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 epoch_mask               = right_n_bits(epoch_bits);
   static const uintptr_t epoch_mask_in_place      = epoch_mask << epoch_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
+  static const uintptr_t klass_mask               = right_n_bits(klass_bits);
+  static const uintptr_t klass_mask_in_place      = klass_mask << klass_shift;
+#endif
 
   // Alignment of JavaThread pointers encoded in object header required by biased locking
   static const size_t biased_lock_alignment       = 2 << (epoch_shift + epoch_bits);
 
   static const uintptr_t locked_value             = 0;
@@ -234,10 +262,11 @@
   // an existing stack-lock.  0 indicates the markword is "BUSY".
   // Lockword mutators that use a LD...CAS idiom should always
   // check for and avoid overwriting a 0 value installed by some
   // other thread.  (They should spin or block instead.  The 0 value
   // is transient and *should* be short-lived).
+  // Fast-locking does not use INFLATING.
   static markWord INFLATING() { return zero(); }    // inflate-in-progress
 
   // Should this header be preserved during GC?
   inline bool must_be_preserved(const oopDesc* obj) const;
 
@@ -265,33 +294,51 @@
   // They must get updated if markWord layout get changed.
   markWord set_unlocked() const {
     return markWord(value() | unlocked_value);
   }
   bool has_locker() const {
-    return ((value() & lock_mask_in_place) == locked_value);
+    assert(LockingMode == LM_LEGACY, "should only be called with legacy stack locking");
+    return (value() & lock_mask_in_place) == locked_value;
   }
   BasicLock* locker() const {
     assert(has_locker(), "check");
     return (BasicLock*) value();
   }
+
+  bool is_fast_locked() const {
+    assert(LockingMode == LM_LIGHTWEIGHT, "should only be called with new lightweight locking");
+    return (value() & lock_mask_in_place) == locked_value;
+  }
+  markWord set_fast_locked() const {
+    return markWord(value() & ~lock_mask_in_place);
+  }
+
   bool has_monitor() const {
     return ((value() & monitor_value) != 0);
   }
   ObjectMonitor* monitor() const {
     assert(has_monitor(), "check");
     // Use xor instead of &~ to provide one extra tag-bit check.
     return (ObjectMonitor*) (value() ^ monitor_value);
   }
   bool has_displaced_mark_helper() const {
-    return ((value() & unlocked_value) == 0);
+    intptr_t lockbits = value() & lock_mask_in_place;
+    return LockingMode == LM_LIGHTWEIGHT  ? lockbits == monitor_value   // monitor?
+                                          : (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);
@@ -327,17 +374,31 @@
   }
   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 Klass* safe_klass() const;
+  inline markWord set_klass(const Klass* klass) const;
+  inline narrowKlass narrow_klass() const;
+  inline markWord set_narrow_klass(const narrowKlass klass) const;
+#endif
+
   // Prototype mark for initialization
   static markWord prototype() {
     return markWord( no_hash_in_place | no_lock_in_place );
   }
 
@@ -350,10 +411,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() { if (UseBiasedLocking && has_bias_pattern()) return NULL; 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 TrueType {
diff a/src/hotspot/share/oops/markWord.inline.hpp b/src/hotspot/share/oops/markWord.inline.hpp
--- a/src/hotspot/share/oops/markWord.inline.hpp
+++ b/src/hotspot/share/oops/markWord.inline.hpp
@@ -28,10 +28,11 @@
 #include "oops/markWord.hpp"
 
 #include "oops/klass.hpp"
 #include "oops/oop.inline.hpp"
 #include "runtime/globals.hpp"
+#include "runtime/safepoint.hpp"
 
 // Should this header be preserved during GC?
 inline bool markWord::must_be_preserved(const oopDesc* obj) const {
   if (UseBiasedLocking) {
     if (has_bias_pattern()) {
@@ -68,11 +69,61 @@
   return (!is_unlocked() || !has_no_hash());
 }
 
 inline markWord markWord::prototype_for_klass(const Klass* klass) {
   markWord prototype_header = klass->prototype_header();
-  assert(prototype_header == prototype() || prototype_header.has_bias_pattern(), "corrupt prototype header");
+  assert(UseCompactObjectHeaders || prototype_header == prototype() || prototype_header.has_bias_pattern(), "corrupt prototype header");
 
   return prototype_header;
 }
 
+#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;
+  }
+}
+
+narrowKlass markWord::narrow_klass() const {
+  assert(UseCompactObjectHeaders, "only used with compact object headers");
+  return narrowKlass(value() >> klass_shift);
+}
+
+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());
+}
+
+markWord markWord::set_narrow_klass(const narrowKlass nklass) const {
+  assert(UseCompactObjectHeaders, "only used with compact object headers");
+  return markWord((value() & ~klass_mask_in_place) | ((uintptr_t) nklass << klass_shift));
+}
+
+Klass* markWord::safe_klass() const {
+  assert(UseCompactObjectHeaders, "only used with compact object headers");
+  assert(SafepointSynchronize::is_at_safepoint(), "only call at safepoint");
+  markWord m = *this;
+  if (m.has_displaced_mark_helper()) {
+    m = m.displaced_mark_helper();
+  }
+  return CompressedKlassPointers::decode_not_null(m.narrow_klass());
+}
+
+markWord markWord::set_klass(const Klass* klass) const {
+  assert(UseCompactObjectHeaders, "only used with compact object headers");
+  assert(UseCompressedClassPointers, "expect compressed klass pointers");
+  // TODO: Don't cast to non-const, change CKP::encode() to accept const Klass* instead.
+  narrowKlass nklass = CompressedKlassPointers::encode(const_cast<Klass*>(klass));
+  return set_narrow_klass(nklass);
+}
+#endif
+
 #endif // SHARE_OOPS_MARKWORD_INLINE_HPP
diff a/src/hotspot/share/oops/objArrayKlass.cpp b/src/hotspot/share/oops/objArrayKlass.cpp
--- a/src/hotspot/share/oops/objArrayKlass.cpp
+++ b/src/hotspot/share/oops/objArrayKlass.cpp
@@ -154,11 +154,11 @@
   assert(is_array_klass(), "sanity");
   assert(is_objArray_klass(), "sanity");
 }
 
 int ObjArrayKlass::oop_size(oop obj) const {
-  assert(obj->is_objArray(), "must be object array");
+  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,11 @@
   }
 }
 
 template <typename T, typename OopClosureType>
 void ObjArrayKlass::oop_oop_iterate(oop obj, OopClosureType* closure) {
-  assert (obj->is_array(), "obj must be array");
+  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
@@ -45,33 +45,13 @@
   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;
+  // Give size of objArrayOop in bytes minus the header
+  static size_t array_size_in_bytes(int length) {
+    return (size_t)length * heapOopSize;
   }
 
  public:
   // Returns the offset of the first element.
   static int base_offset_in_bytes() {
@@ -87,20 +67,19 @@
   void obj_at_put(int index, oop value);
 
   oop atomic_compare_exchange_oop(int index, oop exchange_value, oop compare_value);
 
   // Sizing
-  static int header_size()    { return arrayOopDesc::header_size(T_OBJECT); }
   int object_size()           { return object_size(length()); }
 
   static int 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 (int)osz;
+    size_t asz = array_size_in_bytes(length);
+    size_t size_words = align_up(base_offset_in_bytes() + asz, HeapWordSize) / HeapWordSize;
+    size_t osz = align_object_size(size_words);
+    assert(osz < max_jint, "no overflow");
+    return checked_cast<int>(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
@@ -103,20 +103,20 @@
   if (!Universe::heap()->is_oop(obj)) {
     return false;
   }
 
   // Header verification: the mark is typically non-zero. If we're
-  // at a safepoint, it must not be zero.
+  // at a safepoint, it must not be zero, except when using the new lightweight locking.
   // Outside of a safepoint, the header could be changing (for example,
   // another thread could be inflating a lock on this object).
   if (ignore_mark_word) {
     return true;
   }
   if (obj->mark().value() != 0) {
     return true;
   }
-  return !SafepointSynchronize::is_at_safepoint();
+  return LockingMode == LM_LIGHTWEIGHT || !SafepointSynchronize::is_at_safepoint();
 }
 
 // used only for asserts and guarantees
 bool oopDesc::is_oop_or_null(oop obj, bool ignore_mark_word) {
   return obj == NULL ? true : is_oop(obj, ignore_mark_word);
@@ -137,30 +137,33 @@
 bool oopDesc::is_array_noinline()             const { return is_array();               }
 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;
+  // Only has a klass gap when compressed class pointers are used, but
+  // only if not 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!");
   assert(UseCompressedClassPointers, "must be");
+  assert(!UseCompactObjectHeaders, "not with compact headers");
   _metadata._compressed_klass = nk;
 }
 #endif
 
 void* oopDesc::load_klass_raw(oop obj) {
-  if (UseCompressedClassPointers) {
-    narrowKlass narrow_klass = obj->_metadata._compressed_klass;
-    if (narrow_klass == 0) return NULL;
-    return (void*)CompressedKlassPointers::decode_raw(narrow_klass);
-  } else {
-    return obj->_metadata._klass;
-  }
+  // TODO: Remove method altogether and replace with calls to obj->klass() ?
+  // OTOH, we may eventually get rid of locking in header, and then no
+  // longer have to deal with that anymore.
+#ifdef _LP64
+  return obj->klass();
+#else
+  return obj->_metadata._klass;
+#endif
 }
 
 void* oopDesc::load_oop_raw(oop obj, int offset) {
   uintptr_t addr = (uintptr_t)(void*)obj + (uint)offset;
   if (UseCompressedOops) {
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
@@ -55,22 +55,28 @@
   union _metadata {
     Klass*      _klass;
     narrowKlass _compressed_klass;
   } _metadata;
 
- public:
+public:
   inline markWord  mark()          const;
   inline markWord  mark_acquire()  const;
   inline markWord* mark_addr() const;
 
   inline void set_mark(markWord m);
   static inline void set_mark(HeapWord* mem, markWord m);
 
   inline void release_set_mark(markWord m);
+  static inline void release_set_mark(HeapWord* mem, 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;
@@ -85,11 +91,18 @@
   inline int klass_gap() const;
   inline void set_klass_gap(int z);
   static inline void set_klass_gap(HeapWord* mem, int z);
 
   // size of object header, aligned to platform wordSize
-  static int header_size() { return sizeof(oopDesc)/HeapWordSize; }
+  static int header_size() {
+#ifdef _LP64
+    if (UseCompactObjectHeaders) {
+      return sizeof(markWord) / HeapWordSize;
+    } else
+#endif
+    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
@@ -97,10 +110,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 int 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 size_t   forward_safe_size();
+  inline Klass*   forward_safe_klass(markWord m) const;
+  inline void     forward_safe_init_mark();
+
   // type test operations (inlined in oop.inline.hpp)
   inline bool is_instance()            const;
   inline bool is_array()               const;
   inline bool is_objArray()            const;
   inline bool is_typeArray()           const;
@@ -247,19 +274,21 @@
   inline bool is_forwarded() const;
 
   void verify_forwardee(oop forwardee) NOT_DEBUG_RETURN;
 
   inline void forward_to(oop p);
-  inline bool cas_forward_to(oop p, markWord compare, atomic_memory_order order = memory_order_conservative);
+  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();
 
@@ -301,16 +330,40 @@
 
   static bool has_klass_gap();
 
   // for code generation
   static int mark_offset_in_bytes()      { return offset_of(oopDesc, _mark); }
-  static int klass_offset_in_bytes()     { return 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 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 offset_of(oopDesc, _metadata._klass);
+  }
+
+  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.
+      assert(sizeof(markWord) == 8, "sanity");
+      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);
 
   // Avoid include gc_globals.hpp in oop.inline.hpp
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
@@ -34,10 +34,12 @@
 #include "oops/compressedOops.inline.hpp"
 #include "oops/markWord.inline.hpp"
 #include "oops/oopsHierarchy.hpp"
 #include "runtime/atomic.hpp"
 #include "runtime/globals.hpp"
+#include "runtime/safepoint.hpp"
+#include "runtime/objectMonitor.inline.hpp"
 #include "utilities/align.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/macros.hpp"
 #include "utilities/globalDefinitions.hpp"
 
@@ -49,11 +51,10 @@
 }
 
 markWord oopDesc::mark_acquire() const {
   return Atomic::load_acquire(&_mark);
 }
-
 markWord* oopDesc::mark_addr() const {
   return (markWord*) &_mark;
 }
 
 void oopDesc::set_mark(markWord m) {
@@ -66,78 +67,122 @@
 
 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 hdr = mark();
+  if (hdr.has_displaced_mark_helper()) {
+    hdr = hdr.displaced_mark_helper();
+  }
+  return hdr;
+}
+
+markWord oopDesc::prototype_mark() const {
+  if (UseCompactObjectHeaders) {
+    return klass()->prototype_header();
+  } else {
+    return markWord::prototype();
+  }
+}
+
 void oopDesc::init_mark() {
   set_mark(markWord::prototype_for_klass(klass()));
 }
 
 Klass* oopDesc::klass() const {
-  if (UseCompressedClassPointers) {
+#ifdef _LP64
+  if (UseCompactObjectHeaders) {
+    assert(UseCompressedClassPointers, "only with compressed class pointers");
+    markWord header = resolve_mark();
+    return header.klass();
+  } else if (UseCompressedClassPointers) {
     return CompressedKlassPointers::decode_not_null(_metadata._compressed_klass);
-  } else {
-    return _metadata._klass;
-  }
+  } else
+#endif
+  return _metadata._klass;
 }
 
 Klass* oopDesc::klass_or_null() const {
-  if (UseCompressedClassPointers) {
+#ifdef _LP64
+  if (UseCompactObjectHeaders) {
+    assert(UseCompressedClassPointers, "only with compressed class pointers");
+    markWord header = resolve_mark();
+    return header.klass_or_null();
+  } else if (UseCompressedClassPointers) {
     return CompressedKlassPointers::decode(_metadata._compressed_klass);
-  } else {
-    return _metadata._klass;
-  }
+  } 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 {
-    return Atomic::load_acquire(&_metadata._klass);
-  }
+#ifdef _LP64
+  if (UseCompactObjectHeaders) {
+    assert(UseCompressedClassPointers, "only with compressed class pointers");
+    markWord header = mark_acquire();
+    if (header.has_monitor()) {
+      header = header.monitor()->header();
+    }
+    return header.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);
 }
 
 void oopDesc::set_klass(Klass* k) {
   assert(Universe::is_bootstrapping() || (k != NULL && 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 != NULL && 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);
   }
 }
 
 int oopDesc::klass_gap() const {
+  assert(!UseCompactObjectHeaders, "don't get Klass* gap with compact headers");
   return *(int*)(((intptr_t)this) + klass_gap_offset_in_bytes());
 }
 
 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;
   }
 }
 
 void oopDesc::set_klass_gap(int v) {
+  assert(!UseCompactObjectHeaders, "don't set Klass* gap with compact headers");
   set_klass_gap((HeapWord*)this, v);
 }
 
 bool oopDesc::is_a(Klass* k) const {
   return klass()->is_subtype_of(k);
@@ -201,10 +246,57 @@
   assert(s > 0, "Oop size must be greater than zero, not %d", s);
   assert(is_object_aligned(s), "Oop size is not properly aligned: %d", 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 {
+    init_mark();
+  }
+}
+
 bool oopDesc::is_instance()  const { return klass()->is_instance_klass();  }
 bool oopDesc::is_array()     const { return klass()->is_array_klass();     }
 bool oopDesc::is_objArray()  const { return klass()->is_objArray_klass();  }
 bool oopDesc::is_typeArray() const { return klass()->is_typeArray_klass(); }
 
@@ -272,41 +364,93 @@
   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");
   verify_forwardee(p);
   markWord m = markWord::encode_pointer_as_mark(p);
-  assert(m.decode_pointer() == p, "encoding must be reversable");
+  assert(forwardee(m) == p, "encoding must be reversable");
   set_mark(m);
 }
 
-// Used by parallel scavengers
-bool oopDesc::cas_forward_to(oop p, markWord compare, atomic_memory_order order) {
-  verify_forwardee(p);
-  markWord m = markWord::encode_pointer_as_mark(p);
-  assert(m.decode_pointer() == p, "encoding must be reversable");
-  return cas_set_mark(m, compare, order) == compare;
+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");
   verify_forwardee(p);
   markWord m = markWord::encode_pointer_as_mark(p);
-  assert(m.decode_pointer() == p, "encoding must be reversable");
+  assert(forwardee(m) == p, "encoding must be reversable");
   markWord old_mark = cas_set_mark(m, compare, order);
   if (old_mark == compare) {
     return NULL;
   } 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 {
-  return cast_to_oop(mark().decode_pointer());
+  return forwardee(mark());
 }
 
 // The following method needs to be MT safe.
 uint oopDesc::age() const {
   assert(!is_forwarded(), "Attempt to read age from forwarded mark");
@@ -357,11 +501,11 @@
   oop_iterate_backwards(cl, klass());
 }
 
 template <typename OopClosureType>
 void oopDesc::oop_iterate_backwards(OopClosureType* cl, Klass* k) {
-  assert(k == klass(), "wrong klass");
+  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 == NULL || 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,11 @@
 Klass* TypeArrayKlass::array_klass_or_null() {
   return array_klass_or_null(dimension() +  1);
 }
 
 int TypeArrayKlass::oop_size(oop obj) const {
-  assert(obj->is_typeArray(),"must be a type array");
+  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,11 @@
 #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");
+  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.cpp b/src/hotspot/share/opto/c2_CodeStubs.cpp
--- /dev/null
+++ b/src/hotspot/share/opto/c2_CodeStubs.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2022, 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 "asm/codeBuffer.hpp"
+#include "code/codeBlob.hpp"
+#include "opto/c2_CodeStubs.hpp"
+#include "opto/c2_MacroAssembler.hpp"
+#include "opto/compile.hpp"
+#include "opto/output.hpp"
+
+C2CodeStubList::C2CodeStubList() :
+    _stubs(Compile::current()->comp_arena(), 2, 0, NULL) {}
+
+void C2CodeStubList::emit(CodeBuffer& cb) {
+  C2_MacroAssembler masm(&cb);
+  for (int i = _stubs.length() - 1; i >= 0; i--) {
+    C2CodeStub* stub = _stubs.at(i);
+    int max_size = stub->max_size();
+    // Make sure there is enough space in the code buffer
+    if (cb.insts()->maybe_expand_to_ensure_remaining(max_size) && cb.blob() == NULL) {
+      ciEnv::current()->record_failure("CodeCache is full");
+      return;
+    }
+
+    DEBUG_ONLY(int size_before = cb.insts_size();)
+
+    stub->emit(masm);
+
+    DEBUG_ONLY(int actual_size = cb.insts_size() - size_before;)
+    assert(max_size >= actual_size, "Expected stub size (%d) must be larger than or equal to actual stub size (%d)", max_size, actual_size);
+  }
+  _stubs.clear();
+}
diff a/src/hotspot/share/opto/c2_CodeStubs.hpp b/src/hotspot/share/opto/c2_CodeStubs.hpp
--- /dev/null
+++ b/src/hotspot/share/opto/c2_CodeStubs.hpp
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ *
+ * 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 "asm/assembler.hpp"
+#include "asm/codeBuffer.hpp"
+#include "memory/allocation.hpp"
+#include "opto/c2_MacroAssembler.hpp"
+#include "utilities/growableArray.hpp"
+
+#ifndef SHARE_OPTO_C2_CODESTUBS_HPP
+#define SHARE_OPTO_C2_CODESTUBS_HPP
+
+class C2CodeStub : public ResourceObj {
+private:
+  Label _entry;
+  Label _continuation;
+
+protected:
+  C2CodeStub() :
+    _entry(),
+    _continuation() {}
+  ~C2CodeStub() {}
+
+public:
+  Label& entry()        { return _entry; }
+  Label& continuation() { return _continuation; }
+
+  virtual void emit(C2_MacroAssembler& masm) = 0;
+  virtual int max_size() const = 0;
+};
+
+class C2CodeStubList {
+private:
+  GrowableArray<C2CodeStub*> _stubs;
+
+public:
+  C2CodeStubList();
+  ~C2CodeStubList() {}
+  void add_stub(C2CodeStub* stub) { _stubs.append(stub); }
+  void emit(CodeBuffer& cb);
+};
+
+class C2SafepointPollStub : public C2CodeStub {
+private:
+  uintptr_t _safepoint_offset;
+public:
+  C2SafepointPollStub(uintptr_t safepoint_offset) :
+    _safepoint_offset(safepoint_offset) {}
+  int max_size() const;
+  void emit(C2_MacroAssembler& masm);
+};
+
+class C2CheckLockStackStub : public C2CodeStub {
+public:
+  C2CheckLockStackStub() : C2CodeStub() {}
+  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;
+public:
+  C2HandleAnonOMOwnerStub(Register monitor, Register tmp = noreg) : C2CodeStub(),
+    _monitor(monitor), _tmp(tmp) {}
+  Register monitor() { return _monitor; }
+  Register tmp() { return _tmp; }
+  int max_size() const;
+  void emit(C2_MacroAssembler& masm);
+};
+
+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
+
+#endif // SHARE_OPTO_C2_CODESTUBS_HPP
diff a/src/hotspot/share/opto/c2_MacroAssembler.hpp b/src/hotspot/share/opto/c2_MacroAssembler.hpp
--- a/src/hotspot/share/opto/c2_MacroAssembler.hpp
+++ b/src/hotspot/share/opto/c2_MacroAssembler.hpp
@@ -25,10 +25,11 @@
 #ifndef SHARE_OPTO_C2_MACROASSEMBLER_HPP
 #define SHARE_OPTO_C2_MACROASSEMBLER_HPP
 
 #include "asm/macroAssembler.hpp"
 #include "asm/macroAssembler.inline.hpp"
+#include "utilities/globalDefinitions.hpp"
 #include "utilities/macros.hpp"
 
 class C2_MacroAssembler: public MacroAssembler {
  public:
   // creation
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
@@ -1675,15 +1675,16 @@
   }
 }
 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
-  if (UseBiasedLocking && Opcode() == Op_Allocate) {
+  if ((UseBiasedLocking && Opcode() == Op_Allocate) || 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;
 }
 
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
@@ -1645,10 +1645,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
@@ -3841,25 +3841,33 @@
   // The control of the load must be null. Otherwise, the load can move before
   // the null check after castPP removal.
   Node* no_ctrl = nullptr;
   Node* header = make_load(no_ctrl, header_addr, TypeX_X, TypeX_X->basic_type(), MemNode::unordered);
 
-  // Test the header to see if it is unlocked.
+  // Test the header to see if it is safe to read w.r.t. locking.
   Node *lock_mask      = _gvn.MakeConX(markWord::biased_lock_mask_in_place);
   Node *lmasked_header = _gvn.transform(new AndXNode(header, lock_mask));
-  Node *unlocked_val   = _gvn.MakeConX(markWord::unlocked_value);
-  Node *chk_unlocked   = _gvn.transform(new CmpXNode( lmasked_header, unlocked_val));
-  Node *test_unlocked  = _gvn.transform(new BoolNode( chk_unlocked, BoolTest::ne));
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    Node *monitor_val   = _gvn.MakeConX(markWord::monitor_value);
+    Node *chk_monitor   = _gvn.transform(new CmpXNode(lmasked_header, monitor_val));
+    Node *test_monitor  = _gvn.transform(new BoolNode(chk_monitor, BoolTest::eq));
 
-  generate_slow_guard(test_unlocked, slow_region);
+    generate_slow_guard(test_monitor, slow_region);
+  } else {
+    Node *unlocked_val      = _gvn.MakeConX(markWord::unlocked_value);
+    Node *chk_unlocked      = _gvn.transform(new CmpXNode(lmasked_header, unlocked_val));
+    Node *test_not_unlocked = _gvn.transform(new BoolNode(chk_unlocked, BoolTest::ne));
+
+    generate_slow_guard(test_not_unlocked, slow_region);
+  }
 
   // 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
@@ -1675,11 +1675,14 @@
   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
@@ -1850,10 +1850,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());
@@ -2020,10 +2027,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.
@@ -2104,11 +2118,11 @@
       return Type::get_zero_type(_type->basic_type());
     }
   }
 
   Node* alloc = is_new_object_mark_load(phase);
-  if (alloc != nullptr && !(alloc->Opcode() == Op_Allocate && UseBiasedLocking)) {
+  if (alloc != nullptr && !(alloc->Opcode() == Op_Allocate && UseBiasedLocking) && !UseCompactObjectHeaders) {
     return TypeX::make(markWord::prototype().value());
   }
 
   return _type;
 }
diff a/src/hotspot/share/opto/output.cpp b/src/hotspot/share/opto/output.cpp
--- a/src/hotspot/share/opto/output.cpp
+++ b/src/hotspot/share/opto/output.cpp
@@ -223,83 +223,17 @@
   }
 #endif
 
 };
 
-volatile int C2SafepointPollStubTable::_stub_size = 0;
-
-Label& C2SafepointPollStubTable::add_safepoint(uintptr_t safepoint_offset) {
-  C2SafepointPollStub* entry = new (Compile::current()->comp_arena()) C2SafepointPollStub(safepoint_offset);
-  _safepoints.append(entry);
-  return entry->_stub_label;
-}
-
-void C2SafepointPollStubTable::emit(CodeBuffer& cb) {
-  MacroAssembler masm(&cb);
-  for (int i = _safepoints.length() - 1; i >= 0; i--) {
-    // Make sure there is enough space in the code buffer
-    if (cb.insts()->maybe_expand_to_ensure_remaining(PhaseOutput::MAX_inst_size) && cb.blob() == NULL) {
-      ciEnv::current()->record_failure("CodeCache is full");
-      return;
-    }
-
-    C2SafepointPollStub* entry = _safepoints.at(i);
-    emit_stub(masm, entry);
-  }
-}
-
-int C2SafepointPollStubTable::stub_size_lazy() const {
-  int size = Atomic::load(&_stub_size);
-
-  if (size != 0) {
-    return size;
-  }
-
-  Compile* const C = Compile::current();
-  BufferBlob* const blob = C->output()->scratch_buffer_blob();
-  CodeBuffer cb(blob->content_begin(), C->output()->scratch_buffer_code_size());
-  MacroAssembler masm(&cb);
-  C2SafepointPollStub* entry = _safepoints.at(0);
-  emit_stub(masm, entry);
-  size += cb.insts_size();
-
-  Atomic::store(&_stub_size, size);
-
-  return size;
-}
-
-int C2SafepointPollStubTable::estimate_stub_size() const {
-  if (_safepoints.length() == 0) {
-    return 0;
-  }
-
-  int result = stub_size_lazy() * _safepoints.length();
-
-#ifdef ASSERT
-  Compile* const C = Compile::current();
-  BufferBlob* const blob = C->output()->scratch_buffer_blob();
-  int size = 0;
-
-  for (int i = _safepoints.length() - 1; i >= 0; i--) {
-    CodeBuffer cb(blob->content_begin(), C->output()->scratch_buffer_code_size());
-    MacroAssembler masm(&cb);
-    C2SafepointPollStub* entry = _safepoints.at(i);
-    emit_stub(masm, entry);
-    size += cb.insts_size();
-  }
-  assert(size == result, "stubs should not have variable size");
-#endif
-
-  return result;
-}
-
 PhaseOutput::PhaseOutput()
   : Phase(Phase::Output),
     _code_buffer("Compile::Fill_buffer"),
     _first_block_size(0),
     _handler_table(),
     _inc_table(),
+    _stub_list(),
     _oop_map_set(nullptr),
     _scratch_buffer_blob(nullptr),
     _scratch_locs_memory(nullptr),
     _scratch_const_size(-1),
     _in_scratch_emit_size(false),
@@ -1311,11 +1245,10 @@
 
   int pad_req   = NativeCall::instruction_size;
 
   BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2();
   stub_req += bs->estimate_stub_size();
-  stub_req += safepoint_poll_table()->estimate_stub_size();
 
   // nmethod and CodeBuffer count stubs & constants as part of method's code.
   // class HandlerImpl is platform-specific and defined in the *.ad files.
   int exception_handler_req = HandlerImpl::size_exception_handler() + MAX_stubs_size; // add marginal slop for handler
   int deopt_handler_req     = HandlerImpl::size_deopt_handler()     + MAX_stubs_size; // add marginal slop for handler
@@ -1818,12 +1751,12 @@
 
   BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2();
   bs->emit_stubs(*cb);
   if (C->failing())  return;
 
-  // Fill in stubs for calling the runtime from safepoint polls.
-  safepoint_poll_table()->emit(*cb);
+  // Fill in stubs.
+  _stub_list.emit(*cb);
   if (C->failing())  return;
 
 #ifndef PRODUCT
   // Information on the size of the method, without the extraneous code
   Scheduling::increment_method_size(cb->insts_size());
diff a/src/hotspot/share/opto/output.hpp b/src/hotspot/share/opto/output.hpp
--- a/src/hotspot/share/opto/output.hpp
+++ b/src/hotspot/share/opto/output.hpp
@@ -27,10 +27,11 @@
 
 #include "code/debugInfo.hpp"
 #include "code/exceptionHandlerTable.hpp"
 #include "metaprogramming/enableIf.hpp"
 #include "opto/ad.hpp"
+#include "opto/c2_CodeStubs.hpp"
 #include "opto/constantTable.hpp"
 #include "opto/phase.hpp"
 #include "runtime/vm_version.hpp"
 #include "utilities/globalDefinitions.hpp"
 #include "utilities/macros.hpp"
@@ -70,60 +71,19 @@
     _const(0),
     _reloc(0)
   { };
 };
 
-class C2SafepointPollStubTable {
-private:
-  struct C2SafepointPollStub: public ResourceObj {
-    uintptr_t _safepoint_offset;
-    Label     _stub_label;
-    Label     _trampoline_label;
-    C2SafepointPollStub(uintptr_t safepoint_offset) :
-      _safepoint_offset(safepoint_offset),
-      _stub_label(),
-      _trampoline_label() {}
-  };
-
-  GrowableArray<C2SafepointPollStub*> _safepoints;
-
-  static volatile int _stub_size;
-
-  void emit_stub_impl(MacroAssembler& masm, C2SafepointPollStub* entry) const;
-
-  // The selection logic below relieves the need to add dummy files to unsupported platforms.
-  template <bool enabled>
-  typename EnableIf<enabled>::type
-  select_emit_stub(MacroAssembler& masm, C2SafepointPollStub* entry) const {
-    emit_stub_impl(masm, entry);
-  }
-
-  template <bool enabled>
-  typename EnableIf<!enabled>::type
-  select_emit_stub(MacroAssembler& masm, C2SafepointPollStub* entry) const {}
-
-  void emit_stub(MacroAssembler& masm, C2SafepointPollStub* entry) const {
-    select_emit_stub<VM_Version::supports_stack_watermark_barrier()>(masm, entry);
-  }
-
-  int stub_size_lazy() const;
-
-public:
-  Label& add_safepoint(uintptr_t safepoint_offset);
-  int estimate_stub_size() const;
-  void emit(CodeBuffer& cb);
-};
-
 class PhaseOutput : public Phase {
 private:
   // Instruction bits passed off to the VM
   int                    _method_size;           // Size of nmethod code segment in bytes
   CodeBuffer             _code_buffer;           // Where the code is assembled
   int                    _first_block_size;      // Size of unvalidated entry point code / OSR poison code
   ExceptionHandlerTable  _handler_table;         // Table of native-code exception handlers
   ImplicitExceptionTable _inc_table;             // Table of implicit null checks in native code
-  C2SafepointPollStubTable _safepoint_poll_table;// Table for safepoint polls
+  C2CodeStubList         _stub_list;             // List of code stubs
   OopMapSet*             _oop_map_set;           // Table of oop maps (one for each safepoint location)
   BufferBlob*            _scratch_buffer_blob;   // For temporary code buffers.
   relocInfo*             _scratch_locs_memory;   // For temporary code buffers.
   int                    _scratch_const_size;    // For temporary code buffers.
   bool                   _in_scratch_emit_size;  // true when in scratch_emit_size.
@@ -167,12 +127,12 @@
   void install_stub(const char* stub_name);
 
   // Constant table
   ConstantTable& constant_table() { return _constant_table; }
 
-  // Safepoint poll table
-  C2SafepointPollStubTable* safepoint_poll_table() { return &_safepoint_poll_table; }
+  // Code stubs list
+  void add_stub(C2CodeStub* stub) { _stub_list.add_stub(stub); }
 
   // Code emission iterator
   Block* block()   { return _block; }
   int index()      { return _index; }
 
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
@@ -302,18 +302,21 @@
   if ((len > 0) && (result != nullptr) &&
       is_deoptimized_caller_frame(current)) {
     // Zero array here if the caller is deoptimized.
     int 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
@@ -4559,11 +4559,12 @@
   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();
diff a/src/hotspot/share/prims/jvmtiEnvBase.cpp b/src/hotspot/share/prims/jvmtiEnvBase.cpp
--- a/src/hotspot/share/prims/jvmtiEnvBase.cpp
+++ b/src/hotspot/share/prims/jvmtiEnvBase.cpp
@@ -968,59 +968,28 @@
       NULL, 0, 0, NULL, 0, NULL
   };
 
   uint32_t debug_bits = 0;
   // first derive the object's owner and entry_count (if any)
-  {
-    // Revoke any biases before querying the mark word
-    BiasedLocking::revoke_at_safepoint(hobj);
-
-    address owner = NULL;
-    {
-      markWord mark = hobj()->mark();
-
-      if (!mark.has_monitor()) {
-        // this object has a lightweight monitor
+  owning_thread = ObjectSynchronizer::get_lock_owner(tlh.list(), hobj);
+  if (owning_thread != NULL) {  // monitor is owned
+    Handle th(current_thread, owning_thread->threadObj());
+    ret.owner = (jthread)jni_reference(calling_thread, th);
 
-        if (mark.has_locker()) {
-          owner = (address)mark.locker(); // save the address of the Lock word
-        }
-        // implied else: no owner
-      } else {
-        // this object has a heavyweight monitor
-        mon = mark.monitor();
-
-        // The owner field of a heavyweight monitor may be NULL for no
-        // owner, a JavaThread * or it may still be the address of the
-        // Lock word in a JavaThread's stack. A monitor can be inflated
-        // by a non-owning JavaThread, but only the owning JavaThread
-        // can change the owner field from the Lock word to the
-        // JavaThread * and it may not have done that yet.
-        owner = (address)mon->owner();
-      }
-    }
-
-    if (owner != NULL) {
-      // This monitor is owned so we have to find the owning JavaThread.
-      owning_thread = Threads::owning_thread_from_monitor_owner(tlh.list(), owner);
-      assert(owning_thread != NULL, "owning JavaThread must not be NULL");
-      Handle     th(current_thread, owning_thread->threadObj());
-      ret.owner = (jthread)jni_reference(calling_thread, th);
-    }
-
-    if (owning_thread != NULL) {  // monitor is owned
-      // The recursions field of a monitor does not reflect recursions
-      // as lightweight locks before inflating the monitor are not included.
-      // We have to count the number of recursive monitor entries the hard way.
-      // We pass a handle to survive any GCs along the way.
-      ret.entry_count = count_locked_objects(owning_thread, hobj);
-    }
-    // implied else: entry_count == 0
+    // The recursions field of a monitor does not reflect recursions
+    // as lightweight locks before inflating the monitor are not included.
+    // We have to count the number of recursive monitor entries the hard way.
+    // We pass a handle to survive any GCs along the way.
+    ret.entry_count = count_locked_objects(owning_thread, hobj);
   }
+  // implied else: entry_count == 0
 
   jint nWant = 0, nWait = 0;
-  if (mon != NULL) {
+  markWord mark = hobj->mark();
+  if (mark.has_monitor()) {
+    mon = mark.monitor();
+    assert(mon != NULL, "must have monitor");
     // this object has a heavyweight monitor
     nWant = mon->contentions(); // # of threads contending for monitor
     nWait = mon->waiters();     // # of threads in Object.wait()
     ret.waiter_count = nWant + nWait;
     ret.notify_waiter_count = nWait;
diff a/src/hotspot/share/prims/jvmtiTagMap.cpp b/src/hotspot/share/prims/jvmtiTagMap.cpp
--- a/src/hotspot/share/prims/jvmtiTagMap.cpp
+++ b/src/hotspot/share/prims/jvmtiTagMap.cpp
@@ -64,12 +64,15 @@
 #include "runtime/thread.inline.hpp"
 #include "runtime/threadSMR.hpp"
 #include "runtime/vframe.hpp"
 #include "runtime/vmThread.hpp"
 #include "runtime/vmOperations.hpp"
+#include "utilities/objectBitSet.inline.hpp"
 #include "utilities/macros.hpp"
 
+typedef ObjectBitSet<mtServiceability> JVMTIBitSet;
+
 bool JvmtiTagMap::_has_object_free_events = false;
 
 // create a JvmtiTagMap
 JvmtiTagMap::JvmtiTagMap(JvmtiEnv* env) :
   _env(env),
@@ -1351,146 +1354,10 @@
     entry_iterate(&collector);
   }
   return collector.result(count_ptr, object_result_ptr, tag_result_ptr);
 }
 
-
-// ObjectMarker is used to support the marking objects when walking the
-// heap.
-//
-// This implementation uses the existing mark bits in an object for
-// marking. Objects that are marked must later have their headers restored.
-// As most objects are unlocked and don't have their identity hash computed
-// we don't have to save their headers. Instead we save the headers that
-// are "interesting". Later when the headers are restored this implementation
-// restores all headers to their initial value and then restores the few
-// objects that had interesting headers.
-//
-// Future work: This implementation currently uses growable arrays to save
-// the oop and header of interesting objects. As an optimization we could
-// use the same technique as the GC and make use of the unused area
-// between top() and end().
-//
-
-// An ObjectClosure used to restore the mark bits of an object
-class RestoreMarksClosure : public ObjectClosure {
- public:
-  void do_object(oop o) {
-    if (o != NULL) {
-      markWord mark = o->mark();
-      if (mark.is_marked()) {
-        o->init_mark();
-      }
-    }
-  }
-};
-
-// ObjectMarker provides the mark and visited functions
-class ObjectMarker : AllStatic {
- private:
-  // saved headers
-  static GrowableArray<oop>* _saved_oop_stack;
-  static GrowableArray<markWord>* _saved_mark_stack;
-  static bool _needs_reset;                  // do we need to reset mark bits?
-
- public:
-  static void init();                       // initialize
-  static void done();                       // clean-up
-
-  static inline void mark(oop o);           // mark an object
-  static inline bool visited(oop o);        // check if object has been visited
-
-  static inline bool needs_reset()            { return _needs_reset; }
-  static inline void set_needs_reset(bool v)  { _needs_reset = v; }
-};
-
-GrowableArray<oop>* ObjectMarker::_saved_oop_stack = NULL;
-GrowableArray<markWord>* ObjectMarker::_saved_mark_stack = NULL;
-bool ObjectMarker::_needs_reset = true;  // need to reset mark bits by default
-
-// initialize ObjectMarker - prepares for object marking
-void ObjectMarker::init() {
-  assert(Thread::current()->is_VM_thread(), "must be VMThread");
-  assert(SafepointSynchronize::is_at_safepoint(), "must be at a safepoint");
-
-  // prepare heap for iteration
-  Universe::heap()->ensure_parsability(false);  // no need to retire TLABs
-
-  // create stacks for interesting headers
-  _saved_mark_stack = new (ResourceObj::C_HEAP, mtServiceability) GrowableArray<markWord>(4000, mtServiceability);
-  _saved_oop_stack = new (ResourceObj::C_HEAP, mtServiceability) GrowableArray<oop>(4000, mtServiceability);
-
-  if (UseBiasedLocking) {
-    BiasedLocking::preserve_marks();
-  }
-}
-
-// Object marking is done so restore object headers
-void ObjectMarker::done() {
-  // iterate over all objects and restore the mark bits to
-  // their initial value
-  RestoreMarksClosure blk;
-  if (needs_reset()) {
-    Universe::heap()->object_iterate(&blk);
-  } else {
-    // We don't need to reset mark bits on this call, but reset the
-    // flag to the default for the next call.
-    set_needs_reset(true);
-  }
-
-  // now restore the interesting headers
-  for (int i = 0; i < _saved_oop_stack->length(); i++) {
-    oop o = _saved_oop_stack->at(i);
-    markWord mark = _saved_mark_stack->at(i);
-    o->set_mark(mark);
-  }
-
-  if (UseBiasedLocking) {
-    BiasedLocking::restore_marks();
-  }
-
-  // free the stacks
-  delete _saved_oop_stack;
-  delete _saved_mark_stack;
-}
-
-// mark an object
-inline void ObjectMarker::mark(oop o) {
-  assert(Universe::heap()->is_in(o), "sanity check");
-  assert(!o->mark().is_marked(), "should only mark an object once");
-
-  // object's mark word
-  markWord mark = o->mark();
-
-  if (o->mark_must_be_preserved(mark)) {
-    _saved_mark_stack->push(mark);
-    _saved_oop_stack->push(o);
-  }
-
-  // mark the object
-  o->set_mark(markWord::prototype().set_marked());
-}
-
-// return true if object is marked
-inline bool ObjectMarker::visited(oop o) {
-  return o->mark().is_marked();
-}
-
-// Stack allocated class to help ensure that ObjectMarker is used
-// correctly. Constructor initializes ObjectMarker, destructor calls
-// ObjectMarker's done() function to restore object headers.
-class ObjectMarkerController : public StackObj {
- public:
-  ObjectMarkerController() {
-    ObjectMarker::init();
-  }
-  ~ObjectMarkerController() {
-    ObjectMarker::done();
-  }
-};
-
-
 // helper to map a jvmtiHeapReferenceKind to an old style jvmtiHeapRootKind
 // (not performance critical as only used for roots)
 static jvmtiHeapRootKind toJvmtiHeapRootKind(jvmtiHeapReferenceKind kind) {
   switch (kind) {
     case JVMTI_HEAP_REFERENCE_JNI_GLOBAL:   return JVMTI_HEAP_ROOT_JNI_GLOBAL;
@@ -1619,20 +1486,21 @@
 
   // context needed for all heap walks
   static JvmtiTagMap* _tag_map;
   static const void* _user_data;
   static GrowableArray<oop>* _visit_stack;
+  static JVMTIBitSet* _bitset;
 
   // accessors
   static JvmtiTagMap* tag_map()                        { return _tag_map; }
   static const void* user_data()                       { return _user_data; }
   static GrowableArray<oop>* visit_stack()             { return _visit_stack; }
 
   // if the object hasn't been visited then push it onto the visit stack
   // so that it will be visited later
   static inline bool check_for_visit(oop obj) {
-    if (!ObjectMarker::visited(obj)) visit_stack()->push(obj);
+    if (!_bitset->is_marked(obj)) visit_stack()->push(obj);
     return true;
   }
 
   // invoke basic style callbacks
   static inline bool invoke_basic_heap_root_callback
@@ -1659,17 +1527,19 @@
  public:
   // initialize for basic mode
   static void initialize_for_basic_heap_walk(JvmtiTagMap* tag_map,
                                              GrowableArray<oop>* visit_stack,
                                              const void* user_data,
-                                             BasicHeapWalkContext context);
+                                             BasicHeapWalkContext context,
+                                             JVMTIBitSet* bitset);
 
   // initialize for advanced mode
   static void initialize_for_advanced_heap_walk(JvmtiTagMap* tag_map,
                                                 GrowableArray<oop>* visit_stack,
                                                 const void* user_data,
-                                                AdvancedHeapWalkContext context);
+                                                AdvancedHeapWalkContext context,
+                                                JVMTIBitSet* bitset);
 
    // functions to report roots
   static inline bool report_simple_root(jvmtiHeapReferenceKind kind, oop o);
   static inline bool report_jni_local_root(jlong thread_tag, jlong tid, jint depth,
     jmethodID m, oop o);
@@ -1698,35 +1568,40 @@
 BasicHeapWalkContext CallbackInvoker::_basic_context;
 AdvancedHeapWalkContext CallbackInvoker::_advanced_context;
 JvmtiTagMap* CallbackInvoker::_tag_map;
 const void* CallbackInvoker::_user_data;
 GrowableArray<oop>* CallbackInvoker::_visit_stack;
+JVMTIBitSet* CallbackInvoker::_bitset;
 
 // initialize for basic heap walk (IterateOverReachableObjects et al)
 void CallbackInvoker::initialize_for_basic_heap_walk(JvmtiTagMap* tag_map,
                                                      GrowableArray<oop>* visit_stack,
                                                      const void* user_data,
-                                                     BasicHeapWalkContext context) {
+                                                     BasicHeapWalkContext context,
+                                                     JVMTIBitSet* bitset) {
   _tag_map = tag_map;
   _visit_stack = visit_stack;
   _user_data = user_data;
   _basic_context = context;
   _advanced_context.invalidate();       // will trigger assertion if used
   _heap_walk_type = basic;
+  _bitset = bitset;
 }
 
 // initialize for advanced heap walk (FollowReferences)
 void CallbackInvoker::initialize_for_advanced_heap_walk(JvmtiTagMap* tag_map,
                                                         GrowableArray<oop>* visit_stack,
                                                         const void* user_data,
-                                                        AdvancedHeapWalkContext context) {
+                                                        AdvancedHeapWalkContext context,
+                                                        JVMTIBitSet* bitset) {
   _tag_map = tag_map;
   _visit_stack = visit_stack;
   _user_data = user_data;
   _advanced_context = context;
   _basic_context.invalidate();      // will trigger assertion if used
   _heap_walk_type = advanced;
+  _bitset = bitset;
 }
 
 
 // invoke basic style heap root callback
 inline bool CallbackInvoker::invoke_basic_heap_root_callback(jvmtiHeapRootKind root_kind, oop obj) {
@@ -2394,10 +2269,12 @@
   bool _is_advanced_heap_walk;                      // indicates FollowReferences
   JvmtiTagMap* _tag_map;
   Handle _initial_object;
   GrowableArray<oop>* _visit_stack;                 // the visit stack
 
+  JVMTIBitSet _bitset;
+
   // Dead object tags in JvmtiTagMap
   GrowableArray<jlong>* _dead_objects;
 
   bool _following_object_refs;                      // are we following object references
 
@@ -2470,11 +2347,11 @@
   _reporting_string_values = false;
   _visit_stack = create_visit_stack();
   _dead_objects = objects;
 
 
-  CallbackInvoker::initialize_for_basic_heap_walk(tag_map, _visit_stack, user_data, callbacks);
+  CallbackInvoker::initialize_for_basic_heap_walk(tag_map, _visit_stack, user_data, callbacks, &_bitset);
 }
 
 VM_HeapWalkOperation::VM_HeapWalkOperation(JvmtiTagMap* tag_map,
                                            Handle initial_object,
                                            AdvancedHeapWalkContext callbacks,
@@ -2488,11 +2365,11 @@
   _reporting_primitive_array_values = (callbacks.array_primitive_value_callback() != NULL);;
   _reporting_string_values = (callbacks.string_primitive_value_callback() != NULL);;
   _visit_stack = create_visit_stack();
   _dead_objects = objects;
 
-  CallbackInvoker::initialize_for_advanced_heap_walk(tag_map, _visit_stack, user_data, callbacks);
+  CallbackInvoker::initialize_for_advanced_heap_walk(tag_map, _visit_stack, user_data, callbacks, &_bitset);
 }
 
 VM_HeapWalkOperation::~VM_HeapWalkOperation() {
   if (_following_object_refs) {
     assert(_visit_stack != NULL, "checking");
@@ -2924,12 +2801,12 @@
 // second get all the outbound references from this object (in other words, all
 // the objects referenced by this object).
 //
 bool VM_HeapWalkOperation::visit(oop o) {
   // mark object as visited
-  assert(!ObjectMarker::visited(o), "can't visit same object more than once");
-  ObjectMarker::mark(o);
+  assert(!_bitset.is_marked(o), "can't visit same object more than once");
+  _bitset.mark_obj(o);
 
   // instance
   if (o->is_instance()) {
     if (o->klass() == vmClasses::Class_klass()) {
       if (!java_lang_Class::is_primitive(o)) {
@@ -2954,33 +2831,23 @@
   return true;
 }
 
 void VM_HeapWalkOperation::doit() {
   ResourceMark rm;
-  ObjectMarkerController marker;
   ClassFieldMapCacheMark cm;
 
   JvmtiTagMap::check_hashmaps_for_heapwalk(_dead_objects);
 
   assert(visit_stack()->is_empty(), "visit stack must be empty");
 
   // the heap walk starts with an initial object or the heap roots
   if (initial_object().is_null()) {
-    // If either collect_stack_roots() or collect_simple_roots()
-    // returns false at this point, then there are no mark bits
-    // to reset.
-    ObjectMarker::set_needs_reset(false);
-
-    // Calling collect_stack_roots() before collect_simple_roots()
     // can result in a big performance boost for an agent that is
     // focused on analyzing references in the thread stacks.
     if (!collect_stack_roots()) return;
 
     if (!collect_simple_roots()) return;
-
-    // no early return so enable heap traversal to reset the mark bits
-    ObjectMarker::set_needs_reset(true);
   } else {
     visit_stack()->push(initial_object()());
   }
 
   // object references required
@@ -2988,11 +2855,11 @@
 
     // visit each object until all reachable objects have been
     // visited or the callback asked to terminate the iteration.
     while (!visit_stack()->is_empty()) {
       oop o = visit_stack()->pop();
-      if (!ObjectMarker::visited(o)) {
+      if (!_bitset.is_marked(o)) {
         if (!visit(o)) {
           break;
         }
       }
     }
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
@@ -64,11 +64,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
@@ -76,10 +76,11 @@
 #include "runtime/handles.inline.hpp"
 #include "runtime/handshake.hpp"
 #include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/javaCalls.hpp"
 #include "runtime/jniHandles.inline.hpp"
+#include "runtime/lockStack.hpp"
 #include "runtime/os.hpp"
 #include "runtime/stackFrameStream.inline.hpp"
 #include "runtime/sweeper.hpp"
 #include "runtime/synchronizer.hpp"
 #include "runtime/thread.hpp"
@@ -1752,10 +1753,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();
 WB_END
 
@@ -2547,10 +2556,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"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},
   {CC"remapInstructionOperandFromCPCache0",
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.
@@ -188,9 +188,12 @@
   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; }
+
   static bool print_matching_lines_from_file(const char* filename, outputStream* st, const char* keywords_to_match[]);
 };
 
 #endif // SHARE_RUNTIME_ABSTRACT_VM_VERSION_HPP
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
@@ -2001,10 +2001,26 @@
     FLAG_SET_CMDLINE(StackReservedPages, 0);
     warning("Reserved Stack Area not supported on this platform");
   }
 #endif
 
+#if !defined(X86) && !defined(AARCH64)
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    FLAG_SET_CMDLINE(LockingMode, LM_LEGACY);
+    warning("New lightweight locking not supported on this platform");
+  }
+#endif
+
+  if (UseHeavyMonitors) {
+    if (FLAG_IS_CMDLINE(LockingMode) && LockingMode != LM_MONITOR) {
+      jio_fprintf(defaultStream::error_stream(),
+                  "Conflicting -XX:+UseHeavyMonitors and -XX:LockingMode=%d flags", LockingMode);
+      return false;
+    }
+    FLAG_SET_CMDLINE(LockingMode, LM_MONITOR);
+  }
+
   return status;
 }
 
 bool Arguments::is_bad_option(const JavaVMOption* option, jboolean ignore,
   const char* option_type) {
@@ -3151,10 +3167,34 @@
 
 #ifndef CAN_SHOW_REGISTERS_ON_ASSERT
   UNSUPPORTED_OPTION(ShowRegistersOnAssert);
 #endif // CAN_SHOW_REGISTERS_ON_ASSERT
 
+#ifdef _LP64
+  if (UseCompactObjectHeaders && UseZGC) {
+    if (FLAG_IS_CMDLINE(UseCompactObjectHeaders)) {
+      warning("ZGC does not work with compact object headers, disabling UseCompactObjectHeaders");
+    }
+    FLAG_SET_DEFAULT(UseCompactObjectHeaders, false);
+  }
+
+  if (UseCompactObjectHeaders && FLAG_IS_CMDLINE(UseCompressedClassPointers) && !UseCompressedClassPointers) {
+    // If user specifies -UseCompressedClassPointers, disable compact headers with a warning.
+    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 && UseBiasedLocking) {
+    FLAG_SET_DEFAULT(UseBiasedLocking, false);
+  }
+  if (UseCompactObjectHeaders && !UseAltGCForwarding) {
+    FLAG_SET_DEFAULT(UseAltGCForwarding, true);
+  }
+#endif
+
   return JNI_OK;
 }
 
 // Helper class for controlling the lifetime of JavaVMInitArgs
 // objects.  The contents of the JavaVMInitArgs are guaranteed to be
@@ -4027,13 +4067,10 @@
   // biased locking
   if (UseHeavyMonitors
 #ifdef COMPILER1
       || !UseFastLocking
 #endif // COMPILER1
-#if INCLUDE_JVMCI
-      || !JVMCIUseFastLocking
-#endif
     ) {
     if (!FLAG_IS_DEFAULT(UseBiasedLocking) && UseBiasedLocking) {
       // flag set to true on command line; warn the user that they
       // can't enable biased locking here
       warning("Biased Locking is not supported with locking debug flags"
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
@@ -72,10 +72,11 @@
 #include "runtime/sharedRuntime.hpp"
 #include "runtime/signature.hpp"
 #include "runtime/stackFrameStream.inline.hpp"
 #include "runtime/stackWatermarkSet.hpp"
 #include "runtime/stubRoutines.hpp"
+#include "runtime/synchronizer.hpp"
 #include "runtime/thread.hpp"
 #include "runtime/threadSMR.hpp"
 #include "runtime/threadWXSetters.inline.hpp"
 #include "runtime/vframe.hpp"
 #include "runtime/vframeArray.hpp"
@@ -1499,11 +1500,11 @@
                  mark.biased_locker() == deoptee_thread, "should be locked to current thread");
           // Reset mark word to unbiased prototype.
           markWord unbiased_prototype = markWord::prototype().set_age(mark.age());
           obj->set_mark(unbiased_prototype);
         } else if (exec_mode == Unpack_none) {
-          if (mark.has_locker() && fr.sp() > (intptr_t*)mark.locker()) {
+          if (LockingMode == LM_LEGACY && mark.has_locker() && fr.sp() > (intptr_t*)mark.locker()) {
             // With exec_mode == Unpack_none obj may be thread local and locked in
             // a callee frame. In this case the bias was revoked before in revoke_for_object_deoptimization().
             // Make the lock in the callee a recursive lock and restore the displaced header.
             markWord dmw = mark.displaced_mark_helper();
             mark.locker()->set_displaced_header(markWord::encode((BasicLock*) NULL));
@@ -1518,13 +1519,23 @@
               JvmtiDeferredUpdates::inc_relock_count_after_wait(deoptee_thread);
               continue;
             }
           }
         }
-        BasicLock* lock = mon_info->lock();
-        ObjectSynchronizer::enter(obj, lock, deoptee_thread);
-        assert(mon_info->owner()->is_locked(), "object must be locked now");
+        if (LockingMode == LM_LIGHTWEIGHT && exec_mode == Unpack_none) {
+          // We have lost information about the correct state of the lock stack.
+          // Inflate the locks instead. Enter then inflate to avoid races with
+          // deflation.
+          ObjectSynchronizer::enter_for(obj, nullptr, deoptee_thread);
+          assert(mon_info->owner()->is_locked(), "object must be locked now");
+          ObjectMonitor* mon = ObjectSynchronizer::inflate_for(deoptee_thread, obj(), ObjectSynchronizer::inflate_cause_vm_internal);
+          assert(mon->owner() == deoptee_thread, "must be");
+        } else {
+          BasicLock* lock = mon_info->lock();
+          ObjectSynchronizer::enter_for(obj, lock, deoptee_thread);
+          assert(mon_info->owner()->is_locked(), "object must be locked now");
+        }
       }
     }
   }
   return relocked_objects;
 }
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, false,                          \
           "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 64-bit object headers instead of 96-bit headers")            \
+                                                                            \
   product(intx, 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 intx ObjectAlignmentInBytes = 8;
 
 #endif // _LP64
 
 #define RUNTIME_FLAGS(develop,                                              \
@@ -2092,10 +2096,23 @@
   develop(bool, VerifyCrossModifyFence,                                     \
           false AARCH64_ONLY(DEBUG_ONLY(||true)),                           \
              "Mark all threads after a safepoint, and clear on a modify "   \
              "fence. Add cleanliness checks.")                              \
                                                                             \
+  product(bool, HeapObjectStats, false, DIAGNOSTIC,                         \
+             "Enable gathering of heap object statistics")                  \
+                                                                            \
+  product(size_t, HeapObjectStatsSamplingInterval, 500, DIAGNOSTIC,         \
+             "Heap object statistics sampling interval (ms)")               \
+                                                                            \
+  product(int, LockingMode, LM_LEGACY, EXPERIMENTAL,                        \
+          "Select locking mode: "                                           \
+          "0: monitors only (LM_MONITOR), "                                 \
+          "1: monitors & legacy stack-locking (LM_LEGACY, default), "       \
+          "2: monitors & new lightweight locking (LM_LIGHTWEIGHT)")         \
+          range(0, 2)                                                       \
+                                                                            \
   develop(bool, TraceOptimizedUpcallStubs, false,                              \
                 "Trace optimized upcall stub generation")                      \
                                                                             \
   product(uint, TrimNativeHeapInterval, 0, EXPERIMENTAL,                    \
           "Interval, in ms, at which the JVM will trim the native heap if " \
diff a/src/hotspot/share/runtime/lockStack.cpp b/src/hotspot/share/runtime/lockStack.cpp
--- /dev/null
+++ b/src/hotspot/share/runtime/lockStack.cpp
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+#include "memory/allocation.hpp"
+#include "runtime/globals.hpp"
+#include "runtime/lockStack.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/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] = NULL;
+  }
+#endif
+}
+
+uint32_t LockStack::start_offset() {
+  int offset = lock_stack_base_offset;
+  assert(offset > 0, "must be positive offset");
+  return static_cast<uint32_t>(offset);
+}
+
+uint32_t LockStack::end_offset() {
+  int offset = lock_stack_base_offset + CAPACITY * oopSize;
+  assert(offset > 0, "must be positive offset");
+  return static_cast<uint32_t>(offset);
+}
+
+#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 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] != NULL, "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++) {
+      assert(_base[i] == NULL, "only zapped entries after top: i: %d, top: %d, entry: " PTR_FORMAT, i, top, p2i(_base[i]));
+    }
+  }
+}
+#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)) {
+      o->print_on(st);
+    } else {
+      st->print_cr("not an oop: " PTR_FORMAT, p2i(o));
+    }
+  }
+}
diff a/src/hotspot/share/runtime/lockStack.hpp b/src/hotspot/share/runtime/lockStack.hpp
--- /dev/null
+++ b/src/hotspot/share/runtime/lockStack.hpp
@@ -0,0 +1,124 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef SHARE_RUNTIME_LOCKSTACK_HPP
+#define SHARE_RUNTIME_LOCKSTACK_HPP
+
+#include "oops/oopsHierarchy.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "utilities/sizes.hpp"
+
+class JavaThread;
+class OopClosure;
+class outputStream;
+
+class LockStack {
+  friend class LockStackTest;
+  friend class VMStructs;
+  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;
+  static const int lock_stack_top_offset;
+  static const int lock_stack_base_offset;
+
+  // 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;
+
+  // Tests if the calling thread is the thread that owns this lock-stack.
+  bool is_owning_thread() const;
+
+  // Verifies consistency of the lock-stack.
+  void verify(const char* msg) const PRODUCT_RETURN;
+
+  // Given an offset (in bytes) calculate the index into the lock-stack.
+  static inline int to_index(uint32_t offset);
+
+public:
+  static ByteSize top_offset()  { return byte_offset_of(LockStack, _top); }
+  static ByteSize base_offset() { return byte_offset_of(LockStack, _base); }
+
+  LockStack(JavaThread* jt);
+
+  // The boundary indicies of the lock-stack.
+  static uint32_t start_offset();
+  static uint32_t end_offset();
+
+  // 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);
+
+  // 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.
+  // 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);
+};
+
+#endif // SHARE_RUNTIME_LOCKSTACK_HPP
diff a/src/hotspot/share/runtime/lockStack.inline.hpp b/src/hotspot/share/runtime/lockStack.inline.hpp
--- /dev/null
+++ b/src/hotspot/share/runtime/lockStack.inline.hpp
@@ -0,0 +1,226 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef SHARE_RUNTIME_LOCKSTACK_INLINE_HPP
+#define SHARE_RUNTIME_LOCKSTACK_INLINE_HPP
+
+#include "runtime/lockStack.hpp"
+
+#include "memory/iterator.hpp"
+#include "runtime/safepoint.hpp"
+#include "runtime/stackWatermark.hpp"
+#include "runtime/stackWatermarkSet.inline.hpp"
+#include "runtime/thread.hpp"
+#include "runtime/vm_version.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::is_full() const {
+  return to_index(_top) == CAPACITY;
+}
+
+inline bool LockStack::is_owning_thread() const {
+  Thread* current = Thread::current();
+  if (current->is_Java_thread()) {
+    JavaThread* thread = current->as_Java_thread();
+    bool is_owning = &thread->lock_stack() == this;
+    assert(is_owning == (get_thread() == thread), "is_owning sanity");
+    return is_owning;
+  }
+  return false;
+}
+
+inline void LockStack::push(oop o) {
+  verify("pre-push");
+  assert(oopDesc::is_oop(o), "must be");
+  assert(!contains(o), "entries must be unique");
+  assert(!is_full(), "must have room");
+  assert(_base[to_index(_top)] == NULL, "expect zapped entry");
+  _base[to_index(_top)] = o;
+  _top += oopSize;
+  verify("post-push");
+}
+
+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;
+  DEBUG_ONLY(_base[to_index(_top)] = nullptr;)
+  verify("post-try_recursive_exit");
+  return true;
+}
+
+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) {
+      if (inserted != i) {
+        _base[inserted] = _base[i];
+      }
+      inserted++;
+    }
+  }
+
+#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");
+
+  // Can't poke around in thread oops without having started stack watermark processing.
+  assert(StackWatermarkSet::processing_started(get_thread()), "Processing must have started!");
+
+  int end = to_index(_top);
+  for (int i = end - 1; i >= 0; i--) {
+    if (_base[i] == o) {
+      verify("post-contains");
+      return true;
+    }
+  }
+  verify("post-contains");
+  return false;
+}
+
+inline void LockStack::oops_do(OopClosure* cl) {
+  verify("pre-oops-do");
+  int end = to_index(_top);
+  for (int i = 0; i < end; i++) {
+    cl->do_oop(&_base[i]);
+  }
+  verify("post-oops-do");
+}
+
+#endif // SHARE_RUNTIME_LOCKSTACK_INLINE_HPP
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, 2022, 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/mutexLocker.hpp"
 #include "runtime/objectMonitor.hpp"
 #include "runtime/objectMonitor.inline.hpp"
@@ -51,10 +52,11 @@
 #include "runtime/safepointMechanism.inline.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "runtime/thread.inline.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
@@ -316,11 +318,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(NULL, current);
   if (cur == NULL) {
@@ -332,11 +397,11 @@
     // TODO-FIXME: check for integer overflow!  BUGID 6557169.
     _recursions++;
     return true;
   }
 
-  if (current->is_lock_owned((address)cur)) {
+  if (LockingMode != LM_LIGHTWEIGHT && current->is_lock_owned((address)cur)) {
     assert(_recursions == 0, "internal state error");
     _recursions = 1;
     set_owner_from_BasicLock(cur, current);  // Convert from BasicLock* to Thread*.
     return true;
   }
@@ -598,10 +663,26 @@
   // We leave owner == DEFLATER_MARKER and contentions < 0
   // to force any racing threads to retry.
   return true;  // Success, ObjectMonitor has been deflated.
 }
 
+// We might access the dead object headers for parsable heap walk, make sure
+// headers are in correct shape, e.g. monitors deflated.
+void ObjectMonitor::maybe_deflate_dead(oop* p) {
+  oop obj = *p;
+  assert(obj != NULL, "must not yet been cleared");
+  markWord mark = obj->mark();
+  if (mark.has_monitor()) {
+    ObjectMonitor* monitor = mark.monitor();
+    if (p == monitor->_object.ptr_raw()) {
+      assert(monitor->object_peek() == obj, "lock object must match");
+      markWord dmw = monitor->header();
+      obj->set_mark(dmw);
+    }
+  }
+}
+
 // Install the displaced mark word (dmw) of a deflating ObjectMonitor
 // into the header of the object associated with the monitor. This
 // idempotent method is called by a thread that is deflating a
 // monitor and by other threads that have detected a race with the
 // deflation process.
@@ -1133,11 +1214,11 @@
 // of such futile wakups is low.
 
 void ObjectMonitor::exit(JavaThread* current, bool not_suspended) {
   void* cur = owner_raw();
   if (current != cur) {
-    if (current->is_lock_owned((address)cur)) {
+    if (LockingMode != LM_LIGHTWEIGHT && current->is_lock_owned((address)cur)) {
       assert(_recursions == 0, "invariant");
       set_owner_from_BasicLock(cur, current);  // Convert from BasicLock* to Thread*.
       _recursions = 0;
     } else {
       // Apparent unbalanced locking ...
@@ -1353,11 +1434,11 @@
 intx ObjectMonitor::complete_exit(JavaThread* current) {
   assert(InitDone, "Unexpectedly not initialized");
 
   void* cur = owner_raw();
   if (current != cur) {
-    if (current->is_lock_owned((address)cur)) {
+    if (LockingMode != LM_LIGHTWEIGHT && current->is_lock_owned((address)cur)) {
       assert(_recursions == 0, "internal state error");
       set_owner_from_BasicLock(cur, current);  // Convert from BasicLock* to Thread*.
       _recursions = 0;
     }
   }
@@ -1402,14 +1483,15 @@
 // (IMSE). If there is a pending exception and the specified thread
 // is not the owner, that exception will be replaced by the IMSE.
 bool ObjectMonitor::check_owner(TRAPS) {
   JavaThread* current = THREAD;
   void* cur = owner_raw();
+  assert(cur != anon_owner_ptr(), "no anon owner here");
   if (cur == current) {
     return true;
   }
-  if (current->is_lock_owned((address)cur)) {
+  if (LockingMode != LM_LIGHTWEIGHT && current->is_lock_owned((address)cur)) {
     set_owner_from_BasicLock(cur, current);  // Convert from BasicLock* to Thread*.
     _recursions = 0;
     return true;
   }
   THROW_MSG_(vmSymbols::java_lang_IllegalMonitorStateException(),
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, 2022, 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.
@@ -143,12 +143,26 @@
   // have busy multi-threaded access. _header and _object are set at initial
   // inflation. The _object does not change, so it is a good choice to share
   // its cache line with _header.
   DEFINE_PAD_MINUS_SIZE(0, OM_CACHE_LINE_SIZE, sizeof(volatile markWord) +
                         sizeof(WeakHandle));
-  // Used by async deflation as a marker in the _owner field:
-  #define DEFLATER_MARKER reinterpret_cast<void*>(-1)
+  // Used by async deflation as a marker in the _owner field.
+  // Note that the choice of the two markers is peculiar:
+  // - They need to represent values that cannot be pointers. In particular,
+  //   we achieve this by using the lowest two bits.
+  // - ANONYMOUS_OWNER should be a small value, it is used in generated code
+  //   and small values encode much better.
+  // - We test for anonymous owner by testing for the lowest bit, therefore
+  //   DEFLATER_MARKER must *not* have that bit set.
+  #define DEFLATER_MARKER reinterpret_cast<void*>(2)
+public:
+  // NOTE: Typed as uintptr_t so that we can pick it up in SA, via vmStructs.
+  static const uintptr_t ANONYMOUS_OWNER = 1;
+
+private:
+  static void* anon_owner_ptr() { return reinterpret_cast<void*>(ANONYMOUS_OWNER); }
+
   void* volatile _owner;            // pointer to owning thread OR BasicLock
   volatile jlong _previous_owner_tid;  // thread id of the previous owner of the monitor
   // Separate _owner and _next_om on different cache lines since
   // both can have busy multi-threaded access. _previous_owner_tid is only
   // changed by ObjectMonitor::exit() so it is a good choice to share the
@@ -242,12 +256,13 @@
     }
     return ret_code != 0;
   }
   const char* is_busy_to_string(stringStream* ss);
 
-  intptr_t  is_entered(JavaThread* current) const;
+  bool is_entered(JavaThread* current) const;
 
+  bool      has_owner() const;
   void*     owner() const;  // Returns NULL if DEFLATER_MARKER is observed.
   void*     owner_raw() const;
   // Returns true if owner field == DEFLATER_MARKER and false otherwise.
   bool      owner_is_DEFLATER_MARKER() const;
   // Returns true if 'this' is being async deflated and false otherwise.
@@ -261,10 +276,22 @@
   // Try to set _owner field to new_value if the current value matches
   // old_value, using Atomic::cmpxchg(). Otherwise, does not change the
   // _owner field. Returns the prior value of the _owner field.
   void*     try_set_owner_from(void* old_value, void* new_value);
 
+  void set_owner_anonymous() {
+    set_owner_from(NULL, anon_owner_ptr());
+  }
+
+  bool is_owner_anonymous() const {
+    return owner_raw() == anon_owner_ptr();
+  }
+
+  void set_owner_from_anonymous(Thread* owner) {
+    set_owner_from(anon_owner_ptr(), owner);
+  }
+
   // Simply get _next_om field.
   ObjectMonitor* next_om() const;
   // Get _next_om field with acquire semantics.
   ObjectMonitor* next_om_acquire() const;
   // Simply set _next_om field to new_value.
@@ -279,10 +306,11 @@
   jint      waiters() const;
 
   jint      contentions() const;
   void      add_to_contentions(jint 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; }
@@ -313,10 +341,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);
@@ -329,10 +358,12 @@
 
   // Use the following at your own risk
   intx      complete_exit(JavaThread* current);
   bool      reenter(intx recursions, JavaThread* current);
 
+  static void maybe_deflate_dead(oop* p);
+
  private:
   void      AddWaiter(ObjectWaiter* waiter);
   void      INotify(JavaThread* current);
   ObjectWaiter* DequeueWaiter();
   void      DequeueSpecificWaiter(ObjectWaiter* waiter);
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, 2021, 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.
@@ -28,18 +28,27 @@
 #include "runtime/objectMonitor.hpp"
 
 #include "logging/log.hpp"
 #include "oops/access.inline.hpp"
 #include "runtime/atomic.hpp"
+#include "runtime/lockStack.inline.hpp"
 #include "runtime/synchronizer.hpp"
 
-inline intptr_t ObjectMonitor::is_entered(JavaThread* current) const {
-  void* owner = owner_raw();
-  if (current == owner || current->is_lock_owned((address)owner)) {
-    return 1;
+inline bool ObjectMonitor::is_entered(JavaThread* current) const {
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    if (is_owner_anonymous()) {
+      return current->lock_stack().contains(object());
+    } else {
+      return current == owner_raw();
+    }
+  } else {
+    void* owner = owner_raw();
+    if (current == owner || current->is_lock_owned((address)owner)) {
+      return true;
+    }
   }
-  return 0;
+  return false;
 }
 
 inline markWord ObjectMonitor::header() const {
   return Atomic::load(&_header);
 }
@@ -54,10 +63,15 @@
 
 inline jint ObjectMonitor::waiters() const {
   return _waiters;
 }
 
+inline bool ObjectMonitor::has_owner() const {
+  void* owner = owner_raw();
+  return owner != NULL && owner != DEFLATER_MARKER;
+}
+
 // Returns NULL if DEFLATER_MARKER is observed.
 inline void* ObjectMonitor::owner() const {
   void* owner = owner_raw();
   return owner != DEFLATER_MARKER ? owner : NULL;
 }
@@ -86,10 +100,16 @@
 // Add value to the contentions field.
 inline void ObjectMonitor::add_to_contentions(jint 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
@@ -3239,20 +3239,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() != NULL) {         // 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, 2021, 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.
@@ -23,23 +23,26 @@
  */
 
 #include "precompiled.hpp"
 #include "classfile/vmSymbols.hpp"
 #include "jfr/jfrEvents.hpp"
+#include "gc/shared/suspendibleThreadSet.hpp"
 #include "logging/log.hpp"
 #include "logging/logStream.hpp"
 #include "memory/allocation.inline.hpp"
 #include "memory/padded.hpp"
 #include "memory/resourceArea.hpp"
 #include "memory/universe.hpp"
 #include "oops/markWord.hpp"
 #include "oops/oop.inline.hpp"
 #include "runtime/atomic.hpp"
 #include "runtime/biasedLocking.hpp"
+#include "runtime/globals.hpp"
 #include "runtime/handles.inline.hpp"
 #include "runtime/handshake.hpp"
 #include "runtime/interfaceSupport.inline.hpp"
+#include "runtime/lockStack.inline.hpp"
 #include "runtime/mutexLocker.hpp"
 #include "runtime/objectMonitor.hpp"
 #include "runtime/objectMonitor.inline.hpp"
 #include "runtime/os.inline.hpp"
 #include "runtime/osThread.hpp"
@@ -55,10 +58,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/preserveException.hpp"
 
 void MonitorList::add(ObjectMonitor* m) {
   ObjectMonitor* head;
   do {
@@ -273,14 +277,22 @@
   assert(current->thread_state() == _thread_in_Java, "invariant");
   NoSafepointVerifier nsv;
   if (obj == NULL) return false;  // slow-path for invalid obj
   const markWord mark = obj->mark();
 
-  if (mark.has_locker() && current->is_lock_owned((address)mark.locker())) {
-    // Degenerate notify
-    // stack-locked by caller so by definition the implied waitset is empty.
-    return true;
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    if (mark.is_fast_locked() && current->lock_stack().contains(cast_to_oop(obj))) {
+      // Degenerate notify
+      // fast-locked by caller so by definition the implied waitset is empty.
+      return true;
+    }
+  } else if (LockingMode == LM_LEGACY) {
+    if (mark.has_locker() && current->is_lock_owned((address)mark.locker())) {
+      // Degenerate notify
+      // stack-locked by caller so by definition the implied waitset is empty.
+      return true;
+    }
   }
 
   if (mark.has_monitor()) {
     ObjectMonitor* const mon = mark.monitor();
     assert(mon->object() == oop(obj), "invariant");
@@ -324,10 +336,22 @@
 
   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.
+      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
@@ -346,21 +370,23 @@
     if (owner == current) {
       m->_recursions++;
       return true;
     }
 
-    // This Java Monitor is inflated so obj's header will never be
-    // displaced to this thread's BasicLock. Make the displaced header
-    // non-NULL so this BasicLock is not seen as recursive nor as
-    // being locked. We do this unconditionally so that this thread's
-    // BasicLock cannot be mis-interpreted by any stack walkers. For
-    // performance reasons, stack walkers generally first check for
-    // Biased Locking in the object's header, the second check is for
-    // stack-locking in the object's header, the third check is for
-    // recursive stack-locking in the displaced header in the BasicLock,
-    // and last are the inflated Java Monitor (ObjectMonitor) checks.
-    lock->set_displaced_header(markWord::unused_mark());
+    if (LockingMode != LM_LIGHTWEIGHT) {
+      // This Java Monitor is inflated so obj's header will never be
+      // displaced to this thread's BasicLock. Make the displaced header
+      // non-NULL so this BasicLock is not seen as recursive nor as
+      // being locked. We do this unconditionally so that this thread's
+      // BasicLock cannot be mis-interpreted by any stack walkers. For
+      // performance reasons, stack walkers generally first check for
+      // Biased Locking in the object's header, the second check is for
+      // stack-locking in the object's header, the third check is for
+      // recursive stack-locking in the displaced header in the BasicLock,
+      // and last are the inflated Java Monitor (ObjectMonitor) checks.
+      lock->set_displaced_header(markWord::unused_mark());
+    }
 
     if (owner == NULL && m->try_set_owner_from(NULL, current) == NULL) {
       assert(m->_recursions == 0, "invariant");
       return true;
     }
@@ -375,12 +401,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() &&
@@ -389,28 +416,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_stack_on(&ss);
+    locking_thread->print_stack_on(&ss);
     char* base = (char*)strstr(ss.base(), "at");
     char* newline = (char*)strchr(ss.base(), '\n');
     if (newline != NULL) {
       *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_stack_on(&info_stream);
+      locking_thread->print_stack_on(&info_stream);
     } else {
       vblog.info("Cannot find the last Java frame");
     }
 
     EventSyncOnValueBasedClass event;
@@ -423,109 +450,233 @@
   if (bcp_was_adjusted) {
     last_frame.interpreter_frame_set_bcp(last_frame.interpreter_frame_bcp() + 1);
   }
 }
 
+static bool useHeavyMonitors() {
+#if defined(X86) || defined(AARCH64) || defined(PPC64) || defined(RISCV64)
+  return LockingMode == LM_MONITOR;
+#else
+  return false;
+#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);
+  }
+
+  if (!useHeavyMonitors()) {
+    if (LockingMode == LM_LIGHTWEIGHT) {
+      // Fast-locking does not use the 'lock' argument.
+      LockStack& lock_stack = locking_thread->lock_stack();
+      if (lock_stack.is_full()) {
+        // We unconditionally make room on the lock stack by inflating
+        // the least recently locked object on the lock stack.
+
+        // About the choice to inflate least recently locked object.
+        // First we must chose to inflate a lock, either some lock on
+        // the lock-stack or the lock that is currently being entered
+        // (which may or may not be on the lock-stack).
+        // Second the best lock to inflate is a lock which is entered
+        // in a control flow where there are only a very few locks being
+        // used, as the costly part of inflated locking is inflation,
+        // not locking. But this property is entirely program dependent.
+        // Third inflating the lock currently being entered on when it
+        // is not present on the lock-stack will result in a still full
+        // lock-stack. This creates a scenario where every deeper nested
+        // monitorenter must call into the runtime.
+        // The rational here is as follows:
+        // Because we cannot (currently) figure out the second, and want
+        // to avoid the third, we inflate a lock on the lock-stack.
+        // The least recently locked lock is chosen as it is the lock
+        // with the longest critical section.
+
+        log_info(monitorinflation)("LockStack capacity exceeded, inflating.");
+        ObjectMonitor* monitor = inflate_for(locking_thread, lock_stack.bottom(), inflate_cause_vm_internal);
+        assert(monitor->owner() == Thread::current(), "must be owner=" PTR_FORMAT " current=" PTR_FORMAT " mark=" PTR_FORMAT,
+               p2i(monitor->owner()), p2i(Thread::current()), monitor->object()->mark_acquire().value());
+        assert(!lock_stack.is_full(), "must have made room here");
+      }
 
-  if (UseBiasedLocking) {
-    BiasedLocking::revoke(current, obj);
-  }
+      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;
+      }
 
-  markWord mark = obj->mark();
-  assert(!mark.has_bias_pattern(), "should not see bias pattern here");
+      if (mark.is_fast_locked() && lock_stack.try_recursive_enter(obj())) {
+        // Recursive lock successful.
+        return true;
+      }
 
-  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;
-    }
-    // Fall through to inflate() ...
-  } else if (mark.has_locker() &&
-             current->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(NULL));
-    return;
-  }
+      // Failed to fast lock.
+      return false;
+    } else if (LockingMode == LM_LEGACY) {
+      if (UseBiasedLocking) {
+        BiasedLocking::revoke(locking_thread, obj);
+      }
 
-  // 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());
-  // 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;
+      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 true;
+        }
+      } else if (mark.has_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(NULL));
+        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;
     }
   }
+
+  return false;
 }
 
 void ObjectSynchronizer::exit(oop object, BasicLock* lock, JavaThread* current) {
-  markWord mark = object->mark();
-  // We cannot check for Biased Locking if we are racing an inflation.
-  assert(mark == markWord::INFLATING() ||
-         !mark.has_bias_pattern(), "should not see bias pattern here");
-
-  markWord dhw = lock->displaced_header();
-  if (dhw.value() == 0) {
-    // If the displaced header is NULL, then this exit matches up with
-    // a recursive enter. No real work to do here except for diagnostics.
-#ifndef PRODUCT
-    if (mark != markWord::INFLATING()) {
-      // Only do diagnostics if we are not racing an inflation. Simply
-      // exiting a recursive enter of a Java Monitor that is being
-      // inflated is safe; see the has_monitor() comment below.
-      assert(!mark.is_neutral(), "invariant");
-      assert(!mark.has_locker() ||
-             current->is_lock_owned((address)mark.locker()), "invariant");
-      if (mark.has_monitor()) {
-        // The BasicLock's displaced_header is marked as a recursive
-        // enter and we have an inflated Java Monitor (ObjectMonitor).
-        // This is a special case where the Java Monitor was inflated
-        // after this thread entered the stack-lock recursively. When a
-        // Java Monitor is inflated, we cannot safely walk the Java
-        // Monitor owner's stack and update the BasicLocks because a
-        // Java Monitor can be asynchronously inflated by a thread that
-        // does not own the Java Monitor.
-        ObjectMonitor* m = mark.monitor();
-        assert(m->object()->mark() == mark, "invariant");
-        assert(m->is_entered(current), "invariant");
+  if (!useHeavyMonitors()) {
+    markWord mark = object->mark();
+    if (LockingMode == LM_LIGHTWEIGHT) {
+      // Fast-locking does not use the 'lock' argument.
+      LockStack& lock_stack = current->lock_stack();
+      if (mark.is_fast_locked() && lock_stack.try_recursive_exit(object)) {
+        // Recursively unlocked.
+        return;
       }
-    }
+
+      if (mark.is_fast_locked() && lock_stack.is_recursive(object)) {
+        // This lock is recursive but is not at the top of the lock stack so we're
+        // doing an unbalanced exit. We have to fall thru to inflation below and
+        // let ObjectMonitor::exit() do the unlock.
+      } else {
+        while (mark.is_fast_locked()) {
+          // Retry until a lock state change has been observed. cas_set_mark() may collide with non lock bits modifications.
+          const markWord unlocked_mark = mark.set_unlocked();
+          const markWord old_mark = object->cas_set_mark(unlocked_mark, mark);
+          if (old_mark == mark) {
+            size_t recursions = lock_stack.remove(object) - 1;
+            assert(recursions == 0, "must not be recursive here");
+            return;
+          }
+          mark = old_mark;
+        }
+      }
+    } else if (LockingMode == LM_LEGACY) {
+      markWord dhw = lock->displaced_header();
+      if (dhw.value() == 0) {
+        // If the displaced header is NULL, then this exit matches up with
+        // a recursive enter. No real work to do here except for diagnostics.
+#ifndef PRODUCT
+        if (mark != markWord::INFLATING()) {
+          // Only do diagnostics if we are not racing an inflation. Simply
+          // exiting a recursive enter of a Java Monitor that is being
+          // inflated is safe; see the has_monitor() comment below.
+          assert(!mark.is_neutral(), "invariant");
+          assert(!mark.has_locker() ||
+                 current->is_lock_owned((address)mark.locker()), "invariant");
+          if (mark.has_monitor()) {
+            // The BasicLock's displaced_header is marked as a recursive
+            // enter and we have an inflated Java Monitor (ObjectMonitor).
+            // This is a special case where the Java Monitor was inflated
+            // after this thread entered the stack-lock recursively. When a
+            // Java Monitor is inflated, we cannot safely walk the Java
+            // Monitor owner's stack and update the BasicLocks because a
+            // Java Monitor can be asynchronously inflated by a thread that
+            // does not own the Java Monitor.
+            ObjectMonitor* m = mark.monitor();
+            assert(m->object()->mark() == mark, "invariant");
+            assert(m->is_entered(current), "invariant");
+          }
+        }
 #endif
-    return;
-  }
+        return;
+      }
 
-  if (mark == markWord::from_pointer(lock)) {
-    // If the object is stack-locked by the current thread, try to
-    // swing the displaced header from the BasicLock back to the mark.
-    assert(dhw.is_neutral(), "invariant");
-    if (object->cas_set_mark(dhw, mark) == mark) {
-      return;
+      if (mark == markWord::from_pointer(lock)) {
+        // If the object is stack-locked by the current thread, try to
+        // swing the displaced header from the BasicLock back to the mark.
+        assert(dhw.is_neutral(), "invariant");
+        if (object->cas_set_mark(dhw, mark) == mark) {
+          return;
+        }
+      }
     }
   }
 
   // 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);
+  assert(!monitor->is_owner_anonymous(), "must not be");
   monitor->exit(current);
 }
 
 // -----------------------------------------------------------------------------
 // Class Loader  support to workaround deadlocks on the class loader lock objects
@@ -686,13 +837,20 @@
     BiasedLocking::revoke(current, obj);
     assert(!obj->mark().has_bias_pattern(), "biases should be revoked by now");
   }
 
   markWord mark = obj->mark();
-  if (mark.has_locker() && current->is_lock_owned((address)mark.locker())) {
-    // Not inflated so there can't be any waiters to notify.
-    return;
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    if ((mark.is_fast_locked() && current->lock_stack().contains(obj()))) {
+      // Not inflated so there can't be any waiters to notify.
+      return;
+    }
+  } else if (LockingMode == LM_LEGACY) {
+    if (mark.has_locker() && current->is_lock_owned((address)mark.locker())) {
+      // Not inflated so there can't be any waiters to notify.
+      return;
+    }
   }
   // The ObjectMonitor* can't be async deflated until ownership is
   // dropped by the calling thread.
   ObjectMonitor* monitor = inflate(current, obj(), inflate_cause_notify);
   monitor->notify(CHECK);
@@ -705,13 +863,20 @@
     BiasedLocking::revoke(current, obj);
     assert(!obj->mark().has_bias_pattern(), "biases should be revoked by now");
   }
 
   markWord mark = obj->mark();
-  if (mark.has_locker() && current->is_lock_owned((address)mark.locker())) {
-    // Not inflated so there can't be any waiters to notify.
-    return;
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    if ((mark.is_fast_locked() && current->lock_stack().contains(obj()))) {
+      // Not inflated so there can't be any waiters to notify.
+      return;
+    }
+  } else if (LockingMode == LM_LEGACY) {
+    if (mark.has_locker() && current->is_lock_owned((address)mark.locker())) {
+      // Not inflated so there can't be any waiters to notify.
+      return;
+    }
   }
   // The ObjectMonitor* can't be async deflated until ownership is
   // dropped by the calling thread.
   ObjectMonitor* monitor = inflate(current, obj(), inflate_cause_notify);
   monitor->notifyAll(CHECK);
@@ -733,11 +898,12 @@
 
 static SharedGlobals GVars;
 
 static markWord read_stable_mark(oop obj) {
   markWord mark = obj->mark_acquire();
-  if (!mark.is_being_inflated()) {
+  if (!mark.is_being_inflated() || LockingMode == LM_LIGHTWEIGHT) {
+    // New lightweight locking does not use the markWord::INFLATING() protocol.
     return mark;       // normal fast-path return
   }
 
   int its = 0;
   for (;;) {
@@ -842,11 +1008,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;
 }
 
@@ -878,11 +1044,11 @@
     markWord mark = read_stable_mark(obj);
 
     // object should remain ineligible for biased locking
     assert(!mark.has_bias_pattern(), "invariant");
 
-    if (mark.is_neutral()) {               // if this is a normal header
+    if (mark.is_neutral() || (LockingMode == LM_LIGHTWEIGHT && mark.is_fast_locked())) {
       hash = mark.hash();
       if (hash != 0) {                     // if it has a hash, just return it
         return hash;
       }
       hash = get_next_hash(current, obj);  // get a new hash
@@ -890,10 +1056,14 @@
                                            // try to install the hash
       test = obj->cas_set_mark(temp, mark);
       if (test == mark) {                  // if the hash was installed, return it
         return hash;
       }
+      if (LockingMode == LM_LIGHTWEIGHT) {
+        // CAS failed, retry
+        continue;
+      }
       // Failed to install the hash. It could be that another thread
       // installed the hash just before our attempt or inflation has
       // occurred or... so we fall thru to inflate the monitor for
       // stability and then install the hash.
     } else if (mark.has_monitor()) {
@@ -921,11 +1091,11 @@
         }
         return hash;
       }
       // Fall thru so we only have one place that installs the hash in
       // the ObjectMonitor.
-    } else if (current->is_lock_owned((address)mark.locker())) {
+    } else if (LockingMode == LM_LEGACY && mark.has_locker() && current->is_lock_owned((address)mark.locker())) {
       // This is a stack lock owned by the calling thread so fetch the
       // displaced markWord from the BasicLock on the stack.
       temp = mark.displaced_mark_helper();
       assert(temp.is_neutral(), "invariant: header=" INTPTR_FORMAT, temp.value());
       hash = temp.hash();
@@ -998,14 +1168,20 @@
   assert(current == JavaThread::current(), "Can only be called on current thread");
   oop obj = h_obj();
 
   markWord mark = read_stable_mark(obj);
 
-  // Uncontended case, header points to stack
-  if (mark.has_locker()) {
+  if (LockingMode == LM_LEGACY && mark.has_locker()) {
+    // stack-locked case, header points into owner's stack
     return current->is_lock_owned((address)mark.locker());
   }
+
+  if (LockingMode == LM_LIGHTWEIGHT && mark.is_fast_locked()) {
+    // fast-locking case, see if lock is in current's lock stack
+    return current->lock_stack().contains(h_obj());
+  }
+
   // Contended case, header points to ObjectMonitor (tagged pointer)
   if (mark.has_monitor()) {
     // The first stage of async deflation does not affect any field
     // used by this comparison so the ObjectMonitor* is usable here.
     ObjectMonitor* monitor = mark.monitor();
@@ -1026,31 +1202,32 @@
     }
     assert(!h_obj->mark().has_bias_pattern(), "biases should be revoked by now");
   }
 
   oop obj = h_obj();
-  address owner = NULL;
-
   markWord mark = read_stable_mark(obj);
 
-  // Uncontended case, header points to stack
-  if (mark.has_locker()) {
-    owner = (address) mark.locker();
+  if (LockingMode == LM_LEGACY && mark.has_locker()) {
+    // stack-locked so header points into owner's stack.
+    // owning_thread_from_monitor_owner() may also return null here:
+    return Threads::owning_thread_from_monitor_owner(t_list, (address) mark.locker());
+  }
+
+  if (LockingMode == LM_LIGHTWEIGHT && mark.is_fast_locked()) {
+    // fast-locked so get owner from the object.
+    // owning_thread_from_object() may also return null here:
+    return Threads::owning_thread_from_object(t_list, h_obj());
   }
 
   // Contended case, header points to ObjectMonitor (tagged pointer)
-  else if (mark.has_monitor()) {
+  if (mark.has_monitor()) {
     // The first stage of async deflation does not affect any field
     // used by this comparison so the ObjectMonitor* is usable here.
     ObjectMonitor* monitor = mark.monitor();
     assert(monitor != NULL, "monitor should be non-null");
-    owner = (address) monitor->owner();
-  }
-
-  if (owner != NULL) {
-    // owning_thread_from_monitor_owner() may also return NULL here
-    return Threads::owning_thread_from_monitor_owner(t_list, owner);
+    // owning_thread_from_monitor() may also return null here:
+    return Threads::owning_thread_from_monitor(t_list, monitor);
   }
 
   // Unlocked case, header in place
   // Cannot have assertion since this object may have been
   // locked by another thread when reaching here.
@@ -1240,42 +1417,136 @@
     return;
   }
   (void)inflate(Thread::current(), obj, inflate_cause_vm_internal);
 }
 
-ObjectMonitor* ObjectSynchronizer::inflate(Thread* current, oop object,
-                                           const InflateCause cause) {
+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(current->as_Java_thread(), 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_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();
     assert(!mark.has_bias_pattern(), "invariant");
 
     // The mark can be in one of the following states:
-    // *  Inflated     - just return
-    // *  Stack-locked - coerce it to inflated
+    // *  inflated     - Just return if using stack-locking.
+    //                   If using fast-locking and the ObjectMonitor owner
+    //                   is anonymous and the inflating_thread owns the
+    //                   object lock, then we make the inflating_thread
+    //                   the ObjectMonitor owner and remove the lock from
+    //                   the inflating_thread's lock stack.
+    // *  fast-locked  - Coerce it to inflated from fast-locked.
+    // *  stack-locked - Coerce it to inflated from stack-locked.
     // *  INFLATING    - busy wait for conversion to complete
     // *  Neutral      - aggressively inflate the object.
     // *  BIASED       - Illegal.  We should never see this
 
     // CASE: inflated
     if (mark.has_monitor()) {
       ObjectMonitor* inf = mark.monitor();
       markWord dmw = inf->header();
       assert(dmw.is_neutral(), "invariant: header=" INTPTR_FORMAT, dmw.value());
+      if (LockingMode == LM_LIGHTWEIGHT && inf->is_owner_anonymous() &&
+          inflating_thread != nullptr && inflating_thread->lock_stack().contains(object)) {
+        inf->set_owner_from_anonymous(inflating_thread);
+        size_t removed = inflating_thread->lock_stack().remove(object);
+        inf->set_recursions(removed - 1);
+      }
       return inf;
     }
 
     // CASE: inflation in progress - inflating over a stack-lock.
     // Some other thread is converting from stack-locked to inflated.
     // Only that thread can complete inflation -- other threads must wait.
     // The INFLATING value is transient.
     // Currently, we spin/yield/park and poll the markword, waiting for inflation to finish.
     // We could always eliminate polling by parking the thread on some auxiliary list.
-    if (mark == markWord::INFLATING()) {
-      read_stable_mark(object);
-      continue;
+    if (LockingMode != LM_LIGHTWEIGHT) {
+      // New lightweight locking does not use INFLATING.
+      // CASE: inflation in progress - inflating over a stack-lock.
+      // Some other thread is converting from stack-locked to inflated.
+      // Only that thread can complete inflation -- other threads must wait.
+      // The INFLATING value is transient.
+      // Currently, we spin/yield/park and poll the markword, waiting for inflation to finish.
+      // We could always eliminate polling by parking the thread on some auxiliary list.
+      if (mark == markWord::INFLATING()) {
+        read_stable_mark(object);
+        continue;
+      }
+    }
+
+    // CASE: fast-locked
+    // Could be fast-locked either by the inflating_thread or by some other thread.
+    //
+    // Note that we allocate the ObjectMonitor speculatively, _before_
+    // attempting to set the object's mark to the new ObjectMonitor. If
+    // the inflating_thread owns the monitor, then we set the ObjectMonitor's
+    // owner to the inflating_thread. Otherwise, we set the ObjectMonitor's owner
+    // to anonymous. If we lose the race to set the object's mark to the
+    // new ObjectMonitor, then we just delete it and loop around again.
+    //
+    LogStreamHandle(Trace, monitorinflation) lsh;
+    if (LockingMode == LM_LIGHTWEIGHT && mark.is_fast_locked()) {
+      ObjectMonitor* monitor = new ObjectMonitor(object);
+      monitor->set_header(mark.set_unlocked());
+      bool own = inflating_thread != nullptr && inflating_thread->lock_stack().contains(object);
+      if (own) {
+        // Owned by us.
+        monitor->set_owner_from(NULL, inflating_thread);
+      } else {
+        // Owned by somebody else.
+        monitor->set_owner_anonymous();
+      }
+      markWord monitor_mark = markWord::encode(monitor);
+      markWord old_mark = object->cas_set_mark(monitor_mark, mark);
+      if (old_mark == mark) {
+        // Success! Return inflated monitor.
+        if (own) {
+          size_t removed = inflating_thread->lock_stack().remove(object);
+          monitor->set_recursions(removed - 1);
+        }
+        // Once the ObjectMonitor is configured and object is associated
+        // with the ObjectMonitor, it is safe to allow async deflation:
+        _in_use_list.add(monitor);
+
+        // Hopefully the performance counters are allocated on distinct
+        // cache lines to avoid false sharing on MP systems ...
+        OM_PERFDATA_OP(Inflations, inc());
+        if (log_is_enabled(Trace, monitorinflation)) {
+          ResourceMark rm;
+          lsh.print_cr("inflate(has_locker): object=" INTPTR_FORMAT ", mark="
+                       INTPTR_FORMAT ", type='%s'", p2i(object),
+                       object->mark().value(), object->klass()->external_name());
+        }
+        if (event.should_commit()) {
+          post_monitor_inflate_event(&event, object, cause);
+        }
+        return monitor;
+      } else {
+        delete monitor;
+        continue;  // Interference -- just retry
+      }
     }
 
     // CASE: stack-locked
     // Could be stack-locked either by this thread or by some other thread.
     //
@@ -1284,13 +1555,12 @@
     // allocated the ObjectMonitor, and then finally STed the address of the
     // ObjectMonitor into the mark.  This was correct, but artificially lengthened
     // the interval in which INFLATING appeared in the mark, thus increasing
     // the odds of inflation contention.
 
-    LogStreamHandle(Trace, monitorinflation) lsh;
-
-    if (mark.has_locker()) {
+    if (LockingMode == LM_LEGACY && mark.has_locker()) {
+      assert(LockingMode != LM_LIGHTWEIGHT, "cannot happen with new lightweight locking");
       ObjectMonitor* m = new ObjectMonitor(object);
       // Optimistically prepare the ObjectMonitor - anticipate successful CAS
       // We do this before the CAS in order to minimize the length of time
       // in which INFLATING appears in the mark.
 
@@ -1358,11 +1628,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()) {
@@ -1402,11 +1672,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()) {
@@ -1479,10 +1749,20 @@
     log_trace(monitorinflation)("HandshakeForDeflation::do_thread: thread="
                                 INTPTR_FORMAT, p2i(thread));
   }
 };
 
+class VM_RendezvousGCThreads : public VM_Operation {
+public:
+  bool evaluate_at_safepoint() const override { return false; }
+  VMOp_Type type() const override { return VMOp_RendezvousGCThreads; }
+  void doit() override {
+    SuspendibleThreadSet::synchronize();
+    SuspendibleThreadSet::desynchronize();
+  };
+};
+
 // This function is called by the MonitorDeflationThread to deflate
 // ObjectMonitors. It is also called via do_final_audit_and_print_stats()
 // by the VMThread.
 size_t ObjectSynchronizer::deflate_idle_monitors() {
   Thread* current = Thread::current();
@@ -1531,12 +1811,17 @@
                      _in_use_list.count(), _in_use_list.max());
       }
 
       // A JavaThread needs to handshake in order to safely free the
       // ObjectMonitors that were deflated in this cycle.
+      // Also, we sync and desync GC threads around the handshake, so that they can
+      // safely read the mark-word and look-through to the object-monitor, without
+      // being afraid that the object-monitor is going away.
       HandshakeForDeflation hfd_hc;
       Handshake::execute(&hfd_hc);
+      VM_RendezvousGCThreads sync_gc;
+      VMThread::execute(&sync_gc);
 
       if (ls != NULL) {
         ls->print_cr("after handshaking: in_use_list stats: ceiling="
                      SIZE_FORMAT ", count=" SIZE_FORMAT ", max=" SIZE_FORMAT,
                      in_use_list_ceiling(), _in_use_list.count(), _in_use_list.max());
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, 2021, 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.
@@ -88,11 +88,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);
 
@@ -115,10 +126,18 @@
   static intx complete_exit(Handle obj, JavaThread* current);
   static void reenter (Handle obj, intx recursions, JavaThread* current);
 
   // 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
@@ -181,11 +200,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/thread.cpp b/src/hotspot/share/runtime/thread.cpp
--- a/src/hotspot/share/runtime/thread.cpp
+++ b/src/hotspot/share/runtime/thread.cpp
@@ -87,24 +87,25 @@
 #include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/java.hpp"
 #include "runtime/javaCalls.hpp"
 #include "runtime/jniHandles.inline.hpp"
 #include "runtime/jniPeriodicChecker.hpp"
+#include "runtime/lockStack.inline.hpp"
 #include "runtime/monitorDeflationThread.hpp"
 #include "runtime/mutexLocker.hpp"
 #include "runtime/nonJavaThread.hpp"
-#include "runtime/objectMonitor.hpp"
+#include "runtime/objectMonitor.inline.hpp"
 #include "runtime/orderAccess.hpp"
 #include "runtime/osThread.hpp"
 #include "runtime/prefetch.inline.hpp"
 #include "runtime/safepoint.hpp"
 #include "runtime/safepointMechanism.inline.hpp"
 #include "runtime/safepointVerifiers.hpp"
 #include "runtime/serviceThread.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "runtime/stackFrameStream.inline.hpp"
-#include "runtime/stackWatermarkSet.hpp"
+#include "runtime/stackWatermarkSet.inline.hpp"
 #include "runtime/statSampler.hpp"
 #include "runtime/task.hpp"
 #include "runtime/thread.inline.hpp"
 #include "runtime/threadCritical.hpp"
 #include "runtime/threadSMR.inline.hpp"
@@ -147,10 +148,13 @@
 #include "runtime/rtmLocking.hpp"
 #endif
 #if INCLUDE_JFR
 #include "jfr/jfr.hpp"
 #endif
+#if INCLUDE_VM_STRUCTS
+#include "runtime/vmStructs.hpp"
+#endif
 
 // Initialization after module runtime initialization
 void universe_post_module_init();  // must happen after call_initPhase2
 
 #ifdef DTRACE_ENABLED
@@ -703,10 +707,11 @@
 // However, there is a note in JavaThread::is_lock_owned() about the VM threads not being
 // used for compilation in the future. If that change is made, the need for these methods
 // should be revisited, and they should be removed if possible.
 
 bool Thread::is_lock_owned(address adr) const {
+  assert(LockingMode != LM_LIGHTWEIGHT, "should not be called with new lightweight locking");
   return is_in_full_stack(adr);
 }
 
 bool Thread::set_as_starting_thread() {
   assert(_starting_thread == NULL, "already initialized: "
@@ -1092,12 +1097,13 @@
   _parker(),
   _cached_monitor_info(nullptr),
 
   _class_to_be_initialized(nullptr),
 
-  _SleepEvent(ParkEvent::Allocate(this))
-{
+  _SleepEvent(ParkEvent::Allocate(this)),
+
+  _lock_stack(this) {
   set_jni_functions(jni_functions());
 
 #if INCLUDE_JVMCI
   assert(_jvmci._implicit_exception_pc == nullptr, "must be");
   if (JVMCICounterSize > 0) {
@@ -1571,11 +1577,12 @@
     return ret;
   }
 }
 
 bool JavaThread::is_lock_owned(address adr) const {
-  if (Thread::is_lock_owned(adr)) return true;
+  assert(LockingMode != LM_LIGHTWEIGHT, "should not be called with new lightweight locking");
+ if (Thread::is_lock_owned(adr)) return true;
 
   for (MonitorChunk* chunk = monitor_chunks(); chunk != NULL; chunk = chunk->next()) {
     if (chunk->contains(adr)) return true;
   }
 
@@ -2021,10 +2028,14 @@
 #endif
 
   if (jvmti_thread_state() != NULL) {
     jvmti_thread_state()->oops_do(f, cf);
   }
+
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    lock_stack().oops_do(f);
+  }
 }
 
 void JavaThread::oops_do_frames(OopClosure* f, CodeBlobClosure* cf) {
   if (!has_last_Java_frame()) {
     return;
@@ -2849,10 +2860,17 @@
   // Must be before create_vm_init_agents()
   if (Arguments::init_libraries_at_startup()) {
     convert_vm_init_libraries_to_agents();
   }
 
+  // Should happen before any agent attaches and pokes into vmStructs
+#if INCLUDE_VM_STRUCTS
+  if (UseCompactObjectHeaders) {
+    VMStructs::compact_headers_overrides();
+  }
+#endif
+
   // Launch -agentlib/-agentpath and converted -Xrun agents
   if (Arguments::init_agents_at_startup()) {
     create_vm_init_agents();
   }
 
@@ -3738,10 +3756,11 @@
 }
 
 
 JavaThread *Threads::owning_thread_from_monitor_owner(ThreadsList * t_list,
                                                       address owner) {
+  assert(LockingMode != LM_LIGHTWEIGHT, "Not with new lightweight locking");
   // NULL owner means not locked so we can skip the search
   if (owner == NULL) return NULL;
 
   DO_JAVA_THREADS(t_list, p) {
     // first, see if owner is the address of a Java thread
@@ -3767,10 +3786,41 @@
 
   // cannot assert on lack of success here; see above comment
   return the_owner;
 }
 
+JavaThread* Threads::owning_thread_from_object(ThreadsList * t_list, oop obj) {
+  assert(LockingMode == LM_LIGHTWEIGHT, "Only with new lightweight locking");
+  DO_JAVA_THREADS(t_list, q) {
+    // Need to start processing before accessing oops in the thread.
+    StackWatermark* watermark = StackWatermarkSet::get(q, StackWatermarkKind::gc);
+    if (watermark != nullptr) {
+      watermark->start_processing();
+    }
+
+    if (q->lock_stack().contains(obj)) {
+      return q;
+    }
+  }
+  return NULL;
+}
+
+JavaThread* Threads::owning_thread_from_monitor(ThreadsList* t_list, ObjectMonitor* monitor) {
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    if (monitor->is_owner_anonymous()) {
+      return owning_thread_from_object(t_list, monitor->object());
+    } else {
+      Thread* owner = reinterpret_cast<Thread*>(monitor->owner());
+      assert(owner == NULL || owner->is_Java_thread(), "only JavaThreads own monitors");
+      return reinterpret_cast<JavaThread*>(owner);
+    }
+  } else {
+    address owner = (address)monitor->owner();
+    return owning_thread_from_monitor_owner(t_list, owner);
+  }
+}
+
 class PrintOnClosure : public ThreadClosure {
 private:
   outputStream* _st;
 
 public:
diff a/src/hotspot/share/runtime/thread.hpp b/src/hotspot/share/runtime/thread.hpp
--- a/src/hotspot/share/runtime/thread.hpp
+++ b/src/hotspot/share/runtime/thread.hpp
@@ -34,10 +34,11 @@
 #include "oops/oopHandle.hpp"
 #include "runtime/frame.hpp"
 #include "runtime/globals.hpp"
 #include "runtime/handshake.hpp"
 #include "runtime/javaFrameAnchor.hpp"
+#include "runtime/lockStack.hpp"
 #include "runtime/mutexLocker.hpp"
 #include "runtime/os.hpp"
 #include "runtime/park.hpp"
 #include "runtime/safepointMechanism.hpp"
 #include "runtime/stackWatermarkSet.hpp"
@@ -1637,10 +1638,23 @@
 
   // java.lang.Thread interruption support
   void interrupt();
   bool is_interrupted(bool clear_interrupted);
 
+private:
+  LockStack _lock_stack;
+
+public:
+  LockStack& lock_stack() { return _lock_stack; }
+
+  static ByteSize lock_stack_offset()      { return byte_offset_of(JavaThread, _lock_stack); }
+  // Those offsets are used in code generators to access the LockStack that is embedded in this
+  // JavaThread structure. Those accesses are relative to the current thread, which
+  // is typically in a dedicated register.
+  static ByteSize lock_stack_top_offset()  { return lock_stack_offset() + LockStack::top_offset(); }
+  static ByteSize lock_stack_base_offset() { return lock_stack_offset() + LockStack::base_offset(); }
+
   static OopStorage* thread_oop_storage();
 
   static void verify_cross_modify_fence_failure(JavaThread *thread) PRODUCT_RETURN;
 
   // Helper function to start a VM-internal daemon thread.
@@ -1769,10 +1783,13 @@
 
   // Get owning Java thread from the monitor's owner field.
   static JavaThread *owning_thread_from_monitor_owner(ThreadsList * t_list,
                                                       address owner);
 
+  static JavaThread* owning_thread_from_object(ThreadsList* t_list, oop obj);
+  static JavaThread* owning_thread_from_monitor(ThreadsList* t_list, ObjectMonitor* owner);
+
   // Number of threads on the active threads list
   static int number_of_threads()                 { return _number_of_threads; }
   // Number of non-daemon threads on the active threads list
   static int number_of_non_daemon_threads()      { return _number_of_non_daemon_threads; }
 
diff a/src/hotspot/share/runtime/vmOperation.hpp b/src/hotspot/share/runtime/vmOperation.hpp
--- a/src/hotspot/share/runtime/vmOperation.hpp
+++ b/src/hotspot/share/runtime/vmOperation.hpp
@@ -80,19 +80,21 @@
   template(ChangeBreakpoints)                     \
   template(GetOrSetLocal)                         \
   template(ChangeSingleStep)                      \
   template(HeapWalkOperation)                     \
   template(HeapIterateOperation)                  \
+  template(HeapObjectStatistics)                  \
   template(ReportJavaOutOfMemory)                 \
   template(JFRCheckpoint)                         \
   template(ShenandoahFullGC)                      \
   template(ShenandoahInitMark)                    \
   template(ShenandoahFinalMarkStartEvac)          \
   template(ShenandoahInitUpdateRefs)              \
   template(ShenandoahFinalUpdateRefs)             \
   template(ShenandoahFinalRoots)                  \
   template(ShenandoahDegeneratedGC)               \
+  template(RendezvousGCThreads)                   \
   template(Exit)                                  \
   template(LinuxDllLoad)                          \
   template(RotateGCLog)                           \
   template(WhiteBoxOperation)                     \
   template(JVMCIResizeCounters)                   \
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
@@ -140,10 +140,20 @@
 #include "opto/rootnode.hpp"
 #include "opto/subnode.hpp"
 #include "opto/vectornode.hpp"
 #endif // COMPILER2
 
+// Used by VMStructs when CompactObjectHeaders are enabled.
+// Must match the relevant parts from the real oopDesc.
+class fakeOopDesc {
+private:
+  union _metadata {
+    Klass*      _klass;
+    narrowKlass _compressed_klass;
+  } _metadata;
+};
+
 // Note: the cross-product of (c1, c2, product, nonproduct, ...),
 // (nonstatic, static), and (unchecked, checked) has not been taken.
 // Only the macros currently needed have been defined.
 
 // A field whose type is not checked is given a null string as the
@@ -732,10 +742,13 @@
   nonstatic_field(ThreadShadow,                _exception_file,                               const char*)                           \
   nonstatic_field(ThreadShadow,                _exception_line,                               int)                                   \
   nonstatic_field(Thread,                      _active_handles,                               JNIHandleBlock*)                       \
   nonstatic_field(Thread,                      _tlab,                                         ThreadLocalAllocBuffer)                \
   nonstatic_field(Thread,                      _allocated_bytes,                              jlong)                                 \
+  nonstatic_field(JavaThread,                  _lock_stack,                                   LockStack)                             \
+  nonstatic_field(LockStack,                   _top,                                          uint32_t)                              \
+  nonstatic_field(LockStack,                   _base[0],                                      oop)                                   \
   nonstatic_field(NamedThread,                 _name,                                         char*)                                 \
   nonstatic_field(NamedThread,                 _processed_thread,                             Thread*)                               \
   nonstatic_field(JavaThread,                  _threadObj,                                    OopHandle)                             \
   nonstatic_field(JavaThread,                  _anchor,                                       JavaFrameAnchor)                       \
   nonstatic_field(JavaThread,                  _vm_result,                                    oop)                                   \
@@ -1237,10 +1250,12 @@
   declare_toplevel_type(oopDesc)                                          \
     declare_type(arrayOopDesc, oopDesc)                                   \
       declare_type(objArrayOopDesc, arrayOopDesc)                         \
     declare_type(instanceOopDesc, oopDesc)                                \
                                                                           \
+  declare_toplevel_type(fakeOopDesc)                                      \
+                                                                          \
   /**************************************************/                    \
   /* MetadataOopDesc hierarchy (NOTE: some missing) */                    \
   /**************************************************/                    \
                                                                           \
   declare_toplevel_type(CompiledICHolder)                                 \
@@ -1348,10 +1363,11 @@
   declare_toplevel_type(OSThread)                                         \
   declare_toplevel_type(JavaFrameAnchor)                                  \
                                                                           \
   declare_toplevel_type(ThreadsSMRSupport)                                \
   declare_toplevel_type(ThreadsList)                                      \
+  declare_toplevel_type(LockStack)                                        \
                                                                           \
   /***************/                                                       \
   /* Interpreter */                                                       \
   /***************/                                                       \
                                                                           \
@@ -2469,10 +2485,18 @@
   declare_constant(T_ARRAY_size)                                          \
   declare_constant(T_NARROWOOP_size)                                      \
   declare_constant(T_NARROWKLASS_size)                                    \
   declare_constant(T_VOID_size)                                           \
                                                                           \
+  /**********************************************/                        \
+  /* LockingMode enum (globalDefinitions.hpp) */                          \
+  /**********************************************/                        \
+                                                                          \
+  declare_constant(LM_MONITOR)                                            \
+  declare_constant(LM_LEGACY)                                             \
+  declare_constant(LM_LIGHTWEIGHT)                                        \
+                                                                          \
   /*********************/                                                 \
   /* Matcher (C2 only) */                                                 \
   /*********************/                                                 \
                                                                           \
   declare_c2_preprocessor_constant("Matcher::interpreter_frame_pointer_reg", Matcher::interpreter_frame_pointer_reg()) \
@@ -2631,15 +2655,18 @@
   declare_constant(markWord::age_bits)                                    \
   declare_constant(markWord::lock_bits)                                   \
   declare_constant(markWord::biased_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::biased_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::biased_lock_mask)                            \
   declare_constant(markWord::biased_lock_mask_in_place)                   \
@@ -2648,10 +2675,12 @@
   declare_constant(markWord::age_mask_in_place)                           \
   declare_constant(markWord::epoch_mask)                                  \
   declare_constant(markWord::epoch_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::biased_lock_alignment)                       \
                                                                           \
   declare_constant(markWord::locked_value)                                \
   declare_constant(markWord::unlocked_value)                              \
   declare_constant(markWord::monitor_value)                               \
@@ -2663,12 +2692,14 @@
   declare_constant(markWord::no_lock_in_place)                            \
   declare_constant(markWord::max_age)                                     \
                                                                           \
   /* InvocationCounter constants */                                       \
   declare_constant(InvocationCounter::count_increment)                    \
-  declare_constant(InvocationCounter::count_shift)
-
+  declare_constant(InvocationCounter::count_shift)                        \
+                                                                          \
+  /* ObjectMonitor constants */                                           \
+  declare_constant(ObjectMonitor::ANONYMOUS_OWNER)                        \
 
 //--------------------------------------------------------------------------------
 //
 
 // Generate and check a nonstatic field in non-product builds
@@ -3184,5 +3215,31 @@
 
 void vmStructs_init() {
   VMStructs::init();
 }
 #endif // ASSERT
+
+void VMStructs::compact_headers_overrides() {
+  assert(UseCompactObjectHeaders, "Should have been checked before");
+
+  // We cannot allow SA and other facilities to poke into VM internal fields
+  // expecting the class pointers there. This will crash in the best case,
+  // or yield incorrect execution in the worst case. This code hides the
+  // risky fields from external code by replacing their original container
+  // type to a fake one. The fake type should exist for VMStructs verification
+  // code to work.
+
+  size_t len = localHotSpotVMStructsLength();
+  for (size_t off = 0; off < len; off++) {
+    VMStructEntry* e = &localHotSpotVMStructs[off];
+    if (e == nullptr) continue;
+    if (e->typeName == nullptr) continue;
+    if (e->fieldName == nullptr) continue;
+
+    if (strcmp(e->typeName, "oopDesc") == 0) {
+      if ((strcmp(e->fieldName, "_metadata._klass") == 0) ||
+          (strcmp(e->fieldName, "_metadata._compressed_klass") == 0)) {
+        e->typeName = "fakeOopDesc";
+      }
+    }
+  }
+}
diff a/src/hotspot/share/runtime/vmStructs.hpp b/src/hotspot/share/runtime/vmStructs.hpp
--- a/src/hotspot/share/runtime/vmStructs.hpp
+++ b/src/hotspot/share/runtime/vmStructs.hpp
@@ -144,10 +144,13 @@
 private:
   // Look up a type in localHotSpotVMTypes using strcmp() (debug build only).
   // Returns 1 if found, 0 if not.
   static int findType(const char* typeName) NOT_VM_STRUCTS_RETURN_(0);
 #endif // ASSERT
+
+public:
+  static void compact_headers_overrides() NOT_VM_STRUCTS_RETURN;
 };
 
 // This utility macro quotes the passed string
 #define QUOTE(x) #x
 
diff a/src/hotspot/share/services/heapObjectStatistics.cpp b/src/hotspot/share/services/heapObjectStatistics.cpp
--- /dev/null
+++ b/src/hotspot/share/services/heapObjectStatistics.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 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
+ * 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/collectedHeap.hpp"
+#include "logging/logStream.hpp"
+#include "logging/logTag.hpp"
+#include "memory/allocation.hpp"
+#include "memory/iterator.hpp"
+#include "memory/resourceArea.hpp"
+#include "memory/universe.hpp"
+#include "oops/oop.inline.hpp"
+#include "runtime/mutexLocker.hpp"
+#include "runtime/vmThread.hpp"
+#include "services/heapObjectStatistics.hpp"
+#include "utilities/copy.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "utilities/ostream.hpp"
+
+HeapObjectStatistics* HeapObjectStatistics::_instance = NULL;
+
+class HeapObjectStatsObjectClosure : public ObjectClosure {
+private:
+  HeapObjectStatistics* const _stats;
+public:
+  HeapObjectStatsObjectClosure() : _stats(HeapObjectStatistics::instance()) {}
+  void do_object(oop obj) {
+    _stats->visit_object(obj);
+  }
+};
+
+class VM_HeapObjectStatistics : public VM_Operation {
+public:
+  VMOp_Type type() const { return VMOp_HeapObjectStatistics; }
+  bool doit_prologue() {
+    Heap_lock->lock();
+    return true;
+  }
+
+  void doit_epilogue() {
+    Heap_lock->unlock();
+  }
+
+  void doit() {
+    assert(SafepointSynchronize::is_at_safepoint(), "all threads are stopped");
+    assert(Heap_lock->is_locked(), "should have the Heap_lock");
+
+    CollectedHeap* heap = Universe::heap();
+    heap->ensure_parsability(false);
+
+    HeapObjectStatistics* stats = HeapObjectStatistics::instance();
+    stats->begin_sample();
+
+    HeapObjectStatsObjectClosure cl;
+    heap->object_iterate(&cl);
+  }
+};
+
+HeapObjectStatisticsTask::HeapObjectStatisticsTask() : PeriodicTask(HeapObjectStatsSamplingInterval) {}
+
+void HeapObjectStatisticsTask::task() {
+  VM_HeapObjectStatistics vmop;
+  VMThread::execute(&vmop);
+}
+
+void HeapObjectStatistics::initialize() {
+  assert(_instance == NULL, "Don't init twice");
+  if (HeapObjectStats) {
+    _instance = new HeapObjectStatistics();
+    _instance->start();
+  }
+}
+
+void HeapObjectStatistics::shutdown() {
+  if (HeapObjectStats) {
+    assert(_instance != NULL, "Must be initialized");
+    LogTarget(Info, heap, stats) lt;
+    if (lt.is_enabled()) {
+      LogStream ls(lt);
+      ResourceMark rm;
+      _instance->print(&ls);
+    }
+    _instance->stop();
+    delete _instance;
+    _instance = NULL;
+  }
+}
+
+HeapObjectStatistics* HeapObjectStatistics::instance() {
+  assert(_instance != NULL, "Must be initialized");
+  return _instance;
+}
+
+void HeapObjectStatistics::increase_counter(uint64_t& counter, uint64_t val) {
+  uint64_t oldval = counter;
+  uint64_t newval = counter + val;
+  if (newval < oldval) {
+    log_warning(heap, stats)("HeapObjectStats counter overflow: resulting statistics will be useless");
+  }
+  counter = newval;
+}
+
+HeapObjectStatistics::HeapObjectStatistics() :
+  _task(), _num_samples(0), _num_objects(0), _num_ihashed(0), _num_locked(0), _lds(0) { }
+
+void HeapObjectStatistics::start() {
+  _task.enroll();
+}
+
+void HeapObjectStatistics::stop() {
+  _task.disenroll();
+}
+
+void HeapObjectStatistics::begin_sample() {
+  _num_samples++;
+}
+
+void HeapObjectStatistics::visit_object(oop obj) {
+  increase_counter(_num_objects);
+  markWord mark = obj->mark();
+  if (!mark.has_no_hash()) {
+    increase_counter(_num_ihashed);
+    if (mark.age() > 0) {
+      increase_counter(_num_ihashed_moved);
+    }
+  }
+  if (mark.is_locked()) {
+    increase_counter(_num_locked);
+  }
+#ifdef ASSERT
+#ifdef _LP64
+  if (!mark.has_displaced_mark_helper()) {
+    assert(mark.narrow_klass() == CompressedKlassPointers::encode(obj->klass_or_null()), "upper 32 mark bits must be narrow klass: mark: " INTPTR_FORMAT ", compressed-klass: " INTPTR_FORMAT, (intptr_t)mark.narrow_klass(), (intptr_t)CompressedKlassPointers::encode(obj->klass_or_null()));
+  }
+#endif
+#endif
+  increase_counter(_lds, obj->size());
+}
+
+void HeapObjectStatistics::print(outputStream* out) const {
+  if (!HeapObjectStats) {
+    return;
+  }
+  if (_num_samples == 0 || _num_objects == 0) {
+    return;
+  }
+
+  out->print_cr("Number of samples:  " UINT64_FORMAT, _num_samples);
+  out->print_cr("Average number of objects: " UINT64_FORMAT, _num_objects / _num_samples);
+  out->print_cr("Average object size: " UINT64_FORMAT " bytes, %.1f words", (_lds * HeapWordSize) / _num_objects, (float) _lds / _num_objects);
+  out->print_cr("Average number of hashed objects: " UINT64_FORMAT " (%.2f%%)", _num_ihashed / _num_samples, (float) (_num_ihashed * 100.0) / _num_objects);
+  out->print_cr("Average number of moved hashed objects: " UINT64_FORMAT " (%.2f%%)", _num_ihashed_moved / _num_samples, (float) (_num_ihashed_moved * 100.0) / _num_objects);
+  out->print_cr("Average number of locked objects: " UINT64_FORMAT " (%.2f%%)", _num_locked / _num_samples, (float) (_num_locked * 100) / _num_objects);
+  out->print_cr("Average LDS: " UINT64_FORMAT " bytes", _lds * HeapWordSize / _num_samples);
+  out->print_cr("Avg LDS with (assumed) 64bit header: " UINT64_FORMAT " bytes (%.1f%%)", (_lds - _num_objects) * HeapWordSize / _num_samples, ((float) _lds - _num_objects) * 100.0 / _lds);
+}
diff a/src/hotspot/share/services/heapObjectStatistics.hpp b/src/hotspot/share/services/heapObjectStatistics.hpp
--- /dev/null
+++ b/src/hotspot/share/services/heapObjectStatistics.hpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 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
+ * 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_SERVICES_HEAPOBJECTSTATISTICS_HPP
+#define SHARE_SERVICES_HEAPOBJECTSTATISTICS_HPP
+
+#include "memory/allocation.hpp"
+#include "oops/oopsHierarchy.hpp"
+#include "runtime/task.hpp"
+#include "runtime/vmOperation.hpp"
+
+class outputStream;
+
+class HeapObjectStatisticsTask : public PeriodicTask {
+public:
+  HeapObjectStatisticsTask();
+  void task();
+};
+
+class HeapObjectStatistics : public CHeapObj<mtGC> {
+private:
+  static HeapObjectStatistics* _instance;
+
+  HeapObjectStatisticsTask _task;
+  uint64_t _num_samples;
+  uint64_t _num_objects;
+  uint64_t _num_ihashed;
+  uint64_t _num_ihashed_moved;
+  uint64_t _num_locked;
+  uint64_t _lds;
+
+  static void increase_counter(uint64_t& counter, uint64_t val = 1);
+
+  void print(outputStream* out) const;
+
+public:
+  static void initialize();
+  static void shutdown();
+
+  static HeapObjectStatistics* instance();
+
+  HeapObjectStatistics();
+  void start();
+  void stop();
+
+  void begin_sample();
+  void visit_object(oop object);
+};
+
+#endif // SHARE_SERVICES_HEAPOBJECTSTATISTICS_HPP
diff a/src/hotspot/share/services/threadService.cpp b/src/hotspot/share/services/threadService.cpp
--- a/src/hotspot/share/services/threadService.cpp
+++ b/src/hotspot/share/services/threadService.cpp
@@ -433,14 +433,12 @@
         if (owner != NULL && // the raw monitor could be released at any time
             owner->is_Java_thread()) {
           currentThread = owner->as_Java_thread();
         }
       } else if (waitingToLockMonitor != NULL) {
-        address currentOwner = (address)waitingToLockMonitor->owner();
-        if (currentOwner != NULL) {
-          currentThread = Threads::owning_thread_from_monitor_owner(t_list,
-                                                                    currentOwner);
+        if (waitingToLockMonitor->has_owner()) {
+          currentThread = Threads::owning_thread_from_monitor(t_list, waitingToLockMonitor);
           if (currentThread == NULL) {
             // This function is called at a safepoint so the JavaThread
             // that owns waitingToLockMonitor should be findable, but
             // if it is not findable, then the previous currentThread is
             // blocked permanently. We record this as a deadlock.
@@ -1012,12 +1010,11 @@
                  obj->klass()->external_name());
 
       if (!currentThread->current_pending_monitor_is_from_java()) {
         owner_desc = "\n  in JNI, which is held by";
       }
-      currentThread = Threads::owning_thread_from_monitor_owner(t_list,
-                                                                (address)waitingToLockMonitor->owner());
+      currentThread = Threads::owning_thread_from_monitor(t_list, waitingToLockMonitor);
       if (currentThread == NULL) {
         // The deadlock was detected at a safepoint so the JavaThread
         // that owns waitingToLockMonitor should be findable, but
         // if it is not findable, then the previous currentThread is
         // blocked permanently.
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
@@ -962,10 +962,19 @@
   _thread_blocked           = 10, // blocked in vm
   _thread_blocked_trans     = 11, // corresponding transition state
   _thread_max_state         = 12  // maximum thread state+1 - used for statistics allocation
 };
 
+enum LockingMode {
+  // Use only heavy monitors for locking
+  LM_MONITOR     = 0,
+  // Legacy stack-locking, with monitors as 2nd tier
+  LM_LEGACY      = 1,
+  // New lightweight locking, with monitors as 2nd tier
+  LM_LIGHTWEIGHT = 2
+};
+
 //----------------------------------------------------------------------------------------------------
 // Special constants for debugging
 
 const jint     badInt           = -3;                       // generic "bad int" value
 const intptr_t badAddressVal    = -2;                       // generic "bad address" value
@@ -979,11 +988,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       badOop            (cast_to_oop(::badOopVal))
diff a/src/hotspot/share/utilities/objectBitSet.hpp b/src/hotspot/share/utilities/objectBitSet.hpp
--- /dev/null
+++ b/src/hotspot/share/utilities/objectBitSet.hpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2014, 2022, 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_OBJECTBITSET_HPP
+#define SHARE_UTILITIES_OBJECTBITSET_HPP
+
+#include "memory/allocation.hpp"
+#include "oops/oop.hpp"
+#include "oops/oopsHierarchy.hpp"
+#include "utilities/bitMap.hpp"
+#include "utilities/hashtable.hpp"
+
+class MemRegion;
+
+/*
+ * ObjectBitSet is a sparse bitmap for marking objects in the Java heap.
+ * It holds one bit per ObjAlignmentInBytes-aligned address. Its underlying backing memory is
+ * allocated on-demand only, in fragments covering 64M heap ranges. Fragments are never deleted
+ * during the lifetime of the ObjectBitSet. The underlying memory is allocated from C-Heap.
+ */
+template<MEMFLAGS F>
+class ObjectBitSet : public CHeapObj<F> {
+  const static size_t _bitmap_granularity_shift = 26; // 64M
+  const static size_t _bitmap_granularity_size = (size_t)1 << _bitmap_granularity_shift;
+  const static size_t _bitmap_granularity_mask = _bitmap_granularity_size - 1;
+
+  class BitMapFragment;
+
+  class BitMapFragmentTable : public BasicHashtable<F> {
+    class Entry : public BasicHashtableEntry<F> {
+    public:
+      uintptr_t _key;
+      CHeapBitMap* _value;
+
+      Entry* next() {
+        return (Entry*)BasicHashtableEntry<F>::next();
+      }
+    };
+
+  protected:
+    Entry* bucket(int i) const;
+
+    Entry* new_entry(unsigned int hashValue, uintptr_t key, CHeapBitMap* value);
+
+    unsigned hash_segment(uintptr_t key) {
+      unsigned hash = (unsigned)key;
+      return hash ^ (hash >> 3);
+    }
+
+    unsigned hash_to_index(unsigned hash) {
+      return hash & (BasicHashtable<F>::table_size() - 1);
+    }
+
+  public:
+    BitMapFragmentTable(int table_size) : BasicHashtable<F>(table_size, sizeof(Entry)) {}
+    ~BitMapFragmentTable();
+    void add(uintptr_t key, CHeapBitMap* value);
+    CHeapBitMap** lookup(uintptr_t key);
+  };
+
+  CHeapBitMap* get_fragment_bits(uintptr_t addr);
+
+  BitMapFragmentTable _bitmap_fragments;
+  BitMapFragment* _fragment_list;
+  CHeapBitMap* _last_fragment_bits;
+  uintptr_t _last_fragment_granule;
+
+ public:
+  ObjectBitSet();
+  ~ObjectBitSet();
+
+  BitMap::idx_t addr_to_bit(uintptr_t addr) const;
+
+  void mark_obj(uintptr_t addr);
+
+  void mark_obj(oop obj) {
+    return mark_obj(cast_from_oop<uintptr_t>(obj));
+  }
+
+  bool is_marked(uintptr_t addr);
+
+  bool is_marked(oop obj) {
+    return is_marked(cast_from_oop<uintptr_t>(obj));
+  }
+};
+
+template<MEMFLAGS F>
+class ObjectBitSet<F>::BitMapFragment : public CHeapObj<F> {
+  CHeapBitMap _bits;
+  BitMapFragment* _next;
+
+public:
+  BitMapFragment(uintptr_t granule, BitMapFragment* next);
+
+  BitMapFragment* next() const {
+    return _next;
+  }
+
+  CHeapBitMap* bits() {
+    return &_bits;
+  }
+};
+
+#endif // SHARE_UTILITIES_OBJECTBITSET_HPP
diff a/src/hotspot/share/utilities/objectBitSet.inline.hpp b/src/hotspot/share/utilities/objectBitSet.inline.hpp
--- /dev/null
+++ b/src/hotspot/share/utilities/objectBitSet.inline.hpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2019, 2022, 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_OBJECTBITSET_INLINE_HPP
+#define SHARE_UTILITIES_OBJECTBITSET_INLINE_HPP
+
+#include "utilities/objectBitSet.hpp"
+
+#include "memory/memRegion.hpp"
+#include "utilities/bitMap.inline.hpp"
+#include "utilities/hashtable.inline.hpp"
+
+template<MEMFLAGS F>
+ObjectBitSet<F>::BitMapFragment::BitMapFragment(uintptr_t granule, BitMapFragment* next) :
+        _bits(_bitmap_granularity_size >> LogMinObjAlignmentInBytes, F, true /* clear */),
+        _next(next) {
+}
+
+template<MEMFLAGS F>
+ObjectBitSet<F>::ObjectBitSet() :
+        _bitmap_fragments(32),
+        _fragment_list(NULL),
+        _last_fragment_bits(NULL),
+        _last_fragment_granule(UINTPTR_MAX) {
+}
+
+template<MEMFLAGS F>
+ObjectBitSet<F>::~ObjectBitSet() {
+  BitMapFragment* current = _fragment_list;
+  while (current != NULL) {
+    BitMapFragment* next = current->next();
+    delete current;
+    current = next;
+  }
+}
+
+template<MEMFLAGS F>
+ObjectBitSet<F>::BitMapFragmentTable::~BitMapFragmentTable() {
+  for (int index = 0; index < BasicHashtable<F>::table_size(); index ++) {
+    Entry* e = bucket(index);
+    while (e != nullptr) {
+      Entry* tmp = e;
+      e = e->next();
+      BasicHashtable<F>::free_entry(tmp);
+    }
+  }
+}
+
+template<MEMFLAGS F>
+inline typename ObjectBitSet<F>::BitMapFragmentTable::Entry* ObjectBitSet<F>::BitMapFragmentTable::bucket(int i) const {
+  return (Entry*)BasicHashtable<F>::bucket(i);
+}
+
+template<MEMFLAGS F>
+inline typename ObjectBitSet<F>::BitMapFragmentTable::Entry*
+  ObjectBitSet<F>::BitMapFragmentTable::new_entry(unsigned int hash, uintptr_t key, CHeapBitMap* value) {
+
+  Entry* entry = (Entry*)BasicHashtable<F>::new_entry(hash);
+  entry->_key = key;
+  entry->_value = value;
+  return entry;
+}
+
+template<MEMFLAGS F>
+inline void ObjectBitSet<F>::BitMapFragmentTable::add(uintptr_t key, CHeapBitMap* value) {
+  unsigned hash = hash_segment(key);
+  Entry* entry = new_entry(hash, key, value);
+  BasicHashtable<F>::add_entry(hash_to_index(hash), entry);
+}
+
+template<MEMFLAGS F>
+inline CHeapBitMap** ObjectBitSet<F>::BitMapFragmentTable::lookup(uintptr_t key) {
+  unsigned hash = hash_segment(key);
+  int index = hash_to_index(hash);
+  for (Entry* e = bucket(index); e != NULL; e = e->next()) {
+    if (e->hash() == hash && e->_key == key) {
+      return &(e->_value);
+    }
+  }
+  return NULL;
+}
+
+template<MEMFLAGS F>
+inline BitMap::idx_t ObjectBitSet<F>::addr_to_bit(uintptr_t addr) const {
+  return (addr & _bitmap_granularity_mask) >> LogMinObjAlignmentInBytes;
+}
+
+template<MEMFLAGS F>
+inline CHeapBitMap* ObjectBitSet<F>::get_fragment_bits(uintptr_t addr) {
+  uintptr_t granule = addr >> _bitmap_granularity_shift;
+  if (granule == _last_fragment_granule) {
+    return _last_fragment_bits;
+  }
+  CHeapBitMap* bits = NULL;
+
+  CHeapBitMap** found = _bitmap_fragments.lookup(granule);
+  if (found != NULL) {
+    bits = *found;
+  } else {
+    BitMapFragment* fragment = new BitMapFragment(granule, _fragment_list);
+    bits = fragment->bits();
+    _fragment_list = fragment;
+    if (_bitmap_fragments.number_of_entries() * 100 / _bitmap_fragments.table_size() > 25) {
+      _bitmap_fragments.resize(_bitmap_fragments.table_size() * 2);
+    }
+    _bitmap_fragments.add(granule, bits);
+  }
+
+  _last_fragment_bits = bits;
+  _last_fragment_granule = granule;
+
+  return bits;
+}
+
+template<MEMFLAGS F>
+inline void ObjectBitSet<F>::mark_obj(uintptr_t addr) {
+  CHeapBitMap* bits = get_fragment_bits(addr);
+  const BitMap::idx_t bit = addr_to_bit(addr);
+  bits->set_bit(bit);
+}
+
+template<MEMFLAGS F>
+inline bool ObjectBitSet<F>::is_marked(uintptr_t addr) {
+  CHeapBitMap* bits = get_fragment_bits(addr);
+  const BitMap::idx_t bit = addr_to_bit(addr);
+  return bits->at(bit);
+}
+
+#endif // SHARE_UTILITIES_OBJECTBITSET_INLINE_HPP
diff a/src/hotspot/share/utilities/vmError.cpp b/src/hotspot/share/utilities/vmError.cpp
--- a/src/hotspot/share/utilities/vmError.cpp
+++ b/src/hotspot/share/utilities/vmError.cpp
@@ -944,10 +944,18 @@
          }
        }
        st->cr();
      }
 
+  STEP("printing lock stack")
+
+    if (_verbose && _thread != nullptr && _thread->is_Java_thread() && LockingMode == LM_LIGHTWEIGHT) {
+      st->print_cr("Lock stack of current Java thread (top to bottom):");
+      _thread->as_Java_thread()->lock_stack().print_on(st);
+      st->cr();
+    }
+
   STEP("printing code blobs if possible")
 
      if (_verbose) {
        const int printed_capacity = max_error_log_print_code;
        address printed[printed_capacity];
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.
@@ -472,11 +475,19 @@
     return value;
   }
 
   protected long readCompKlassAddressValue(long address)
     throws UnmappedAddressException, UnalignedAddressException {
-    long value = readCInteger(address, getKlassPtrSize(), true);
+    long value;
+    if (VM.getVM().isCompactObjectHeadersEnabled()) {
+      // On 64 bit systems, 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
@@ -112,17 +112,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,39 +55,59 @@
   // Size of the arrayOopDesc
   private static long headerSize=0;
   private static long lengthOffsetInBytes=0;
   private static long typeSize;
 
+  // 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) {
+    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;
+    if (VM.getVM().isCompactObjectHeadersEnabled()) {
+      headerSize = lengthOffsetInBytes() + VM.getVM().getIntSize();
     } else {
-      headerSize = VM.getVM().alignUp(typeSize + VM.getVM().getIntSize(),
-                                      VM.getVM().getHeapWordSize());
+      if (VM.getVM().isCompressedKlassPointersEnabled()) {
+        headerSize = typeSize;
+      } else {
+        headerSize = VM.getVM().alignUp(typeSize + VM.getVM().getIntSize(),
+                                        VM.getVM().getHeapWordSize());
+      }
     }
     return headerSize;
   }
 
   private static long headerSize(BasicType type) {
-    if (Universe.elementTypeShouldBeAligned(type)) {
-       return alignObjectSize(headerSizeInBytes())/VM.getVM().getHeapWordSize();
-    } else {
-      return headerSizeInBytes()/VM.getVM().getHeapWordSize();
-    }
-  }
+     if (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()) {
-      lengthOffsetInBytes = typeSize - VM.getVM().getIntSize();
+    if (VM.getVM().isCompactObjectHeadersEnabled()) {
+      lengthOffsetInBytes = Oop.getHeaderSize();
     } else {
-      lengthOffsetInBytes = typeSize;
+      if (VM.getVM().isCompressedKlassPointersEnabled()) {
+        lengthOffsetInBytes = typeSize - VM.getVM().getIntSize();
+      } else {
+        lengthOffsetInBytes = typeSize;
+      }
     }
     return lengthOffsetInBytes;
   }
 
   // Accessors for declared fields
@@ -106,11 +126,21 @@
     s = Oop.alignObjectSize(s);
     return s;
   }
 
   public static long baseOffsetInBytes(BasicType type) {
-    return headerSize(type) * VM.getVM().getHeapWordSize();
+    if (VM.getVM().isCompactObjectHeadersEnabled()) {
+      long typeSizeInBytes = headerSizeInBytes();
+      if (elementTypeShouldBeAligned(type)) {
+        VM vm = VM.getVM();
+        return vm.alignUp(typeSizeInBytes, vm.getVM().getHeapWordSize());
+      } else {
+        return typeSizeInBytes;
+      }
+    } else {
+      return headerSize(type) * VM.getVM().getHeapWordSize();
+    }
   }
 
   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,10 +53,13 @@
     super(handle, heap);
   }
 
   // Returns header size in bytes.
   public static long getHeaderSize() {
+    if (VM.getVM().isCompactObjectHeadersEnabled()) {
+      return Oop.getHeaderSize();
+    }
     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
@@ -50,23 +50,30 @@
     ageBits             = db.lookupLongConstant("markWord::age_bits").longValue();
     lockBits            = db.lookupLongConstant("markWord::lock_bits").longValue();
     biasedLockBits      = db.lookupLongConstant("markWord::biased_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();
     biasedLockShift     = db.lookupLongConstant("markWord::biased_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();
     biasedLockMask      = db.lookupLongConstant("markWord::biased_lock_mask").longValue();
     biasedLockMaskInPlace  = db.lookupLongConstant("markWord::biased_lock_mask_in_place").longValue();
     biasedLockBitInPlace  = db.lookupLongConstant("markWord::biased_lock_bit_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();
     biasedLockAlignment  = db.lookupLongConstant("markWord::biased_lock_alignment").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();
@@ -84,25 +91,30 @@
   private static long ageBits;
   private static long lockBits;
   private static long biasedLockBits;
   private static long maxHashBits;
   private static long hashBits;
+  private static long hashBitsCompact;
 
   private static long lockShift;
   private static long biasedLockShift;
   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 biasedLockMask;
   private static long biasedLockMaskInPlace;
   private static long biasedLockBitInPlace;
   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 biasedLockAlignment;
 
   private static long lockedValue;
   private static long unlockedValue;
   private static long monitorValue;
@@ -119,10 +131,14 @@
   /* Constants in markWord used by CMS. */
   private static long cmsShift;
   private static long cmsMask;
   private static long sizeShift;
 
+  public static long getKlassShift() {
+    return klassShift;
+  }
+
   public Mark(Address addr) {
     super(addr);
   }
 
   public long value() {
@@ -213,17 +229,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
@@ -46,13 +46,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;
 
@@ -75,12 +80,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());
     }
   }
@@ -147,14 +165,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); }
@@ -206,11 +226,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/JavaThread.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/JavaThread.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/JavaThread.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/JavaThread.java
@@ -42,18 +42,21 @@
 
 public class JavaThread extends Thread {
   private static final boolean DEBUG = System.getProperty("sun.jvm.hotspot.runtime.JavaThread.DEBUG") != null;
 
   private static long          threadObjFieldOffset;
+  private static long          lockStackTopOffset;
+  private static long          lockStackBaseOffset;
   private static AddressField  anchorField;
   private static AddressField  lastJavaSPField;
   private static AddressField  lastJavaPCField;
   private static CIntegerField threadStateField;
   private static AddressField  osThreadField;
   private static AddressField  stackBaseField;
   private static CIntegerField stackSizeField;
   private static CIntegerField terminatedField;
+  private static long oopPtrSize;
 
   private static JavaThreadPDAccess access;
 
   // JavaThreadStates read from underlying process
   private static int           UNINITIALIZED;
@@ -82,10 +85,11 @@
   }
 
   private static synchronized void initialize(TypeDataBase db) {
     Type type = db.lookupType("JavaThread");
     Type anchorType = db.lookupType("JavaFrameAnchor");
+    Type typeLockStack = db.lookupType("LockStack");
 
     threadObjFieldOffset = type.getField("_threadObj").getOffset();
 
     anchorField       = type.getAddressField("_anchor");
     lastJavaSPField   = anchorType.getAddressField("_last_Java_sp");
@@ -94,10 +98,14 @@
     osThreadField     = type.getAddressField("_osthread");
     stackBaseField    = type.getAddressField("_stack_base");
     stackSizeField    = type.getCIntegerField("_stack_size");
     terminatedField   = type.getCIntegerField("_terminated");
 
+    lockStackTopOffset = type.getField("_lock_stack").getOffset() + typeLockStack.getField("_top").getOffset();
+    lockStackBaseOffset = type.getField("_lock_stack").getOffset() + typeLockStack.getField("_base[0]").getOffset();
+    oopPtrSize = VM.getVM().getAddressSize();
+
     UNINITIALIZED     = db.lookupIntConstant("_thread_uninitialized").intValue();
     NEW               = db.lookupIntConstant("_thread_new").intValue();
     NEW_TRANS         = db.lookupIntConstant("_thread_new_trans").intValue();
     IN_NATIVE         = db.lookupIntConstant("_thread_in_native").intValue();
     IN_NATIVE_TRANS   = db.lookupIntConstant("_thread_in_native_trans").intValue();
@@ -390,10 +398,27 @@
     // Be robust
     if (sp == null) return false;
     return stackBase.greaterThan(a) && sp.lessThanOrEqual(a);
   }
 
+  public boolean isLockOwned(OopHandle obj) {
+    long current = lockStackBaseOffset;
+    long end = addr.getJIntAt(lockStackTopOffset);
+    if (Assert.ASSERTS_ENABLED) {
+      Assert.that(current <= end, "current stack offset must be above base offset");
+    }
+
+    while (current < end) {
+      Address oop = addr.getAddressAt(current);
+      if (oop.equals(obj)) {
+        return true;
+      }
+      current += oopPtrSize;
+    }
+    return false;
+  }
+
   public boolean isLockOwned(Address a) {
     Address stackBase = getStackBase();
     Address stackLimit = stackBase.addOffsetTo(-getStackSize());
 
     return stackBase.greaterThan(a) && stackLimit.lessThanOrEqual(a);
diff a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/JavaVFrame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/JavaVFrame.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/JavaVFrame.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/JavaVFrame.java
@@ -75,10 +75,14 @@
   private String identifyLockState(MonitorInfo monitor, String waitingState) {
     Mark mark = new Mark(monitor.owner());
     if (mark.hasMonitor() &&
         ( // we have marked ourself as pending on this monitor
           mark.monitor().equals(thread.getCurrentPendingMonitor()) ||
+          // Owned anonymously means that we are not the owner of
+          // the monitor and must be waiting for the owner to
+          // exit it.
+          mark.monitor().isOwnedAnonymous() ||
           // we are not the owner of this monitor
           !mark.monitor().isEntered(thread)
         )) {
       return waitingState;
     }
diff a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/LockingMode.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/LockingMode.java
--- /dev/null
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/LockingMode.java
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ *
+ */
+
+package sun.jvm.hotspot.runtime;
+
+import sun.jvm.hotspot.types.TypeDataBase;
+
+
+/** Encapsulates the LockingMode enum in globalDefinitions.hpp in
+    the VM. */
+
+public class LockingMode {
+  private static int monitor;
+  private static int legacy;
+  private static int lightweight;
+
+  static {
+    VM.registerVMInitializedObserver(
+        (o, d) -> initialize(VM.getVM().getTypeDataBase()));
+  }
+
+  private static synchronized void initialize(TypeDataBase db) {
+    monitor     = db.lookupIntConstant("LM_MONITOR").intValue();
+    legacy      = db.lookupIntConstant("LM_LEGACY").intValue();
+    lightweight = db.lookupIntConstant("LM_LIGHTWEIGHT").intValue();
+  }
+
+  public static int getMonitor() {
+    return monitor;
+  }
+
+  public static int getLegacy() {
+    return legacy;
+  }
+
+  public static int getLightweight() {
+    return lightweight;
+  }
+}
diff a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ObjectMonitor.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ObjectMonitor.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ObjectMonitor.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ObjectMonitor.java
@@ -53,10 +53,12 @@
     f = type.getField("_next_om");
     nextOMFieldOffset = f.getOffset();
     contentionsField  = type.getJIntField("_contentions");
     waitersField = type.getJIntField("_waiters");
     recursionsField = type.getCIntegerField("_recursions");
+
+    ANONYMOUS_OWNER = db.lookupLongConstant("ObjectMonitor::ANONYMOUS_OWNER").longValue();
   }
 
   public ObjectMonitor(Address addr) {
     super(addr);
   }
@@ -77,10 +79,14 @@
       return true;
     }
     return false;
   }
 
+  public boolean isOwnedAnonymous() {
+    return addr.getAddressAt(ownerFieldOffset).asLongValue() == ANONYMOUS_OWNER;
+  }
+
   public Address owner() { return addr.getAddressAt(ownerFieldOffset); }
   // FIXME
   //  void      set_owner(void* owner);
 
   public int    waiters() { return waitersField.getValue(addr); }
@@ -112,7 +118,9 @@
   private static long          ownerFieldOffset;
   private static long          nextOMFieldOffset;
   private static JIntField     contentionsField;
   private static JIntField     waitersField;
   private static CIntegerField recursionsField;
+  private static long          ANONYMOUS_OWNER;
+
   // FIXME: expose platform-dependent stuff
 }
diff a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Threads.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Threads.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Threads.java
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Threads.java
@@ -209,10 +209,11 @@
         }
     }
 
     // refer to Threads::owning_thread_from_monitor_owner
     public JavaThread owningThreadFromMonitor(Address o) {
+        assert(VM.getVM().getCommandLineFlag("LockingMode").getInt() != LockingMode.getLightweight());
         if (o == null) return null;
         for (int i = 0; i < getNumberOfThreads(); i++) {
             JavaThread thread = getJavaThreadAt(i);
             if (o.equals(thread.threadObjectAddress())) {
                 return thread;
@@ -226,11 +227,32 @@
         }
         return null;
     }
 
     public JavaThread owningThreadFromMonitor(ObjectMonitor monitor) {
-        return owningThreadFromMonitor(monitor.owner());
+        if (VM.getVM().getCommandLineFlag("LockingMode").getInt() == LockingMode.getLightweight()) {
+            if (monitor.isOwnedAnonymous()) {
+                OopHandle object = monitor.object();
+                for (int i = 0; i < getNumberOfThreads(); i++) {
+                    JavaThread thread = getJavaThreadAt(i);
+                    if (thread.isLockOwned(object)) {
+                        return thread;
+                     }
+                }
+                // We should have found the owner, however, as the VM could be in any state, including the middle
+                // of performing GC, it is not always possible to do so. Just return null if we can't locate it.
+                System.out.println("Warning: We failed to find a thread that owns an anonymous lock. This is likely");
+                System.out.println("due to the JVM currently running a GC. Locking information may not be accurate.");
+                return null;
+            }
+            // Owner can only be threads at this point.
+            Address o = monitor.owner();
+            if (o == null) return null;
+            return new JavaThread(o);
+        } else {
+            return owningThreadFromMonitor(monitor.owner());
+        }
     }
 
     // refer to Threads::get_pending_threads
     // Get list of Java threads that are waiting to enter the specified monitor.
     public List<JavaThread> getPendingThreads(ObjectMonitor monitor) {
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
@@ -145,10 +145,11 @@
   private static Type uint64tType;
   private static CIntegerType boolType;
   private Boolean sharingEnabled;
   private Boolean compressedOopsEnabled;
   private Boolean compressedKlassPointersEnabled;
+  private Boolean compactObjectHeadersEnabled;
 
   // command line flags supplied to VM - see struct JVMFlag in jvmFlag.hpp
   public static final class Flag {
      private String type;
      private String name;
@@ -958,10 +959,19 @@
              (flag.getBool()? Boolean.TRUE: Boolean.FALSE);
     }
     return compressedKlassPointersEnabled.booleanValue();
   }
 
+  public boolean isCompactObjectHeadersEnabled() {
+    if (compactObjectHeadersEnabled == null) {
+      Flag flag = getCommandLineFlag("UseCompactObjectHeaders");
+      compactObjectHeadersEnabled = (flag == null) ? Boolean.FALSE:
+                     (flag.getBool()? Boolean.TRUE: Boolean.FALSE);
+    }
+    return compactObjectHeadersEnabled.booleanValue();
+  }
+
   public int getObjectAlignmentInBytes() {
     if (objectAlignmentInBytes == 0) {
         Flag flag = getCommandLineFlag("ObjectAlignmentInBytes");
         objectAlignmentInBytes = (flag == null) ? 8 : (int)flag.getIntx();
     }
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
@@ -27,10 +27,11 @@
 import java.util.*;
 import sun.jvm.hotspot.debugger.*;
 import sun.jvm.hotspot.memory.*;
 import sun.jvm.hotspot.oops.Metadata;
 import sun.jvm.hotspot.oops.Klass;
+import sun.jvm.hotspot.oops.Oop;
 import sun.jvm.hotspot.runtime.*;
 import sun.jvm.hotspot.types.*;
 import sun.jvm.hotspot.utilities.Observable;
 import sun.jvm.hotspot.utilities.Observer;
 
@@ -40,44 +41,21 @@
     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 e) {
       return false;
     } catch (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 ScopedDisabledBiasedLocking {
@@ -64,10 +65,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
@@ -85,5 +85,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), 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,18 @@
   memset(mem, 0, ARRAY_SIZE(mem));
 
   char* addr = align_up(mem, 16);
 
   typeArrayOop o = (typeArrayOop) cast_to_oop(addr);
-  o->set_klass(Universe::boolArrayKlassObj());
+#ifdef _LP64
+  if (UseCompactObjectHeaders) {
+    o->set_mark(Universe::boolArrayKlassObj()->prototype_header());
+  } else
+#endif
+  {
+    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,428 @@
+/*
+ * 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 "memory/universe.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/gtest/utilities/test_objectBitSet.cpp b/test/hotspot/gtest/utilities/test_objectBitSet.cpp
--- /dev/null
+++ b/test/hotspot/gtest/utilities/test_objectBitSet.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2022, 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 "memory/allocation.hpp"
+#include "utilities/objectBitSet.inline.hpp"
+#include "unittest.hpp"
+
+TEST_VM(ObjectBitSet, empty) {
+  ObjectBitSet<mtTracing> obs;
+  oopDesc obj1;
+  ASSERT_FALSE(obs.is_marked(&obj1));
+}
+
+// NOTE: This is a little weird. NULL is not treated any special: ObjectBitSet will happily
+// allocate a fragement for the memory range starting at 0 and mark the first bit when passing NULL.
+// In the absense of any error handling, I am not sure what would possibly be a reasonable better
+// way to do it, though.
+TEST_VM(ObjectBitSet, null) {
+  ObjectBitSet<mtTracing> obs;
+  ASSERT_FALSE(obs.is_marked((oop)NULL));
+  obs.mark_obj((oop) NULL);
+  ASSERT_TRUE(obs.is_marked((oop)NULL));
+}
+
+TEST_VM(ObjectBitSet, mark_single) {
+  ObjectBitSet<mtTracing> obs;
+  oopDesc obj1;
+  ASSERT_FALSE(obs.is_marked(&obj1));
+  obs.mark_obj(&obj1);
+  ASSERT_TRUE(obs.is_marked(&obj1));
+}
+
+TEST_VM(ObjectBitSet, mark_multi) {
+  ObjectBitSet<mtTracing> obs;
+  oopDesc obj1;
+  oopDesc obj2;
+  ASSERT_FALSE(obs.is_marked(&obj1));
+  ASSERT_FALSE(obs.is_marked(&obj2));
+  obs.mark_obj(&obj1);
+  ASSERT_TRUE(obs.is_marked(&obj1));
+  ASSERT_FALSE(obs.is_marked(&obj2));
+  obs.mark_obj(&obj2);
+  ASSERT_TRUE(obs.is_marked(&obj1));
+  ASSERT_TRUE(obs.is_marked(&obj2));
+}
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,52 @@
+#
+# 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.
+#
+
+
+#
+# Tests require specific UseCompressedClassPointers mode
+#
+gc/arguments/TestCompressedClassFlags.java                                          1234567 generic-all
+
+
+#
+# Tests require specific UseBiasedLocking mode
+#
+runtime/logging/BiasedLockingTest.java                                              8256425 generic-all
+compiler/rtm/cli/TestUseRTMLockingOptionWithBiasedLocking.java                      8256425 generic-x64,generic-i586
+jdk/jfr/event/runtime/TestBiasedLockRevocationEvents.java                           8256425 generic-all
+
+
+#
+# Test library tests do not like non-whitelisted +UCOH flag
+#
+testlibrary_tests/ir_framework/tests/TestCheckedTests.java                          1234567 generic-all
+testlibrary_tests/ir_framework/tests/TestBadFormat.java                             1234567 generic-all
+testlibrary_tests/ir_framework/tests/TestRunTests.java                              1234567 generic-all
+testlibrary_tests/ir_framework/tests/TestScenarios.java                             1234567 generic-all
+testlibrary_tests/ir_framework/tests/TestIRMatching.java                            1234567 generic-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, 2022, 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.
@@ -112,10 +112,11 @@
   compiler/arraycopy/stress
 
 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/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
@@ -43,10 +43,11 @@
 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;
 
 /**
  * Test checks PLAB promotion of different size objects.
@@ -70,11 +71,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 = Platform.is64bit() ? 3266 : 3258;
     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
@@ -39,10 +39,27 @@
  * @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 /
  * @requires vm.gc.Shenandoah
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/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,105 @@
+/*
+ * Copyright (c) 2022, 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
+ */
+
+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;
+    }
+
+    static final long INT_OFFSET;
+    static final int  INT_ARRAY_OFFSET;
+    static final int  LONG_ARRAY_OFFSET;
+    static {
+        WhiteBox WB = WhiteBox.getWhiteBox();
+        if (!Platform.is64bit() || WB.getBooleanVMFlag("UseCompactObjectHeaders")) {
+            INT_OFFSET = 8;
+            INT_ARRAY_OFFSET = 12;
+            LONG_ARRAY_OFFSET = 16;
+        } else if (WB.getBooleanVMFlag("UseCompressedClassPointers")) {
+            INT_OFFSET = 12;
+            INT_ARRAY_OFFSET = 16;
+            LONG_ARRAY_OFFSET = 16;
+        } else {
+            INT_OFFSET = 16;
+            INT_ARRAY_OFFSET = 20;
+            LONG_ARRAY_OFFSET = 24;
+        }
+    }
+
+    static public void main(String[] args) {
+        Unsafe unsafe = Unsafe.getUnsafe();
+        Class c = LIClass.class;
+        Field[] fields = c.getFields();
+        for (int i = 0; i < fields.length; i++) {
+            long offset = unsafe.objectFieldOffset(fields[i]);
+            if (fields[i].getType() == int.class) {
+                Asserts.assertEquals(offset, INT_OFFSET, "Misplaced int field");
+            } else {
+                Asserts.fail("Unexpected field type");
+            }
+        }
+
+        Asserts.assertEquals(unsafe.arrayBaseOffset(boolean[].class), INT_ARRAY_OFFSET,  "Misplaced boolean array base");
+        Asserts.assertEquals(unsafe.arrayBaseOffset(byte[].class),    INT_ARRAY_OFFSET,  "Misplaced byte    array base");
+        Asserts.assertEquals(unsafe.arrayBaseOffset(char[].class),    INT_ARRAY_OFFSET,  "Misplaced char    array base");
+        Asserts.assertEquals(unsafe.arrayBaseOffset(short[].class),   INT_ARRAY_OFFSET,  "Misplaced short   array base");
+        Asserts.assertEquals(unsafe.arrayBaseOffset(int[].class),     INT_ARRAY_OFFSET,  "Misplaced int     array base");
+        Asserts.assertEquals(unsafe.arrayBaseOffset(long[].class),    LONG_ARRAY_OFFSET, "Misplaced long    array base");
+        Asserts.assertEquals(unsafe.arrayBaseOffset(float[].class),   INT_ARRAY_OFFSET,  "Misplaced float   array base");
+        Asserts.assertEquals(unsafe.arrayBaseOffset(double[].class),  LONG_ARRAY_OFFSET, "Misplaced double  array base");
+        boolean narrowOops = System.getProperty("java.vm.compressedOopsMode") != null ||
+                             !Platform.is64bit();
+        int expected_objary_offset = narrowOops ? INT_ARRAY_OFFSET : LONG_ARRAY_OFFSET;
+        Asserts.assertEquals(unsafe.arrayBaseOffset(Object[].class),  expected_objary_offset, "Misplaced object  array base");
+    }
+}
diff a/test/hotspot/jtreg/runtime/FieldLayout/OldLayoutCheck.java b/test/hotspot/jtreg/runtime/FieldLayout/OldLayoutCheck.java
--- a/test/hotspot/jtreg/runtime/FieldLayout/OldLayoutCheck.java
+++ b/test/hotspot/jtreg/runtime/FieldLayout/OldLayoutCheck.java
@@ -23,46 +23,58 @@
 
 /*
  * @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;
-
+    private static final long INT_OFFSET;
+    private static final long LONG_OFFSET;
+    static {
+      WhiteBox WB = WhiteBox.getWhiteBox();
+      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();
         for (int i = 0; i < fields.length; i++) {
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
@@ -45,18 +45,22 @@
          System.out.println("0. Dump with ZGC");
          OutputAnalyzer out = TestCommon
                                   .dump(helloJar,
                                         new String[] {"Hello"},
                                         "-XX:+UseZGC",
+                                        "-XX:+UnlockExperimentalVMOptions",
+                                        "-XX:-UseCompactObjectHeaders",
                                         "-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",
+                         "-XX:+UnlockExperimentalVMOptions",
+                         "-XX:-UseCompactObjectHeaders",
                          "-Xlog:cds",
                          "Hello");
          out.shouldContain(HELLO);
          out.shouldHaveExitValue(0);
 
@@ -64,10 +68,12 @@
          out = TestCommon
                    .exec(helloJar,
                          "-XX:-UseZGC",
                          "-XX:+UseCompressedOops",           // in case turned off by vmoptions
                          "-XX:+UseCompressedClassPointers",  // by jtreg
+                         "-XX:+UnlockExperimentalVMOptions",
+                         "-XX:-UseCompactObjectHeaders",
                          "-Xlog:cds",
                          "Hello");
          out.shouldContain(UNABLE_TO_USE_ARCHIVE);
          out.shouldContain(ERR_MSG);
          out.shouldHaveExitValue(1);
@@ -76,10 +82,12 @@
          out = TestCommon
                    .exec(helloJar,
                          "-XX:+UseSerialGC",
                          "-XX:-UseCompressedOops",
                          "-XX:-UseCompressedClassPointers",
+                         "-XX:+UnlockExperimentalVMOptions",
+                         "-XX:-UseCompactObjectHeaders",
                          "-Xlog:cds",
                          "Hello");
          out.shouldContain(UNABLE_TO_USE_ARCHIVE);
          out.shouldContain(ERR_MSG);
          out.shouldHaveExitValue(1);
@@ -88,10 +96,12 @@
          out = TestCommon
                    .exec(helloJar,
                          "-XX:+UseSerialGC",
                          "-XX:-UseCompressedOops",
                          "-XX:+UseCompressedClassPointers",
+                         "-XX:+UnlockExperimentalVMOptions",
+                         "-XX:-UseCompactObjectHeaders",
                          "-Xlog:cds",
                          "Hello");
          out.shouldContain(HELLO);
          out.shouldHaveExitValue(0);
 
@@ -99,10 +109,12 @@
          out = TestCommon
                    .exec(helloJar,
                          "-XX:+UseSerialGC",
                          "-XX:+UseCompressedOops",
                          "-XX:-UseCompressedClassPointers",
+                         "-XX:+UnlockExperimentalVMOptions",
+                         "-XX:-UseCompactObjectHeaders",
                          "-Xlog:cds",
                          "Hello");
          out.shouldContain(UNABLE_TO_USE_ARCHIVE);
          out.shouldContain(ERR_MSG);
          out.shouldHaveExitValue(1);
@@ -111,10 +123,12 @@
          out = TestCommon
                    .exec(helloJar,
                          "-XX:+UseSerialGC",
                          "-XX:+UseCompressedOops",
                          "-XX:+UseCompressedClassPointers",
+                         "-XX:+UnlockExperimentalVMOptions",
+                         "-XX:-UseCompactObjectHeaders",
                          "-Xlog:cds",
                          "Hello");
          out.shouldContain(UNABLE_TO_USE_ARCHIVE);
          out.shouldContain(ERR_MSG);
          out.shouldHaveExitValue(1);
@@ -124,18 +138,22 @@
                    .dump(helloJar,
                          new String[] {"Hello"},
                          "-XX:+UseSerialGC",
                          "-XX:-UseCompressedOops",
                          "-XX:+UseCompressedClassPointers",
+                         "-XX:+UnlockExperimentalVMOptions",
+                         "-XX:-UseCompactObjectHeaders",
                          "-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",
+                         "-XX:+UnlockExperimentalVMOptions",
+                         "-XX:-UseCompactObjectHeaders",
                          "-Xlog:cds",
                          "Hello");
          out.shouldContain(HELLO);
          out.shouldHaveExitValue(0);
     }
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:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xint -XX:+UnlockExperimentalVMOptions -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/hotspot/jtreg/runtime/lockStack/TestStackWalk.java b/test/hotspot/jtreg/runtime/lockStack/TestStackWalk.java
--- /dev/null
+++ b/test/hotspot/jtreg/runtime/lockStack/TestStackWalk.java
@@ -0,0 +1,92 @@
+/*
+ * 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
+ * @bug 8317262
+ * @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:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:+HandshakeALot -XX:GuaranteedSafepointInterval=1 TestStackWalk
+ */
+
+import jdk.test.lib.Asserts;
+import jdk.test.whitebox.WhiteBox;
+import java.util.concurrent.CountDownLatch;
+
+public class TestStackWalk {
+    static Thread worker1;
+    static Thread worker2;
+    static volatile boolean done;
+    static volatile int counter = 0;
+    static Object lock = new Object();
+
+    public static void main(String... args) throws Exception {
+        worker1 = new Thread(() -> syncedWorker());
+        worker1.start();
+        worker2 = new Thread(() -> syncedWorker());
+        worker2.start();
+        Thread worker3 = new Thread(() -> stackWalker());
+        worker3.start();
+
+        worker1.join();
+        worker2.join();
+        worker3.join();
+    }
+
+    public static void syncedWorker() {
+        synchronized (lock) {
+            while (!done) {
+                counter++;
+            }
+        }
+    }
+
+    public static void stackWalker() {
+        // Suspend workers so the one looping waiting for "done"
+        // doesn't execute the handshake below, increasing the
+        // chances the VMThread will do it.
+        suspendWorkers();
+
+        WhiteBox wb = WhiteBox.getWhiteBox();
+        long end = System.currentTimeMillis() + 20000;
+        while (end > System.currentTimeMillis()) {
+            wb.handshakeWalkStack(worker1, false /* all_threads */);
+            wb.handshakeWalkStack(worker2, false /* all_threads */);
+        }
+
+        resumeWorkers();
+        done = true;
+    }
+
+    static void suspendWorkers() {
+        worker1.suspend();
+        worker2.suspend();
+    }
+
+    static void resumeWorkers() {
+        worker1.resume();
+        worker2.resume();
+    }
+}
diff a/test/hotspot/jtreg/runtime/oom/TestOOM.java b/test/hotspot/jtreg/runtime/oom/TestOOM.java
--- /dev/null
+++ b/test/hotspot/jtreg/runtime/oom/TestOOM.java
@@ -0,0 +1,59 @@
+/*
+ * 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
+ * @bug 8303027
+ * @requires vm.bits == "64"
+ * @summary Test that we're failing with OOME and not with VM crash
+ * @run main/othervm -Xmx1g -XX:-UseCompressedOops TestOOM
+ */
+/*
+ * @test
+ * @bug 8303027
+ * @requires vm.bits == "32"
+ * @summary Test that we're failing with OOME and not with VM crash
+ * @run main/othervm -Xmx1g TestOOM
+ */
+public class TestOOM {
+    public static void main(String[] args) {
+        // Test that it exits with OOME and not with VM crash.
+        try {
+            LinkedInsanity previous = null;
+            while (true) {
+                previous = new LinkedInsanity(previous);
+            }
+        } catch (OutOfMemoryError e) {
+            // That's expected
+        }
+    }
+
+    private static class LinkedInsanity {
+        private final LinkedInsanity previous;
+        private final int[] padding = new int[64000];
+
+        public LinkedInsanity(LinkedInsanity previous) {
+            this.previous = previous;
+        }
+    }
+}
diff a/test/hotspot/jtreg/runtime/whitebox/TestWBDeflateIdleMonitors.java b/test/hotspot/jtreg/runtime/whitebox/TestWBDeflateIdleMonitors.java
--- a/test/hotspot/jtreg/runtime/whitebox/TestWBDeflateIdleMonitors.java
+++ b/test/hotspot/jtreg/runtime/whitebox/TestWBDeflateIdleMonitors.java
@@ -59,16 +59,15 @@
 
     public static class InflateMonitorsTest {
         static WhiteBox wb = WhiteBox.getWhiteBox();
         public static Object obj;
 
-        public static void main(String args[]) {
+        public static void main(String args[]) throws Exception {
             obj = new Object();
             synchronized (obj) {
-                // HotSpot implementation detail: asking for the hash code
-                // when the object is locked causes monitor inflation.
-                if (obj.hashCode() == 0xBAD) System.out.println("!");
+                // The current implementation of notify-wait requires inflation.
+                obj.wait(1);
                 Asserts.assertEQ(wb.isMonitorInflated(obj), true,
                                  "Monitor should be inflated.");
             }
             for (int cnt = 1; cnt <= N_TRIES; cnt++) {
                 System.out.println("Deflation try #" + cnt);
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
@@ -40,58 +40,102 @@
  *                 -Xbootclasspath/a:.
  *                 -XX:CompileCommand=dontinline,*::dontinline_*
  *                 -XX:+WhiteBoxAPI
  *                 -Xbatch
  *                 -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks -XX:+UseBiasedLocking
+ *                 -XX:+UnlockExperimentalVMOptions -XX:LockingMode=1
  * @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:+UseBiasedLocking -XX:-UseOptoBiasInlining
+ *                 -XX:+UnlockExperimentalVMOptions -XX:LockingMode=1
  * @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:+UseBiasedLocking
+ *                 -XX:+UnlockExperimentalVMOptions -XX:LockingMode=1
  * @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:+UseBiasedLocking
+ *                 -XX:+UnlockExperimentalVMOptions -XX:LockingMode=1
  * @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:-UseBiasedLocking
+ *                 -XX:+UnlockExperimentalVMOptions -XX:LockingMode=1
  * @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:-UseBiasedLocking
+ *                 -XX:+UnlockExperimentalVMOptions -XX:LockingMode=1
  * @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:-UseBiasedLocking
+ *                 -XX:+UnlockExperimentalVMOptions -XX:LockingMode=1
+ *
+ * @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
+ * @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
+ * @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
+ * @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
  *
  * @comment Excercise -XX:+DeoptimizeObjectsALot. Mostly to prevent bit-rot because the option is meant to stress object deoptimization
  *          with non-synthetic workloads.
  * @run driver EATests
  *                 -XX:+UnlockDiagnosticVMOptions
@@ -101,11 +145,50 @@
  *                 -XX:+WhiteBoxAPI
  *                 -Xbatch
  *                 -XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks -XX:-UseBiasedLocking
  *                 -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
@@ -231,20 +314,23 @@
         new EAMaterializeLocalVariableUponGetAfterSetIntegerTarget()                        .run();
 
         // Relocking test cases
         new EARelockingSimpleTarget()                                                       .run();
         new EARelockingSimple_2Target()                                                     .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 EARelockingArgEscapeLWLockedInCalleeFrame_3Target()                             .run();
         new EARelockingArgEscapeLWLockedInCalleeFrame_4Target()                             .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();
@@ -351,20 +437,23 @@
         new EAMaterializeLocalVariableUponGetAfterSetInteger()                        .run(this);
 
         // Relocking test cases
         new EARelockingSimple()                                                       .run(this);
         new EARelockingSimple_2()                                                     .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 EARelockingArgEscapeLWLockedInCalleeFrame_3()                             .run(this);
         new EARelockingArgEscapeLWLockedInCalleeFrame_4()                             .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);
@@ -1785,10 +1874,11 @@
  * escape through JVMTI. O is referenced by field l0.
  * This tests a regression of a previous version of the implementation.
  */
 class EARelockingSimple_2 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), XYVAL_NAME, "l1");
@@ -1810,10 +1900,70 @@
     }
 }
 
 /////////////////////////////////////////////////////////////////////////////
 
+// The debugger reads and publishes an object with eliminated locking to an instance field.
+// A 2nd thread in the debuggee finds it there and changes its state using a synchronized method.
+// Without eager relocking the accesses are unsynchronized which can be observed.
+class EARelockingSimpleWithAccessInOtherThread extends EATestCaseBaseDebugger {
+
+    public void runTestCase() throws Exception {
+        BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
+        printStack(bpe.thread());
+        String l1ClassName = EARelockingSimpleWithAccessInOtherThreadTarget.SyncCounter.class.getName();
+        ObjectReference ctr = getLocalRef(bpe.thread().frame(1), l1ClassName, "l1");
+        setField(testCase, "sharedCounter", ctr);
+        terminateEndlessLoop();
+    }
+}
+
+class EARelockingSimpleWithAccessInOtherThreadTarget extends EATestCaseBaseTarget {
+
+    public static class SyncCounter {
+        private int val;
+        public synchronized int inc() { return val++; }
+    }
+
+    public volatile SyncCounter sharedCounter;
+
+    @Override
+    public void setUp() {
+        super.setUp();
+        doLoop = true;
+        Thread t = new Thread() {
+            public void run() {
+                while (doLoop) {
+                    SyncCounter ctr = sharedCounter;
+                    if (ctr != null) {
+                        ctr.inc();
+                    }
+                }
+            }
+        };
+        t.setDaemon(true);
+        t.start();
+    }
+
+    public void dontinline_testMethod() {
+        SyncCounter l1 = new SyncCounter();
+        synchronized (l1) {      // Eliminated locking
+            l1.inc();
+            dontinline_brkpt();  // Debugger publishes l1 to sharedCounter.
+            iResult = l1.inc();  // Changes by the 2nd thread will be observed if l1
+                                 // was not relocked before passing it to the debugger.
+        }
+    }
+
+    @Override
+    public int getExpectedIResult() {
+        return 1;
+    }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
 // Test recursive locking
 class EARelockingRecursiveTarget extends EATestCaseBaseTarget {
 
     @Override
     public void setUp() {
@@ -1928,10 +2078,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
+        new Thread(() -> {
+            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 {
@@ -2107,10 +2345,52 @@
     }
 }
 
 /////////////////////////////////////////////////////////////////////////////
 
+/**
+ * Similar to {@link EARelockingArgEscapeLWLockedInCalleeFrame_2Target}. It does
+ * not use recursive locking and exposed a bug in the lightweight-locking implementation.
+ */
+class EARelockingArgEscapeLWLockedInCalleeFrameNoRecursive 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 EARelockingArgEscapeLWLockedInCalleeFrameNoRecursiveTarget extends EATestCaseBaseTarget {
+
+    @Override
+    public void setUp() {
+        super.setUp();
+        testMethodDepth = 2;
+    }
+
+    public void dontinline_testMethod() {
+        XYVal l1 = new XYVal(1, 1);       // NoEscape, scalar replaced
+        XYVal l2 = new XYVal(4, 2);       // NoEscape, scalar replaced
+        XYVal l3 = new XYVal(5, 3);       // ArgEscape
+        synchronized (l1) {                   // eliminated
+            synchronized (l2) {               // eliminated
+                l3.dontinline_sync_method(this);  // l3 escapes
+            }
+        }
+        iResult = l2.x + l2.y;
+    }
+
+    @Override
+    public int getExpectedIResult() {
+        return 6;
+    }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
 /**
  * Test relocking eliminated (nested) locks of an object on which the
  * target thread currently waits.
  */
 class EARelockingObjectCurrentlyWaitingOn extends EATestCaseBaseDebugger {
@@ -2200,10 +2480,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
@@ -311,10 +311,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 COMPACT_HEADERS = WhiteBox.getWhiteBox().getBooleanVMFlag("UseCompactObjectHeaders");
+    static final int HEADER_SIZE = COMPACT_HEADERS ? 8 : (Platform.is64bit() ? 16 : 8);
+
     final String mode;
 
     public GetObjectSizeIntrinsicsTest(String name, String mode) {
         super(name);
         this.mode = mode;
@@ -370,28 +373,28 @@
     private static long roundUp(long v, long a) {
         return (v + a - 1) / a * a;
     }
 
     private void testSize_newObject() {
-        long expected = roundUp(Platform.is64bit() ? 16 : 8, OBJ_ALIGN);
+        long expected = roundUp(HEADER_SIZE, OBJ_ALIGN);
         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 = roundUp(HEADER_SIZE, OBJ_ALIGN);
         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 = roundUp(HEADER_SIZE, OBJ_ALIGN);
         for (int c = 0; c < ITERS; c++) {
             assertEquals(expected, fInst.getObjectSize(staticO));
         }
     }
 
diff a/test/jtreg-ext/requires/VMProps.java b/test/jtreg-ext/requires/VMProps.java
--- a/test/jtreg-ext/requires/VMProps.java
+++ b/test/jtreg-ext/requires/VMProps.java
@@ -574,11 +574,15 @@
         // check -XX flags
         var ignoredXXFlags = Set.of(
                 // added by run-test framework
                 "MaxRAMPercentage",
                 // added by test environment
-                "CreateCoredumpOnCrash"
+                "CreateCoredumpOnCrash",
+                // experimental features unlocking flag does not affect behavior
+                "UnlockExperimentalVMOptions",
+                // all compact headers settings should run flagless tests
+                "UseCompactObjectHeaders"
         );
         result &= allFlags.stream()
                           .filter(s -> s.startsWith("-XX:"))
                           // map to names:
                               // remove -XX:
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();
 
   private native long getConstantPool0(Class<?> aClass);
   public         long getConstantPool(Class<?> aClass) {
     Objects.requireNonNull(aClass);
