diff a/.jcheck/conf b/.jcheck/conf
--- a/.jcheck/conf
+++ b/.jcheck/conf
@@ -1,35 +1,16 @@
 [general]
-project=jdk
+project=loom
+repository=loom
 jbs=JDK
 version=23
 
 [checks]
-error=author,committer,reviewers,merge,issues,executable,symlink,message,hg-tag,whitespace,problemlists
-
-[repository]
-tags=(?:jdk-(?:[1-9]([0-9]*)(?:\.(?:0|[1-9][0-9]*)){0,4})(?:\+(?:(?:[0-9]+))|(?:-ga)))|(?:jdk[4-9](?:u\d{1,3})?-(?:(?:b\d{2,3})|(?:ga)))|(?:hs\d\d(?:\.\d{1,2})?-b\d\d)
-branches=
+error=author,committer,whitespace
 
 [census]
 version=0
 domain=openjdk.org
 
 [checks "whitespace"]
 files=.*\.cpp|.*\.hpp|.*\.c|.*\.h|.*\.java|.*\.cc|.*\.hh|.*\.m|.*\.mm|.*\.S|.*\.md|.*\.properties|.*\.gmk|.*\.m4|.*\.ac|Makefile
 ignore-tabs=.*\.gmk|Makefile
-
-[checks "merge"]
-message=Merge
-
-[checks "reviewers"]
-reviewers=1
-ignore=duke
-
-[checks "committer"]
-role=committer
-
-[checks "issues"]
-pattern=^([124-8][0-9]{6}): (\S.*)$
-
-[checks "problemlists"]
-dirs=test/jdk|test/langtools|test/lib-test|test/hotspot/jtreg|test/jaxp
diff a/make/test/JtregNativeHotspot.gmk b/make/test/JtregNativeHotspot.gmk
--- a/make/test/JtregNativeHotspot.gmk
+++ b/make/test/JtregNativeHotspot.gmk
@@ -869,10 +869,11 @@
 
 ifeq ($(call isTargetOs, windows), true)
   BUILD_HOTSPOT_JTREG_EXECUTABLES_CFLAGS_exeFPRegs := -MT
   BUILD_HOTSPOT_JTREG_EXCLUDE += exesigtest.c libterminatedThread.c libTestJNI.c libCompleteExit.c libMonitorWithDeadObjectTest.c libTestPsig.c exeGetCreatedJavaVMs.c
   BUILD_HOTSPOT_JTREG_LIBRARIES_JDK_LIBS_libnativeStack := java.base:libjvm
+  BUILD_HOTSPOT_JTREG_LIBRARIES_JDK_LIBS_libVThreadEventTest := java.base:libjvm
 else
   BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libbootclssearch_agent += -lpthread
   BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libsystemclssearch_agent += -lpthread
   BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libgetsysprop001 += -lpthread
   BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libgetsysprop002 += -lpthread
@@ -1507,10 +1508,11 @@
   BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libgetphase002 += -lpthread
   BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libterminatedThread += -lpthread
   BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libCompleteExit += -lpthread
   BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libMonitorWithDeadObjectTest += -lpthread
   BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libnativeStack += -lpthread
+  BUILD_HOTSPOT_JTREG_LIBRARIES_JDK_LIBS_libVThreadEventTest := java.base:libjvm
   BUILD_HOTSPOT_JTREG_EXECUTABLES_LIBS_exeGetCreatedJavaVMs := -lpthread
   BUILD_HOTSPOT_JTREG_EXECUTABLES_JDK_LIBS_exeGetCreatedJavaVMs := java.base:libjvm
 
   BUILD_HOTSPOT_JTREG_EXCLUDE += libNativeException.c
 endif
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
@@ -63,10 +63,11 @@
 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_different_registers(hdr, obj, disp_hdr, temp, rscratch2);
   int null_check_offset = -1;
+  Label count_locking, done;
 
   verify_oop(obj);
 
   // save object being locked into the BasicObjectLock
   str(obj, Address(disp_hdr, BasicObjectLock::obj_offset()));
@@ -80,23 +81,24 @@
     br(Assembler::NE, slow_case);
   }
 
   if (LockingMode == LM_LIGHTWEIGHT) {
     lightweight_lock(obj, hdr, temp, rscratch2, slow_case);
+    b(done);
   } else if (LockingMode == LM_LEGACY) {
-    Label done;
     // Load object header
     ldr(hdr, Address(obj, hdr_offset));
     // and mark it as unlocked
     orr(hdr, hdr, markWord::unlocked_value);
     // save unlocked object header into the displaced header location on the stack
     str(hdr, Address(disp_hdr, 0));
     // test if object header is still the same (i.e. unlocked), and if so, store the
     // 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*/nullptr);
+    // if the object header was the same, we're done
+    cmpxchgptr(hdr, disp_hdr, rscratch2, rscratch1, count_locking, /*fallthough*/nullptr);
     // 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
@@ -116,22 +118,24 @@
     // 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);
+    b(done);
   }
-  increment(Address(rthread, JavaThread::held_monitor_count_offset()));
+  bind(count_locking);
+  inc_held_monitor_count();
+  bind(done);
   return null_check_offset;
 }
 
 
 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_different_registers(hdr, obj, disp_hdr, temp, rscratch2);
-  Label done;
+  Label count_locking, done;
 
   if (LockingMode != LM_LIGHTWEIGHT) {
     // load displaced header
     ldr(hdr, Address(disp_hdr, 0));
     // if the loaded hdr is null we had recursive locking
@@ -143,26 +147,28 @@
   ldr(obj, Address(disp_hdr, BasicObjectLock::obj_offset()));
   verify_oop(obj);
 
   if (LockingMode == LM_LIGHTWEIGHT) {
     lightweight_unlock(obj, hdr, temp, rscratch2, slow_case);
+    b(done);
   } else if (LockingMode == LM_LEGACY) {
     // test if object header is pointing to the displaced header, and if so, restore
     // the displaced header in the object - if the object header is not pointing to
     // the displaced header, get the object header instead
     // 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);
+      cmpxchgptr(disp_hdr, hdr, rscratch1, rscratch2, count_locking, &slow_case);
     } else {
-      cmpxchgptr(disp_hdr, hdr, obj, rscratch2, done, &slow_case);
+      cmpxchgptr(disp_hdr, hdr, obj, rscratch2, count_locking, &slow_case);
     }
     // done
-    bind(done);
+    bind(count_locking);
+    dec_held_monitor_count();
   }
-  decrement(Address(rthread, JavaThread::held_monitor_count_offset()));
+  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) {
diff a/src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp
@@ -168,48 +168,69 @@
 
 class StubFrame: public StackObj {
  private:
   StubAssembler* _sasm;
   bool _return_state;
+  bool _use_pop_on_epilogue;
 
- public:
-  StubFrame(StubAssembler* sasm, const char* name, bool must_gc_arguments, return_state_t return_state=requires_return);
-  void load_argument(int offset_in_words, Register reg);
+  StubFrame(StubAssembler* sasm, const char* name, bool must_gc_arguments,
+            return_state_t return_state, bool use_pop_on_epilogue);
 
+ public:
+  StubFrame(StubAssembler* sasm, const char* name, bool must_gc_arguments, bool use_pop_on_epilogue);
+  StubFrame(StubAssembler* sasm, const char* name, bool must_gc_arguments, return_state_t return_state);
+  StubFrame(StubAssembler* sasm, const char* name, bool must_gc_arguments);
   ~StubFrame();
+
+  void load_argument(int offset_in_words, Register reg);
 };;
 
 void StubAssembler::prologue(const char* name, bool must_gc_arguments) {
   set_info(name, must_gc_arguments);
   enter();
 }
 
-void StubAssembler::epilogue() {
-  leave();
+void StubAssembler::epilogue(bool use_pop) {
+  // use_pop when this frame may have been frozen on one carrier
+  // thread then thawed on another carrier thread, rendering the fp
+  // register invalid. We must restore the previous FP because it is
+  // used as a call-saved scratch register by compiled code.
+  if (use_pop) {
+    ldp(rfp, lr, Address(post(sp, 2 * wordSize)));
+  } else {
+    leave();
+  }
   ret(lr);
 }
 
 #define __ _sasm->
 
-StubFrame::StubFrame(StubAssembler* sasm, const char* name, bool must_gc_arguments, return_state_t return_state) {
-  _sasm = sasm;
-  _return_state = return_state;
+StubFrame::StubFrame(StubAssembler* sasm, const char* name, bool must_gc_arguments,
+                     return_state_t return_state, bool use_pop_on_epilogue)
+  : _sasm(sasm), _return_state(return_state), _use_pop_on_epilogue(use_pop_on_epilogue) {
   __ prologue(name, must_gc_arguments);
 }
 
+StubFrame::StubFrame(StubAssembler* sasm, const char* name, bool must_gc_arguments,
+                     bool use_pop_on_epilogue) :
+  StubFrame(sasm, name, must_gc_arguments, requires_return, use_pop_on_epilogue) {}
+
+StubFrame::StubFrame(StubAssembler* sasm, const char* name, bool must_gc_arguments,
+                     return_state_t return_state) :
+  StubFrame(sasm, name, must_gc_arguments, return_state, /*use_pop_on_epilogue*/false) {}
+
+StubFrame::StubFrame(StubAssembler* sasm, const char* name, bool must_gc_arguments) :
+  StubFrame(sasm, name, must_gc_arguments, requires_return, /*use_pop_on_epilogue*/false) {}
+
 // load parameters that were stored with LIR_Assembler::store_parameter
 // Note: offsets for store_parameter and load_argument must match
 void StubFrame::load_argument(int offset_in_words, Register reg) {
   __ load_parameter(offset_in_words, reg);
 }
 
 StubFrame::~StubFrame() {
-  if (_return_state == requires_return) {
-    __ epilogue();
-  } else {
-    __ should_not_reach_here();
-  }
+  __ epilogue(_use_pop_on_epilogue);
 }
 
 #undef __
 
 
@@ -250,11 +271,11 @@
   int frame_size_in_slots = frame_size_in_bytes / sizeof(jint);
   OopMap* oop_map = new OopMap(frame_size_in_slots, 0);
 
   for (int i = 0; i < FrameMap::nof_cpu_regs; i++) {
     Register r = as_Register(i);
-    if (i <= 18 && i != rscratch1->encoding() && i != rscratch2->encoding()) {
+    if (r == rthread || (i <= 18 && i != rscratch1->encoding() && i != rscratch2->encoding())) {
       int sp_offset = cpu_reg_save_offsets[i];
       oop_map->set_callee_saved(VMRegImpl::stack2reg(sp_offset),
                                 r->as_VMReg());
     }
   }
@@ -335,10 +356,19 @@
     cpu_reg_save_offsets[i] = sp_offset;
     sp_offset += 2;   // SP offsets are in halfwords
   }
 }
 
+// return: offset in 64-bit words.
+uint Runtime1::runtime_blob_current_thread_offset(frame f) {
+  CodeBlob* cb = f.cb();
+  assert(cb == Runtime1::blob_for(Runtime1::monitorenter_id) ||
+         cb == Runtime1::blob_for(Runtime1::monitorenter_nofpu_id), "must be");
+  assert(cb != nullptr && cb->is_runtime_stub(), "invalid frame");
+  int offset = cpu_reg_save_offsets[rthread->encoding()];
+  return offset / 2;   // SP offsets are in halfwords
+}
 
 // target: the entry point of the method that creates and posts the exception oop
 // has_argument: true if the exception needs arguments (passed in rscratch1 and rscratch2)
 
 OopMapSet* Runtime1::generate_exception_throw(StubAssembler* sasm, address target, bool has_argument) {
@@ -860,11 +890,11 @@
     case monitorenter_nofpu_id:
       save_fpu_registers = false;
       // fall through
     case monitorenter_id:
       {
-        StubFrame f(sasm, "monitorenter", dont_gc_arguments);
+        StubFrame f(sasm, "monitorenter", dont_gc_arguments, /*use_pop_on_epilogue*/true);
         OopMap* map = save_live_registers(sasm, save_fpu_registers);
 
         // Called with store_parameter and not C abi
 
         f.load_argument(1, r0); // r0,: object
diff a/src/hotspot/cpu/aarch64/c2_CodeStubs_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_CodeStubs_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/c2_CodeStubs_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/c2_CodeStubs_aarch64.cpp
@@ -66,29 +66,34 @@
 
 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;
+  return 52;
 }
 
 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()));
-
-  // 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()));
+  __ ldr(t, Address(rthread, JavaThread::lock_id_offset()));
+  __ str(t, Address(mon, ObjectMonitor::owner_offset()));
 
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    // 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()));
+  } else {
+    __ str(zr, Address(mon, ObjectMonitor::stack_locker_offset()));
+    __ dec_held_monitor_count();
+  }
   __ 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
@@ -50,33 +50,32 @@
                                   Register tmp2Reg, Register tmp3Reg) {
   Register oop = objectReg;
   Register box = boxReg;
   Register disp_hdr = tmpReg;
   Register tmp = tmp2Reg;
-  Label cont;
   Label object_has_monitor;
   Label count, no_count;
 
   assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking should use fast_lock_lightweight");
-  assert_different_registers(oop, box, tmp, disp_hdr);
+  assert_different_registers(oop, box, tmp, disp_hdr, rscratch1);
 
   // 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);
+    br(Assembler::NE, no_count);
   }
 
   // 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);
+    b(no_count);
   } else {
     assert(LockingMode == LM_LEGACY, "must be");
     // Set tmp to be (markWord of object | UNLOCK_VALUE).
     orr(tmp, disp_hdr, markWord::unlocked_value);
 
@@ -86,11 +85,11 @@
     // 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);
+    br(Assembler::EQ, count);
 
     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
@@ -102,80 +101,80 @@
     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);
+    b(no_count);
   }
 
   // Handle existing monitor.
   bind(object_has_monitor);
 
   // The object's monitor m is unlocked iff m->owner == nullptr,
-  // otherwise m->owner may contain a thread or a stack address.
+  // otherwise m->owner may contain a thread id, a stack address for LM_LEGACY,
+  // or the ANONYMOUS_OWNER constant for LM_LIGHTWEIGHT.
   //
   // Try to CAS m->owner from null to current thread.
+  ldr(rscratch2, Address(rthread, JavaThread::lock_id_offset()));
   add(tmp, disp_hdr, (in_bytes(ObjectMonitor::owner_offset())-markWord::monitor_value));
-  cmpxchg(tmp, zr, rthread, Assembler::xword, /*acquire*/ true,
+  cmpxchg(tmp, zr, rscratch2, Assembler::xword, /*acquire*/ true,
           /*release*/ true, /*weak*/ false, tmp3Reg); // 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
+  br(Assembler::EQ, no_count); // CAS success means locking succeeded
 
-  cmp(tmp3Reg, rthread);
-  br(Assembler::NE, cont); // Check for recursive locking
+  cmp(tmp3Reg, rscratch2);
+  br(Assembler::NE, no_count); // Check for recursive locking
 
   // Recursive lock case
   increment(Address(disp_hdr, in_bytes(ObjectMonitor::recursions_offset()) - 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
-  br(Assembler::NE, no_count);
+  b(no_count);
 
   bind(count);
-  increment(Address(rthread, JavaThread::held_monitor_count_offset()));
+  inc_held_monitor_count();
 
   bind(no_count);
+  // 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;
-  Label count, no_count;
+  Label no_count;
 
   assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking should use fast_unlock_lightweight");
   assert_different_registers(oop, box, tmp, disp_hdr);
 
   if (LockingMode == LM_LEGACY) {
     // Find the lock address and load the displaced header from the stack.
     ldr(disp_hdr, Address(box, BasicLock::displaced_header_offset_in_bytes()));
 
     // If the displaced header is 0, we have a recursive unlock.
     cmp(disp_hdr, zr);
-    br(Assembler::EQ, cont);
+    br(Assembler::EQ, no_count);
   }
 
   // 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);
+    b(no_count);
   } 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.
@@ -190,38 +189,50 @@
   // Handle existing monitor.
   bind(object_has_monitor);
   STATIC_ASSERT(markWord::monitor_value <= INT_MAX);
   add(tmp, tmp, -(int)markWord::monitor_value); // monitor
 
+  // If the owner is anonymous, we need to fix it -- in an outline stub.
+  Register tmp2 = disp_hdr;
+  ldr(tmp2, Address(tmp, ObjectMonitor::owner_offset()));
+  // We cannot use tbnz here, the target might be too far away and cannot
+  // be encoded.
+  mov(rscratch1, (uint64_t)ObjectMonitor::ANONYMOUS_OWNER);
+  cmp(tmp2, rscratch1);
+  C2HandleAnonOMOwnerStub* stub = new (Compile::current()->comp_arena()) C2HandleAnonOMOwnerStub(tmp, tmp2);
+  Compile::current()->output()->add_stub(stub);
+  br(Assembler::EQ, stub->entry());
+  bind(stub->continuation());
+
   ldr(disp_hdr, Address(tmp, ObjectMonitor::recursions_offset()));
 
   Label notRecursive;
   cbz(disp_hdr, notRecursive);
 
   // Recursive lock
   sub(disp_hdr, disp_hdr, 1u);
   str(disp_hdr, Address(tmp, ObjectMonitor::recursions_offset()));
   cmp(disp_hdr, disp_hdr); // Sets flags for result
-  b(cont);
+  b(no_count);
 
   bind(notRecursive);
   ldr(rscratch1, Address(tmp, ObjectMonitor::EntryList_offset()));
   ldr(disp_hdr, Address(tmp, ObjectMonitor::cxq_offset()));
   orr(rscratch1, rscratch1, disp_hdr); // Will be 0 if both are 0.
   cmp(rscratch1, zr); // Sets flags for result
-  cbnz(rscratch1, cont);
+  cbnz(rscratch1, no_count);
   // need a release store here
   lea(tmp, Address(tmp, ObjectMonitor::owner_offset()));
   stlr(zr, tmp); // set unowned
+  b(no_count);
 
   bind(cont);
   // flag == EQ indicates success
   // flag == NE indicates failure
   br(Assembler::NE, no_count);
 
-  bind(count);
-  decrement(Address(rthread, JavaThread::held_monitor_count_offset()));
+  dec_held_monitor_count();
 
   bind(no_count);
 }
 
 void C2_MacroAssembler::fast_lock_lightweight(Register obj, Register t1,
@@ -296,25 +307,25 @@
     const Register t3_owner = t3;
 
     // Compute owner address.
     lea(t2_owner_addr, Address(t1_tagged_monitor, (in_bytes(ObjectMonitor::owner_offset()) - monitor_tag)));
 
-    // CAS owner (null => current thread).
-    cmpxchg(t2_owner_addr, zr, rthread, Assembler::xword, /*acquire*/ true,
+    // CAS owner (null => current thread id).
+    ldr(rscratch2, Address(rthread, JavaThread::lock_id_offset()));
+    cmpxchg(t2_owner_addr, zr, rscratch2, Assembler::xword, /*acquire*/ true,
             /*release*/ false, /*weak*/ false, t3_owner);
     br(Assembler::EQ, locked);
 
     // Check if recursive.
-    cmp(t3_owner, rthread);
+    cmp(t3_owner, rscratch2);
     br(Assembler::NE, slow_path);
 
     // Recursive.
     increment(Address(t1_tagged_monitor, in_bytes(ObjectMonitor::recursions_offset()) - monitor_tag), 1);
   }
 
   bind(locked);
-  increment(Address(rthread, JavaThread::held_monitor_count_offset()));
 
 #ifdef ASSERT
   // Check that locked label is reached with Flags == EQ.
   Label flag_correct;
   br(Assembler::EQ, flag_correct);
@@ -448,22 +459,22 @@
     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));
+    // Write the thread id to the owner field so the runtime knows the owner.
+    ldr(t3_t, Address(rthread, JavaThread::lock_id_offset()));
+    str(t3_t, Address(t2_owner_addr));
     b(slow_path);
 
     bind(release);
     // Set owner to null.
     // Release to satisfy the JMM
     stlr(zr, t2_owner_addr);
   }
 
   bind(unlocked);
-  decrement(Address(rthread, JavaThread::held_monitor_count_offset()));
 
 #ifdef ASSERT
   // Check that unlocked label is reached with Flags == EQ.
   Label flag_correct;
   br(Assembler::EQ, flag_correct);
diff a/src/hotspot/cpu/aarch64/continuationFreezeThaw_aarch64.inline.hpp b/src/hotspot/cpu/aarch64/continuationFreezeThaw_aarch64.inline.hpp
--- a/src/hotspot/cpu/aarch64/continuationFreezeThaw_aarch64.inline.hpp
+++ b/src/hotspot/cpu/aarch64/continuationFreezeThaw_aarch64.inline.hpp
@@ -127,10 +127,16 @@
   if (real_unextended_sp != nullptr) {
     f.set_unextended_sp(real_unextended_sp); // can be null at a safepoint
   }
 }
 
+inline void FreezeBase::prepare_freeze_interpreted_top_frame(const frame& f) {
+  assert(*f.addr_at(frame::interpreter_frame_last_sp_offset) == 0, "should be null for top frame");
+  intptr_t* lspp = f.addr_at(frame::interpreter_frame_last_sp_offset);
+  *lspp = f.unextended_sp() - f.fp();
+}
+
 inline void FreezeBase::relativize_interpreted_frame_metadata(const frame& f, const frame& hf) {
   assert(hf.fp() == hf.unextended_sp() + (f.fp() - f.unextended_sp()), "");
   assert((f.at(frame::interpreter_frame_last_sp_offset) != 0)
     || (f.unextended_sp() == f.sp()), "");
   assert(f.fp() > (intptr_t*)f.at_relative(frame::interpreter_frame_initial_sp_offset), "");
@@ -233,11 +239,11 @@
     return f;
   } else {
     int fsize = FKind::size(hf);
     intptr_t* frame_sp = caller.unextended_sp() - fsize;
     if (bottom || caller.is_interpreted_frame()) {
-      int argsize = hf.compiled_frame_stack_argsize();
+      int argsize = FKind::stack_argsize(hf);
 
       fsize += argsize;
       frame_sp   -= argsize;
       caller.set_sp(caller.sp() - argsize);
       assert(caller.sp() == frame_sp + (fsize-argsize), "");
@@ -251,11 +257,11 @@
     if (PreserveFramePointer) {
       // we need to recreate a "real" frame pointer, pointing into the stack
       fp = frame_sp + FKind::size(hf) - frame::sender_sp_offset;
     } else {
       fp = FKind::stub
-        ? frame_sp + fsize - frame::sender_sp_offset // on AArch64, this value is used for the safepoint stub
+        ? frame_sp + fsize - frame::sender_sp_offset // fp always points to the address below the pushed return pc. We need correct address.
         : *(intptr_t**)(hf.sp() - frame::sender_sp_offset); // we need to re-read fp because it may be an oop and we might have fixed the frame.
     }
     return frame(frame_sp, frame_sp, fp, hf.pc(), hf.cb(), hf.oop_map(), false); // TODO PERF : this computes deopt state; is it necessary?
   }
 }
@@ -275,10 +281,68 @@
 
 inline void ThawBase::patch_pd(frame& f, const frame& caller) {
   patch_callee_link(caller, caller.fp());
 }
 
+inline void ThawBase::patch_pd(frame& f, intptr_t* caller_sp) {
+  Unimplemented();
+}
+
+inline intptr_t* ThawBase::push_preempt_rerun_adapter(frame top, bool is_interpreted_frame) {
+  intptr_t* sp = top.sp();
+  CodeBlob* cb = top.cb();
+  if (!is_interpreted_frame && cb->frame_size() == 2) {
+    // C2 runtime stub case. For aarch64 the real size of the c2 runtime stub is 2 words bigger
+    // than what we think, i.e. size is 4. This is because the _last_Java_sp is not set to the
+    // sp right before making the call to the VM, but rather it is artificially set 2 words above
+    // this real sp so that we can store the return address at last_Java_sp[-1], and keep this
+    // property where we can retrieve the last_Java_pc from the last_Java_sp. But that means that
+    // once we return to the runtime stub, the code will adjust sp according to this real size.
+    // So we must adjust the frame size back here. We just copy lr/rfp again. These 2 top words
+    // will be the ones popped in generate_cont_preempt_rerun_compiler_adapter(). The other 2 words
+    // will just be discarded once back in the runtime stub (add sp, sp, #0x10).
+    sp -= 2;
+    sp[-2] = sp[0];
+    sp[-1] = sp[1];
+  }
+
+  intptr_t* fp = sp - frame::sender_sp_offset;
+  address pc = is_interpreted_frame ? Interpreter::cont_preempt_rerun_interpreter_adapter()
+                                    : StubRoutines::cont_preempt_rerun_compiler_adapter();
+
+  sp -= frame::metadata_words;
+  *(address*)(sp - frame::sender_sp_ret_address_offset()) = pc;
+  *(intptr_t**)(sp - frame::sender_sp_offset) = fp;
+
+  log_develop_trace(continuations, preempt)("push_preempt_rerun_%s_adapter() initial sp: " INTPTR_FORMAT " final sp: " INTPTR_FORMAT " fp: " INTPTR_FORMAT,
+    is_interpreted_frame ? "interpreter" : "safepointblob", p2i(sp + frame::metadata_words), p2i(sp), p2i(fp));
+  return sp;
+}
+
+inline intptr_t* ThawBase::push_preempt_monitorenter_redo(stackChunkOop chunk) {
+
+  // fprintf(stderr, "push_preempt_monitorenter_redo\n");
+  frame enterSpecial = new_entry_frame();
+  intptr_t* sp = enterSpecial.sp();
+
+  // First push the return barrier frame
+  sp -= frame::metadata_words;
+  sp[1] = (intptr_t)StubRoutines::cont_returnBarrier();
+  sp[0] = (intptr_t)enterSpecial.fp();
+
+  // Now push the ObjectMonitor*
+  sp -= frame::metadata_words;
+  sp[1] = (intptr_t)chunk->objectMonitor(); // alignment
+  sp[0] = (intptr_t)chunk->objectMonitor();
+
+  // Finally arrange to return to the monitorenter_redo stub
+  sp[-1] = (intptr_t)StubRoutines::cont_preempt_monitorenter_redo();
+  sp[-2] = (intptr_t)enterSpecial.fp();
+  log_develop_trace(continuations, preempt)("push_preempt_monitorenter_redo initial sp: " INTPTR_FORMAT " final sp: " INTPTR_FORMAT, p2i(sp + 2 * frame::metadata_words), p2i(sp));
+  return sp;
+}
+
 inline void ThawBase::derelativize_interpreted_frame_metadata(const frame& hf, const frame& f) {
   // Make sure that last_sp is kept relativized.
   assert((intptr_t*)f.at_relative(frame::interpreter_frame_last_sp_offset) == f.unextended_sp(), "");
 
   // Make sure that monitor_block_top is still relativized.
diff a/src/hotspot/cpu/aarch64/continuationHelper_aarch64.inline.hpp b/src/hotspot/cpu/aarch64/continuationHelper_aarch64.inline.hpp
--- a/src/hotspot/cpu/aarch64/continuationHelper_aarch64.inline.hpp
+++ b/src/hotspot/cpu/aarch64/continuationHelper_aarch64.inline.hpp
@@ -38,10 +38,29 @@
   return FKind::interpreted
             ? (intptr_t**)(f.fp() + frame::link_offset)
             : (intptr_t**)(f.unextended_sp() + f.cb()->frame_size() - frame::sender_sp_offset);
 }
 
+static inline void patch_return_pc_with_preempt_stub(frame& f) {
+  // Unlike x86 we don't know where in the callee frame the return pc is
+  // saved so we can't patch the return from the VM call back to Java. If
+  // the target is coming from compiled code we will patch the return from
+  // the safepoint handler blob back to the compiled method instead. If
+  // it's coming from the interpreter, the target will check for preemption
+  // once it returns to the interpreter and will manually jump to the
+  // preempt stub.
+  if (!f.is_interpreted_frame()) {
+    assert(f.is_runtime_frame(), "invariant");
+    intptr_t* caller_sp = f.sp() + f.cb()->frame_size();
+    caller_sp[-1] = (intptr_t)StubRoutines::cont_preempt_stub();
+  } else {
+    JavaThread *thread = JavaThread::current();
+    thread->set_preempt_alternate_return(StubRoutines::cont_preempt_stub());
+    thread->set_preempt_alternate_return_sp((address)f.sp());
+  }
+}
+
 inline int ContinuationHelper::frame_align_words(int size) {
 #ifdef _LP64
   return size & 1;
 #else
   return 0;
@@ -81,16 +100,16 @@
 
 inline void ContinuationHelper::set_anchor_to_entry_pd(JavaFrameAnchor* anchor, ContinuationEntry* entry) {
   anchor->set_last_Java_fp(entry->entry_fp());
 }
 
-#ifdef ASSERT
 inline void ContinuationHelper::set_anchor_pd(JavaFrameAnchor* anchor, intptr_t* sp) {
   intptr_t* fp = *(intptr_t**)(sp - frame::sender_sp_offset);
   anchor->set_last_Java_fp(fp);
 }
 
+#ifdef ASSERT
 inline bool ContinuationHelper::Frame::assert_frame_laid_out(frame f) {
   intptr_t* sp = f.sp();
   address pc = ContinuationHelper::return_address_at(
                  sp - frame::sender_sp_ret_address_offset());
   intptr_t* fp = *(intptr_t**)(sp - frame::sender_sp_offset);
diff a/src/hotspot/cpu/aarch64/frame_aarch64.cpp b/src/hotspot/cpu/aarch64/frame_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/frame_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/frame_aarch64.cpp
@@ -701,11 +701,11 @@
   // unwind them; for everything else we assume that the native frame
   // pointer chain is intact.
   frame this_frame((intptr_t*)sp, (intptr_t*)fp, (address)pc);
   if (this_frame.is_compiled_frame() ||
       this_frame.is_interpreted_frame()) {
-    frame sender = this_frame.sender(reg_map);
+    frame sender = this_frame.sender_raw(reg_map);
     nextfp = (uintptr_t)sender.fp();
     nextpc = (uintptr_t)sender.pc();
     nextsp = (uintptr_t)sender.unextended_sp();
   } else {
     nextfp = p[frame::link_offset];
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
@@ -695,11 +695,11 @@
       br(Assembler::NE, slow_case);
     }
 
     if (LockingMode == LM_LIGHTWEIGHT) {
       lightweight_lock(obj_reg, tmp, tmp2, tmp3, slow_case);
-      b(count);
+      b(done);
     } else if (LockingMode == LM_LEGACY) {
       // Load (object->mark() | 1) into swap_reg
       ldr(rscratch1, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
       orr(swap_reg, rscratch1, 1);
 
@@ -745,29 +745,32 @@
       sub(swap_reg, swap_reg, rscratch1);
       ands(swap_reg, swap_reg, (uint64_t)(7 - (int)os::vm_page_size()));
 
       // Save the test result, for recursive case, the result is zero
       str(swap_reg, Address(lock_reg, mark_offset));
-      br(Assembler::EQ, count);
+      br(Assembler::NE, slow_case);
+      b(done);
     }
+
+    bind(count);
+    inc_held_monitor_count();
+    b(done);
+
     bind(slow_case);
 
+    //    push_cont_fastpath();
     // Call the runtime routine for slow case
     if (LockingMode == LM_LIGHTWEIGHT) {
       call_VM(noreg,
               CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter_obj),
               obj_reg);
     } else {
       call_VM(noreg,
               CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
               lock_reg);
     }
-    b(done);
-
-    bind(count);
-    increment(Address(rthread, JavaThread::held_monitor_count_offset()));
-
+    //    pop_cont_fastpath();
     bind(done);
   }
 }
 
 
@@ -808,34 +811,34 @@
     ldr(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset()));
 
     // Free entry
     str(zr, Address(lock_reg, BasicObjectLock::obj_offset()));
 
+    Label slow_case;
     if (LockingMode == LM_LIGHTWEIGHT) {
-      Label slow_case;
       lightweight_unlock(obj_reg, header_reg, swap_reg, tmp_reg, slow_case);
-      b(count);
-      bind(slow_case);
+      b(done);
     } else if (LockingMode == LM_LEGACY) {
       // Load the old header from BasicLock structure
       ldr(header_reg, Address(swap_reg,
                               BasicLock::displaced_header_offset_in_bytes()));
 
       // Test for recursion
-      cbz(header_reg, count);
+      cbz(header_reg, done);
 
       // Atomic swap back the old header
-      cmpxchg_obj_header(swap_reg, header_reg, obj_reg, rscratch1, count, /*fallthrough*/nullptr);
+      cmpxchg_obj_header(swap_reg, header_reg, obj_reg, rscratch1, count, &slow_case);
     }
-    // Call the runtime routine for slow case.
-    str(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset())); // restore obj
-    call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit), lock_reg);
-    b(done);
 
     bind(count);
-    decrement(Address(rthread, JavaThread::held_monitor_count_offset()));
+    dec_held_monitor_count();
+    b(done);
 
+    bind(slow_case);
+    // Call the runtime routine for slow case.
+    str(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset())); // restore obj
+    call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit), lock_reg);
     bind(done);
     restore_bcp();
   }
 }
 
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
@@ -37,10 +37,11 @@
 #include "gc/shared/cardTable.hpp"
 #include "gc/shared/collectedHeap.hpp"
 #include "gc/shared/tlab_globals.hpp"
 #include "interpreter/bytecodeHistogram.hpp"
 #include "interpreter/interpreter.hpp"
+#include "interpreter/interpreterRuntime.hpp"
 #include "jvm.h"
 #include "memory/resourceArea.hpp"
 #include "memory/universe.hpp"
 #include "nativeInst_aarch64.hpp"
 #include "oops/accessDecorators.hpp"
@@ -1953,19 +1954,34 @@
 }
 
 void MacroAssembler::call_VM_leaf_base(address entry_point,
                                        int number_of_arguments,
                                        Label *retaddr) {
-  Label E, L;
+  Label not_preempted;
 
   stp(rscratch1, rmethod, Address(pre(sp, -2 * wordSize)));
+  str(zr, Address(rthread, JavaThread::preempt_alternate_return_offset()));
 
   mov(rscratch1, entry_point);
   blr(rscratch1);
   if (retaddr)
     bind(*retaddr);
 
+  if (entry_point == CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter) || 
+      entry_point == CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter_obj)) {
+    ldr(rscratch1, Address(rthread, JavaThread::preempt_alternate_return_offset()));
+    ldr(rscratch2, Address(rthread, in_bytes(JavaThread::preempt_alternate_return_offset()) + wordSize));
+    cbz(rscratch1, not_preempted);
+    mov(r4, sp); // r4 is clobbered by VM calls, so free here
+    cmp(rscratch2, r4);
+    br(LT, not_preempted);
+    str(zr, Address(rthread, JavaThread::preempt_alternate_return_offset()));
+    str(zr, Address(rthread, in_bytes(JavaThread::preempt_alternate_return_offset()) + wordSize));
+    br(rscratch1);
+  }
+
+  bind(not_preempted);
   ldp(rscratch1, rmethod, Address(post(sp, 2 * wordSize)));
 }
 
 void MacroAssembler::call_VM_leaf(address entry_point, int number_of_arguments) {
   call_VM_leaf_base(entry_point, number_of_arguments);
@@ -5304,10 +5320,42 @@
                                    Label& slow_case) {
   BarrierSetAssembler *bs = BarrierSet::barrier_set()->barrier_set_assembler();
   bs->tlab_allocate(this, obj, var_size_in_bytes, con_size_in_bytes, t1, t2, slow_case);
 }
 
+void MacroAssembler::inc_held_monitor_count() {
+  Address dst = Address(rthread, JavaThread::held_monitor_count_offset());
+#ifdef ASSERT
+  ldr(rscratch2, dst);
+  increment(rscratch2);
+  str(rscratch2, dst);
+  Label ok;
+  tbz(rscratch2, 63, ok);
+  STOP("assert(held monitor count underflow)");
+  should_not_reach_here();
+  bind(ok);
+#else
+  increment(dst);
+#endif
+}
+
+void MacroAssembler::dec_held_monitor_count() {
+  Address dst = Address(rthread, JavaThread::held_monitor_count_offset());
+#ifdef ASSERT
+  ldr(rscratch2, dst);
+  decrement(rscratch2);
+  str(rscratch2, dst);
+  Label ok;
+  tbz(rscratch2, 63, ok);
+  STOP("assert(held monitor count underflow)");
+  should_not_reach_here();
+  bind(ok);
+#else
+  decrement(dst);
+#endif
+}
+
 void MacroAssembler::verify_tlab() {
 #ifdef ASSERT
   if (UseTLAB && VerifyOops) {
     Label next, ok;
 
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
@@ -935,12 +935,15 @@
   void push_CPU_state(bool save_vectors = false, bool use_sve = false,
                       int sve_vector_size_in_bytes = 0, int total_predicate_in_bytes = 0);
   void pop_CPU_state(bool restore_vectors = false, bool use_sve = false,
                      int sve_vector_size_in_bytes = 0, int total_predicate_in_bytes = 0);
 
-  void push_cont_fastpath(Register java_thread);
-  void pop_cont_fastpath(Register java_thread);
+  void push_cont_fastpath(Register java_thread = rthread);
+  void pop_cont_fastpath(Register java_thread = rthread);
+
+  void inc_held_monitor_count();
+  void dec_held_monitor_count();
 
   // Round up to a power of two
   void round_to(Register reg, int modulus);
 
   // java.lang.Math::round intrinsics
@@ -1086,10 +1089,11 @@
   // Restore cpu control state after JNI call
   void restore_cpu_control_state_after_jni(Register tmp1, Register tmp2);
 
   // prints msg, dumps registers and stops execution
   void stop(const char* msg);
+  void trace(const char* msg);
 
   static void debug64(char* msg, int64_t pc, int64_t regs[]);
 
   void untested()                                { stop("untested"); }
 
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
@@ -1177,10 +1177,11 @@
 
   __ b(exit);
 
   __ bind(call_thaw);
 
+  ContinuationEntry::_thaw_call_pc_offset = __ pc() - start;
   __ rt_call(CAST_FROM_FN_PTR(address, StubRoutines::cont_thaw()));
   oop_maps->add_gc_map(__ pc() - start, map->deep_copy());
   ContinuationEntry::_return_pc_offset = __ pc() - start;
   __ post_call_nop();
 
@@ -1279,10 +1280,14 @@
 
     OopMap* map = new OopMap(framesize, 1);
     oop_maps->add_gc_map(the_pc - start, map);
 }
 
+void SharedRuntime::continuation_enter_cleanup(MacroAssembler* masm) {
+  ::continuation_enter_cleanup(masm);
+}
+
 static void gen_special_dispatch(MacroAssembler* masm,
                                  const methodHandle& method,
                                  const BasicType* sig_bt,
                                  const VMRegPair* regs) {
   verify_oop_args(masm, method, sig_bt, regs);
@@ -1830,16 +1835,18 @@
       __ ands(swap_reg, swap_reg, 3 - (int)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);
+      __ b(lock_done);
     } else {
       assert(LockingMode == LM_LIGHTWEIGHT, "must be");
       __ lightweight_lock(obj_reg, swap_reg, tmp, lock_tmp, slow_path_lock);
+      __ b(lock_done);
     }
     __ bind(count);
-    __ increment(Address(rthread, JavaThread::held_monitor_count_offset()));
+    __ inc_held_monitor_count();
 
     // Slow path will re-enter here
     __ bind(lock_done);
   }
 
@@ -1948,11 +1955,10 @@
 
     if (LockingMode == LM_LEGACY) {
       // Simple recursive lock?
       __ ldr(rscratch1, Address(sp, lock_slot_offset * VMRegImpl::stack_slot_size));
       __ cbnz(rscratch1, not_recursive);
-      __ decrement(Address(rthread, JavaThread::held_monitor_count_offset()));
       __ b(done);
     }
 
     __ bind(not_recursive);
 
@@ -1971,15 +1977,14 @@
 
       // Atomic swap old header if oop still contains the stack lock
       Label count;
       __ cmpxchg_obj_header(r0, old_hdr, obj_reg, rscratch1, count, &slow_path_unlock);
       __ bind(count);
-      __ decrement(Address(rthread, JavaThread::held_monitor_count_offset()));
+      __ dec_held_monitor_count();
     } else {
       assert(LockingMode == LM_LIGHTWEIGHT, "");
       __ lightweight_unlock(obj_reg, old_hdr, swap_reg, lock_tmp, slow_path_unlock);
-      __ decrement(Address(rthread, JavaThread::held_monitor_count_offset()));
     }
 
     // slow path re-enters here
     __ bind(unlock_done);
     if (ret_type != T_FLOAT && ret_type != T_DOUBLE && ret_type != T_VOID) {
@@ -2584,10 +2589,14 @@
 
 uint SharedRuntime::out_preserve_stack_slots() {
   return 0;
 }
 
+VMReg SharedRuntime::thread_register() {
+  return rthread->as_VMReg();
+}
+
 #ifdef COMPILER2
 //------------------------------generate_uncommon_trap_blob--------------------
 void SharedRuntime::generate_uncommon_trap_blob() {
   // Allocate space for the code
   ResourceMark rm;
diff a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp
@@ -7065,11 +7065,13 @@
     }
 
     // If we want, we can templatize thaw by kind, and have three different entries
     __ movw(c_rarg1, (uint32_t)kind);
 
+    __ set_last_Java_frame(sp, rfp, rscratch1, rscratch2);
     __ call_VM_leaf(Continuation::thaw_entry(), rthread, c_rarg1);
+    __ reset_last_Java_frame(true);
     __ mov(rscratch2, r0); // r0 is the sp of the yielding frame
 
     if (return_barrier) {
       // restore return value (no safepoint in the call to thaw, so even an oop return value should be OK)
       __ ldp(rscratch1, r0, Address(__ post(sp, 2 * wordSize)));
@@ -7142,10 +7144,108 @@
     generate_cont_thaw(Continuation::thaw_return_barrier_exception);
 
     return start;
   }
 
+  address generate_cont_preempt_stub() {
+    if (!Continuations::enabled()) return nullptr;
+    StubCodeMark mark(this, "StubRoutines","Continuation preempt stub");
+    address start = __ pc();
+
+    __ reset_last_Java_frame(true);
+
+    // reset the flag
+    __ strb(zr, Address(rthread, JavaThread::preempting_offset()));
+
+    // Set sp to enterSpecial frame and then remove it from the stack
+    __ ldr(rscratch2, Address(rthread, JavaThread::cont_entry_offset()));
+    __ mov(sp, rscratch2);
+
+    Label preemption_cancelled;
+    // FIXME: Whose responsibility is it to clear this flag?
+    __ ldrb(rscratch1, Address(rthread, JavaThread::preemption_cancelled_offset()));
+    __ cbnz(rscratch1, preemption_cancelled);
+
+    //__ trace("Remove enterSpecial frame from the stack and return to Continuation.run()");
+    // Remove enterSpecial frame from the stack and return to Continuation.run()
+    SharedRuntime::continuation_enter_cleanup(_masm);
+    __ leave();
+    __ ret(lr);
+
+    __ bind(preemption_cancelled);
+    //__ trace("preemption_cancelled");
+    __ lea(rfp, Address(sp, checked_cast<int32_t>(ContinuationEntry::size())));
+    __ lea(rscratch1, ExternalAddress((address)&ContinuationEntry::_thaw_call_pc));
+    __ ldr(rscratch1, Address(rscratch1));
+    __ br(rscratch1);
+
+    return start;
+  }
+
+  address generate_cont_preempt_rerun_compiler_adapter() {
+    if (!Continuations::enabled()) return nullptr;
+    StubCodeMark mark(this, "StubRoutines", "Continuation preempt safepoint blob adapter");
+    address start = __ pc();
+
+    // The safepoint blob handler expects that r20, being a callee saved register, will be preserved
+    // during the VM call. It is used to check if the return pc back to Java was modified in the runtime.
+    // If it wasn't, the return pc is modified so on return the poll instruction is skipped. Saving this
+    // additional value of r20 during freeze will complicate too much the code, so we just zero it here
+    // so that the comparison fails and the skip is not attempted in case the pc was indeed changed.
+    __ movptr(r20, NULL_WORD);
+
+    __ leave();
+    __ ret(lr);
+
+    return start;
+  }
+
+  address generate_cont_preempt_monitorenter_redo() {
+    if (!Continuations::enabled()) return nullptr;
+    StubCodeMark mark(this, "StubRoutines","Continuation monitorenter redo stub");
+    address start = __ pc();
+
+    const Register mon_reg = c_rarg1;
+    __ ldr(mon_reg, __ post(sp, 2 * wordSize));
+
+#ifdef ASSERT
+    { Label L;
+      __ cbnz(mon_reg, L);
+      __ stop("ObjectMonitor to use is null");
+      __ bind(L);
+    }
+#endif // ASSERT
+
+    __ set_last_Java_frame(sp, rfp, lr, rscratch1);
+    __ mov(c_rarg0, rthread);
+    __ rt_call(CAST_FROM_FN_PTR(address, SharedRuntime::redo_monitorenter));
+    __ reset_last_Java_frame(true);
+
+    Label failAcquire;
+    __ ldrb(rscratch1, Address(rthread, JavaThread::preempting_offset()));
+    __ cbnz(rscratch1, failAcquire);
+    // We have the lock now, just return to caller (we will actually hit the
+    // return barrier to thaw more frames)
+
+    // ThawBase::push_preempt_monitorenter_redo set things up so that
+    // SP now points to {fp, lr}.
+    __ ldp(rfp, lr, Address(__ post(sp, 2 * wordSize)));
+    __ ret(lr);
+
+    __ bind(failAcquire);
+    __ strb(/*false*/zr, Address(rthread, JavaThread::preempting_offset()));
+    // Set sp to enterSpecial frame
+    __ ldr(rscratch1, Address(rthread, JavaThread::cont_entry_offset()));
+    __ mov(sp, rscratch1);
+    // Remove enterSpecial frame from the stack and return to Continuation.run()
+    SharedRuntime::continuation_enter_cleanup(_masm);
+    __ leave();
+    __ ret(lr);
+
+    return start;
+  }
+
   // In sun.security.util.math.intpoly.IntegerPolynomial1305, integers
   // are represented as long[5], with BITS_PER_LIMB = 26.
   // Pack five 26-bit limbs into three 64-bit registers.
   void pack_26(Register dest0, Register dest1, Register dest2, Register src) {
     __ ldp(dest0, rscratch1, Address(src, 0));     // 26 bits
@@ -8410,10 +8510,13 @@
   void generate_continuation_stubs() {
     // Continuation stubs:
     StubRoutines::_cont_thaw          = generate_cont_thaw();
     StubRoutines::_cont_returnBarrier = generate_cont_returnBarrier();
     StubRoutines::_cont_returnBarrierExc = generate_cont_returnBarrier_exception();
+    StubRoutines::_cont_preempt_stub = generate_cont_preempt_stub();
+    StubRoutines::_cont_preempt_monitorenter_redo = generate_cont_preempt_monitorenter_redo();
+    StubRoutines::_cont_preempt_rerun_compiler_adapter = generate_cont_preempt_rerun_compiler_adapter();
 
     JFR_ONLY(generate_jfr_stubs();)
   }
 
 #if INCLUDE_JFR
diff a/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp
@@ -610,10 +610,45 @@
   __ membar(Assembler::AnyAny);
   __ dispatch_via(vtos, Interpreter::_normal_table.table_for(vtos));
   return entry;
 }
 
+address TemplateInterpreterGenerator::generate_cont_preempt_rerun_interpreter_adapter() {
+  if (!Continuations::enabled()) return nullptr;
+  address start = __ pc();
+
+  // Restore rfp first since we need it to restore rest of registers
+  __ leave();
+
+  // Restore constant pool cache
+  __ ldr(rcpool, Address(rfp, frame::interpreter_frame_cache_offset * wordSize));
+
+  // Restore Java expression stack pointer
+  __ ldr(rscratch1, Address(rfp, frame::interpreter_frame_last_sp_offset * wordSize));
+  __ lea(esp, Address(rfp, rscratch1, Address::lsl(Interpreter::logStackElementSize)));
+  // and NULL it as marker that esp is now tos until next java call
+  __ str(zr, Address(rfp, frame::interpreter_frame_last_sp_offset * wordSize));
+
+  // Restore machine SP
+  __ ldr(rscratch1, Address(rfp, frame::interpreter_frame_extended_sp_offset * wordSize));
+  __ lea(sp, Address(rfp, rscratch1, Address::lsl(LogBytesPerWord)));
+
+  // Prepare for adjustment on return to call_VM_leaf_base()
+  __ ldr(rmethod, Address(rfp, frame::interpreter_frame_method_offset * wordSize));
+  __ stp(rscratch1, rmethod, Address(__ pre(sp, -2 * wordSize)));
+
+  // Restore dispatch
+  uint64_t offset;
+  __ adrp(rdispatch, ExternalAddress((address)Interpreter::dispatch_table()), offset);
+  __ add(rdispatch, rdispatch, offset);
+
+  __ ret(lr);
+
+  return start;
+}
+
+
 // Helpers for commoning out cases in the various type of method entries.
 //
 
 
 // increment invocation count & check for overflow
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
@@ -3964,16 +3964,26 @@
   // expression stack looks correct.
   __ increment(rbcp);
 
   // store object
   __ str(r0, Address(c_rarg1, BasicObjectLock::obj_offset()));
+  __ set_last_Java_frame(sp, rfp, rscratch1, rscratch2);
   __ lock_object(c_rarg1);
+  __ reset_last_Java_frame(true);
 
   // check to make sure this monitor doesn't cause stack overflow after locking
   __ save_bcp();  // in case of exception
   __ generate_stack_overflow_check(0);
 
+  // Check preemption
+  Label ok;
+  __ ldrb(rscratch1, Address(rthread, in_bytes(JavaThread::preempting_offset())));
+  __ cbz(rscratch1, ok);
+  __ lea(rscratch1, RuntimeAddress(StubRoutines::cont_preempt_stub()));
+  __ br(rscratch1);
+  __ bind(ok);
+
   // The bcp has already been incremented. Just need to dispatch to
   // next instruction.
   __ dispatch_next(vtos);
 }
 
@@ -4018,10 +4028,12 @@
     // if not at bottom then check this entry
     __ br(Assembler::NE, loop);
   }
 
   // error handling. Unlocking was not block-structured
+  __ adr(rscratch1, __ pc());
+  __ set_last_Java_frame(sp, rfp, rscratch1, rscratch2);
   __ call_VM(noreg, CAST_FROM_FN_PTR(address,
                    InterpreterRuntime::throw_illegal_monitor_state_exception));
   __ should_not_reach_here();
 
   // call run-time routine
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
@@ -166,10 +166,11 @@
   static int dcache_line_size() { return _dcache_line_size; }
   static int get_initial_sve_vector_length()  { return _initial_sve_vector_length; };
 
   // Aarch64 supports fast class initialization checks
   static bool supports_fast_class_init_checks() { return true; }
+  static bool supports_cont_preemption() { return true; }
   constexpr static bool supports_stack_watermark_barrier() { return true; }
   constexpr static bool supports_recursive_lightweight_locking() { return true; }
 
   constexpr static bool supports_secondary_supers_table() { return true; }
 
diff a/src/hotspot/cpu/arm/c1_Runtime1_arm.cpp b/src/hotspot/cpu/arm/c1_Runtime1_arm.cpp
--- a/src/hotspot/cpu/arm/c1_Runtime1_arm.cpp
+++ b/src/hotspot/cpu/arm/c1_Runtime1_arm.cpp
@@ -249,10 +249,14 @@
 }
 
 void Runtime1::initialize_pd() {
 }
 
+uint Runtime1::runtime_blob_current_thread_offset(frame f) {
+  unimplemented();
+  return 0;
+}
 
 OopMapSet* Runtime1::generate_exception_throw(StubAssembler* sasm, address target, bool has_argument) {
   OopMap* oop_map = save_live_registers(sasm);
 
   int call_offset;
diff a/src/hotspot/cpu/arm/continuationFreezeThaw_arm.inline.hpp b/src/hotspot/cpu/arm/continuationFreezeThaw_arm.inline.hpp
--- a/src/hotspot/cpu/arm/continuationFreezeThaw_arm.inline.hpp
+++ b/src/hotspot/cpu/arm/continuationFreezeThaw_arm.inline.hpp
@@ -46,10 +46,14 @@
 
 void FreezeBase::adjust_interpreted_frame_unextended_sp(frame& f) {
   Unimplemented();
 }
 
+inline void FreezeBase::prepare_freeze_interpreted_top_frame(const frame& f) {
+  Unimplemented();
+}
+
 inline void FreezeBase::relativize_interpreted_frame_metadata(const frame& f, const frame& hf) {
   Unimplemented();
 }
 
 inline void FreezeBase::patch_pd(frame& hf, const frame& caller) {
@@ -81,10 +85,24 @@
 
 inline void ThawBase::patch_pd(frame& f, const frame& caller) {
   Unimplemented();
 }
 
+inline void ThawBase::patch_pd(frame& f, intptr_t* caller_sp) {
+  Unimplemented();
+}
+
+inline intptr_t* ThawBase::push_preempt_rerun_adapter(frame top, bool is_interpreted_frame) {
+  Unimplemented();
+  return nullptr;
+}
+
+inline intptr_t* ThawBase::push_preempt_monitorenter_redo(stackChunkOop chunk) {
+  Unimplemented();
+  return nullptr;
+}
+
 template <typename ConfigT>
 inline void Thaw<ConfigT>::patch_caller_links(intptr_t* sp, intptr_t* bottom) {
   Unimplemented();
 }
 
diff a/src/hotspot/cpu/arm/continuationHelper_arm.inline.hpp b/src/hotspot/cpu/arm/continuationHelper_arm.inline.hpp
--- a/src/hotspot/cpu/arm/continuationHelper_arm.inline.hpp
+++ b/src/hotspot/cpu/arm/continuationHelper_arm.inline.hpp
@@ -33,10 +33,14 @@
 static inline intptr_t** link_address(const frame& f) {
   Unimplemented();
   return nullptr;
 }
 
+static inline void patch_return_pc_with_preempt_stub(frame& f) {
+  Unimplemented();
+}
+
 inline int ContinuationHelper::frame_align_words(int size) {
   Unimplemented();
   return 0;
 }
 
@@ -60,15 +64,15 @@
 
 inline void ContinuationHelper::set_anchor_to_entry_pd(JavaFrameAnchor* anchor, ContinuationEntry* cont) {
   Unimplemented();
 }
 
-#ifdef ASSERT
 inline void ContinuationHelper::set_anchor_pd(JavaFrameAnchor* anchor, intptr_t* sp) {
   Unimplemented();
 }
 
+#ifdef ASSERT
 inline bool ContinuationHelper::Frame::assert_frame_laid_out(frame f) {
   Unimplemented();
   return false;
 }
 #endif
diff a/src/hotspot/cpu/arm/sharedRuntime_arm.cpp b/src/hotspot/cpu/arm/sharedRuntime_arm.cpp
--- a/src/hotspot/cpu/arm/sharedRuntime_arm.cpp
+++ b/src/hotspot/cpu/arm/sharedRuntime_arm.cpp
@@ -1355,10 +1355,15 @@
 
 uint SharedRuntime::out_preserve_stack_slots() {
   return 0;
 }
 
+VMReg SharedRuntime::thread_register() {
+  Unimplemented();
+  return nullptr;
+}
+
 //------------------------------generate_deopt_blob----------------------------
 void SharedRuntime::generate_deopt_blob() {
   ResourceMark rm;
   CodeBuffer buffer("deopt_blob", 1024, 1024);
   int frame_size_in_words;
diff a/src/hotspot/cpu/arm/templateInterpreterGenerator_arm.cpp b/src/hotspot/cpu/arm/templateInterpreterGenerator_arm.cpp
--- a/src/hotspot/cpu/arm/templateInterpreterGenerator_arm.cpp
+++ b/src/hotspot/cpu/arm/templateInterpreterGenerator_arm.cpp
@@ -456,10 +456,14 @@
   __ ldrb(R3_bytecode, Address(Rbcp));
   __ dispatch_only_normal(vtos);
   return entry;
 }
 
+address TemplateInterpreterGenerator::generate_cont_preempt_rerun_interpreter_adapter() {
+  return nullptr;
+}
+
 
 // Helpers for commoning out cases in the various type of method entries.
 //
 
 // increment invocation count & check for overflow
diff a/src/hotspot/cpu/ppc/c1_Runtime1_ppc.cpp b/src/hotspot/cpu/ppc/c1_Runtime1_ppc.cpp
--- a/src/hotspot/cpu/ppc/c1_Runtime1_ppc.cpp
+++ b/src/hotspot/cpu/ppc/c1_Runtime1_ppc.cpp
@@ -255,10 +255,14 @@
     sp_offset += BytesPerWord;
   }
   frame_size_in_bytes = align_up(sp_offset, frame::alignment_in_bytes);
 }
 
+uint Runtime1::runtime_blob_current_thread_offset(frame f) {
+  Unimplemented();
+  return 0;
+}
 
 OopMapSet* Runtime1::generate_exception_throw(StubAssembler* sasm, address target, bool has_argument) {
   // Make a frame and preserve the caller's caller-save registers.
   OopMap* oop_map = save_live_registers(sasm);
 
diff a/src/hotspot/cpu/ppc/continuationFreezeThaw_ppc.inline.hpp b/src/hotspot/cpu/ppc/continuationFreezeThaw_ppc.inline.hpp
--- a/src/hotspot/cpu/ppc/continuationFreezeThaw_ppc.inline.hpp
+++ b/src/hotspot/cpu/ppc/continuationFreezeThaw_ppc.inline.hpp
@@ -69,10 +69,14 @@
 
 void FreezeBase::adjust_interpreted_frame_unextended_sp(frame& f) {
   // nothing to do
 }
 
+inline void FreezeBase::prepare_freeze_interpreted_top_frame(const frame& f) {
+  Unimplemented();
+}
+
 inline void FreezeBase::relativize_interpreted_frame_metadata(const frame& f, const frame& hf) {
   intptr_t* vfp = f.fp();
   intptr_t* hfp = hf.fp();
   assert(f.fp() > (intptr_t*)f.interpreter_frame_esp(), "");
 
@@ -541,16 +545,30 @@
   assert(f.at_absolute(ijava_idx(esp)) <= f.at_absolute(ijava_idx(monitors)), "");
 
   // Keep top_frame_sp relativized.
 }
 
+inline intptr_t* ThawBase::push_preempt_rerun_adapter(frame top, bool is_interpreted_frame) {
+  Unimplemented();
+  return nullptr;
+}
+
+inline intptr_t* ThawBase::push_preempt_monitorenter_redo(stackChunkOop chunk) {
+  Unimplemented();
+  return nullptr;
+}
+
 inline void ThawBase::patch_pd(frame& f, const frame& caller) {
   patch_callee_link(caller, caller.fp());
   // Prevent assertion if f gets deoptimized right away before it's fully initialized
   f.mark_not_fully_initialized();
 }
 
+inline void ThawBase::patch_pd(frame& f, intptr_t* caller_sp) {
+  Unimplemented();
+}
+
 //
 // Interpreter Calling Procedure on PPC
 //
 // Caller                                   Resized Caller before the Call                New Callee Frame
 //
diff a/src/hotspot/cpu/ppc/continuationHelper_ppc.inline.hpp b/src/hotspot/cpu/ppc/continuationHelper_ppc.inline.hpp
--- a/src/hotspot/cpu/ppc/continuationHelper_ppc.inline.hpp
+++ b/src/hotspot/cpu/ppc/continuationHelper_ppc.inline.hpp
@@ -31,10 +31,14 @@
 static inline intptr_t** link_address(const frame& f) {
   Unimplemented();
   return nullptr;
 }
 
+static inline void patch_return_pc_with_preempt_stub(frame& f) {
+  Unimplemented();
+}
+
 inline int ContinuationHelper::frame_align_words(int size) {
   return size & 1;
 }
 
 inline intptr_t* ContinuationHelper::frame_align_pointer(intptr_t* p) {
@@ -57,15 +61,15 @@
 
 inline void ContinuationHelper::set_anchor_to_entry_pd(JavaFrameAnchor* anchor, ContinuationEntry* cont) {
   // nothing to do
 }
 
-#ifdef ASSERT
 inline void ContinuationHelper::set_anchor_pd(JavaFrameAnchor* anchor, intptr_t* sp) {
   // nothing to do
 }
 
+#ifdef ASSERT
 inline bool ContinuationHelper::Frame::assert_frame_laid_out(frame f) {
   intptr_t* sp = f.sp();
   address pc = *(address*)(sp - frame::sender_sp_ret_address_offset());
   intptr_t* fp = (intptr_t*)f.own_abi()->callers_sp;
   assert(f.raw_pc() == pc, "f.ra_pc: " INTPTR_FORMAT " actual: " INTPTR_FORMAT, p2i(f.raw_pc()), p2i(pc));
diff a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
--- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
+++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
@@ -2337,24 +2337,26 @@
   // The object's monitor m is unlocked iff m->owner is null,
   // otherwise m->owner may contain a thread or a stack address.
 
   // Try to CAS m->owner from null to current thread.
   addi(temp, displaced_header, in_bytes(ObjectMonitor::owner_offset()) - markWord::monitor_value);
+  Register thread_id = displaced_header;
+  ld(thread_id, in_bytes(JavaThread::lock_id_offset()), R16_thread);
   cmpxchgd(/*flag=*/flag,
            /*current_value=*/current_header,
            /*compare_value=*/(intptr_t)0,
-           /*exchange_value=*/R16_thread,
+           /*exchange_value=*/thread_id,
            /*where=*/temp,
            MacroAssembler::MemBarRel | MacroAssembler::MemBarAcq,
            MacroAssembler::cmpxchgx_hint_acquire_lock());
 
   // Store a non-null value into the box.
   std(box, BasicLock::displaced_header_offset_in_bytes(), box);
   beq(flag, success);
 
   // Check for recursive locking.
-  cmpd(flag, current_header, R16_thread);
+  cmpd(flag, current_header, thread_id);
   bne(flag, failure);
 
   // Current thread already owns the lock. Just increment recursions.
   Register recursions = displaced_header;
   ld(recursions, in_bytes(ObjectMonitor::recursions_offset() - ObjectMonitor::owner_offset()), temp);
@@ -2417,11 +2419,13 @@
   addi(current_header, current_header, -(int)markWord::monitor_value); // monitor
   ld(temp,             in_bytes(ObjectMonitor::owner_offset()), current_header);
 
   // In case of LM_LIGHTWEIGHT, we may reach here with (temp & ObjectMonitor::ANONYMOUS_OWNER) != 0.
   // This is handled like owner thread mismatches: We take the slow path.
-  cmpd(flag, temp, R16_thread);
+  Register thread_id = displaced_header;
+  ld(thread_id, in_bytes(JavaThread::lock_id_offset()), R16_thread);
+  cmpd(flag, temp, thread_id);
   bne(flag, failure);
 
   ld(displaced_header, in_bytes(ObjectMonitor::recursions_offset()), current_header);
 
   addic_(displaced_header, displaced_header, -1);
@@ -2521,22 +2525,24 @@
     const Register owner_addr = tmp2;
 
     // Compute owner address.
     addi(owner_addr, tagged_monitor, in_bytes(ObjectMonitor::owner_offset()) - monitor_tag);
 
-    // CAS owner (null => current thread).
+    // CAS owner (null => current thread id).
+    Register thread_id = tmp1;
+    ld(thread_id, in_bytes(JavaThread::lock_id_offset()), R16_thread);
     cmpxchgd(/*flag=*/flag,
             /*current_value=*/t,
             /*compare_value=*/(intptr_t)0,
-            /*exchange_value=*/R16_thread,
+            /*exchange_value=*/thread_id,
             /*where=*/owner_addr,
             MacroAssembler::MemBarRel | MacroAssembler::MemBarAcq,
             MacroAssembler::cmpxchgx_hint_acquire_lock());
     beq(flag, locked);
 
     // Check if recursive.
-    cmpd(flag, t, R16_thread);
+    cmpd(flag, t, thread_id);
     bne(flag, slow_path);
 
     // Recursive.
     ld(tmp1, in_bytes(ObjectMonitor::recursions_offset() - ObjectMonitor::owner_offset()), owner_addr);
     addi(tmp1, tmp1, 1);
@@ -2687,11 +2693,13 @@
     beq(flag, release_);
 
     // The owner may be anonymous and we removed the last obj entry in
     // the lock-stack. This loses the information about the owner.
     // Write the thread to the owner field so the runtime knows the owner.
-    std(R16_thread, in_bytes(ObjectMonitor::owner_offset()), monitor);
+    Register thread_id = tmp2;
+    ld(thread_id, in_bytes(JavaThread::lock_id_offset()), R16_thread);
+    std(thread_id, in_bytes(ObjectMonitor::owner_offset()), monitor);
     b(slow_path);
 
     bind(release_);
     // Set owner to null.
     release();
diff a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp
--- a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp
+++ b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp
@@ -2744,10 +2744,15 @@
 #else
   return 0;
 #endif
 }
 
+VMReg SharedRuntime::thread_register() {
+  Unimplemented();
+  return nullptr;
+}
+
 #if defined(COMPILER1) || defined(COMPILER2)
 // Frame generation for deopt and uncommon trap blobs.
 static void push_skeleton_frame(MacroAssembler* masm, bool deopt,
                                 /* Read */
                                 Register unroll_block_reg,
diff a/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp b/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp
--- a/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp
+++ b/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp
@@ -696,10 +696,15 @@
   __ dispatch_via(vtos, Interpreter::_normal_table.table_for(vtos));
 
   return entry;
 }
 
+address TemplateInterpreterGenerator::generate_cont_preempt_rerun_interpreter_adapter() {
+  return nullptr;
+}
+
+
 // Helpers for commoning out cases in the various type of method entries.
 
 // Increment invocation count & check for overflow.
 //
 // Note: checking for negative value instead of overflow
diff a/src/hotspot/cpu/riscv/c1_Runtime1_riscv.cpp b/src/hotspot/cpu/riscv/c1_Runtime1_riscv.cpp
--- a/src/hotspot/cpu/riscv/c1_Runtime1_riscv.cpp
+++ b/src/hotspot/cpu/riscv/c1_Runtime1_riscv.cpp
@@ -185,11 +185,11 @@
 void StubAssembler::prologue(const char* name, bool must_gc_arguments) {
   set_info(name, must_gc_arguments);
   enter();
 }
 
-void StubAssembler::epilogue() {
+void StubAssembler::epilogue(bool use_pop) {
   leave();
   ret();
 }
 
 #define __ _sasm->
@@ -354,10 +354,15 @@
     cpu_reg_save_offsets[i] = sp_offset;
     sp_offset += step;
   }
 }
 
+uint Runtime1::runtime_blob_current_thread_offset(frame f) {
+  Unimplemented();
+  return 0;
+}
+
 // target: the entry point of the method that creates and posts the exception oop
 // has_argument: true if the exception needs arguments (passed in t0 and t1)
 
 OopMapSet* Runtime1::generate_exception_throw(StubAssembler* sasm, address target, bool has_argument) {
   // make a frame and preserve the caller's caller-save registers
diff a/src/hotspot/cpu/riscv/c2_CodeStubs_riscv.cpp b/src/hotspot/cpu/riscv/c2_CodeStubs_riscv.cpp
--- a/src/hotspot/cpu/riscv/c2_CodeStubs_riscv.cpp
+++ b/src/hotspot/cpu/riscv/c2_CodeStubs_riscv.cpp
@@ -73,21 +73,22 @@
 
 int C2HandleAnonOMOwnerStub::max_size() const {
   // Max size of stub has been determined by testing with 0 without using RISC-V compressed
   // instruction-set extension, in which case C2CodeStubList::emit() will throw an assertion
   // and report the actual size that is needed.
-  return 20 DEBUG_ONLY(+8);
+  return 24 DEBUG_ONLY(+8);
 }
 
 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.
-  __ sd(xthread, Address(mon, ObjectMonitor::owner_offset()));
+  __ mv(t, Address(xthread, JavaThread::lock_id_offset()));
+  __ sd(t, Address(mon, ObjectMonitor::owner_offset()));
 
   // Pop owner object from lock-stack.
   __ lwu(t, Address(xthread, JavaThread::lock_stack_top_offset()));
   __ subw(t, t, oopSize);
 #ifdef ASSERT
diff a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp
--- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp
+++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp
@@ -116,13 +116,15 @@
   // Handle existing monitor.
   bind(object_has_monitor);
   // The object's monitor m is unlocked iff m->owner == nullptr,
   // otherwise m->owner may contain a thread or a stack address.
   //
-  // Try to CAS m->owner from null to current thread.
+  // Try to CAS m->owner from null to current thread id.
+  Register tid = flag;
+  mv(tid, Address(xthread, JavaThread::lock_id_offset()));
   add(tmp, disp_hdr, (in_bytes(ObjectMonitor::owner_offset()) - markWord::monitor_value));
-  cmpxchg(/*memory address*/tmp, /*expected value*/zr, /*new value*/xthread, Assembler::int64,
+  cmpxchg(/*memory address*/tmp, /*expected value*/zr, /*new value*/tid, Assembler::int64,
           Assembler::aq, Assembler::rl, /*result*/tmp3Reg); // cas succeeds if tmp3Reg == zr(expected)
 
   // 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
@@ -130,11 +132,11 @@
   mv(tmp, (address)markWord::unused_mark().value());
   sd(tmp, Address(box, BasicLock::displaced_header_offset_in_bytes()));
 
   beqz(tmp3Reg, locked); // CAS success means locking succeeded
 
-  bne(tmp3Reg, xthread, slow_path); // Check for recursive locking
+  bne(tmp3Reg, tid, slow_path); // Check for recursive locking
 
   // Recursive lock case
   increment(Address(disp_hdr, in_bytes(ObjectMonitor::recursions_offset()) - markWord::monitor_value), 1, tmp2Reg, tmp3Reg);
 
   bind(locked);
@@ -165,11 +167,11 @@
   Register oop = objectReg;
   Register box = boxReg;
   Register disp_hdr = tmp1Reg;
   Register tmp = tmp2Reg;
   Label object_has_monitor;
-  // Finish fast lock successfully. MUST branch to with flag == 0
+  // Finish fast lock successfully. MUST branch to vwith flag == 0
   Label unlocked;
   // Finish fast lock unsuccessfully. slow_path MUST branch to with flag != 0
   Label slow_path;
 
   assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking should use fast_unlock_lightweight");
@@ -330,17 +332,19 @@
     const Register tmp3_owner = tmp3;
 
     // Compute owner address.
     la(tmp2_owner_addr, Address(tmp1_tagged_monitor, (in_bytes(ObjectMonitor::owner_offset()) - monitor_tag)));
 
-    // CAS owner (null => current thread).
-    cmpxchg(/*addr*/ tmp2_owner_addr, /*expected*/ zr, /*new*/ xthread, Assembler::int64,
+    // CAS owner (null => current thread id).
+    Register tid = flag;
+    mv(tid, Address(xthread, JavaThread::lock_id_offset()));
+    cmpxchg(/*addr*/ tmp2_owner_addr, /*expected*/ zr, /*new*/ tid, Assembler::int64,
             /*acquire*/ Assembler::aq, /*release*/ Assembler::relaxed, /*result*/ tmp3_owner);
     beqz(tmp3_owner, locked);
 
     // Check if recursive.
-    bne(tmp3_owner, xthread, slow_path);
+    bne(tmp3_owner, tid, slow_path);
 
     // Recursive.
     increment(Address(tmp1_tagged_monitor, in_bytes(ObjectMonitor::recursions_offset()) - monitor_tag), 1, tmp2, tmp3);
   }
 
@@ -487,11 +491,13 @@
     beqz(t0, release);
 
     // The owner may be anonymous and we removed the last obj entry in
     // the lock-stack. This loses the information about the owner.
     // Write the thread to the owner field so the runtime knows the owner.
-    sd(xthread, Address(tmp2_owner_addr));
+    Register tid = flag;
+    mv(tid, Address(xthread, JavaThread::lock_id_offset()));
+    sd(tid, Address(tmp2_owner_addr));
     j(slow_path);
 
     bind(release);
     // Set owner to null.
     membar(MacroAssembler::LoadStore | MacroAssembler::StoreStore);
diff a/src/hotspot/cpu/riscv/continuationFreezeThaw_riscv.inline.hpp b/src/hotspot/cpu/riscv/continuationFreezeThaw_riscv.inline.hpp
--- a/src/hotspot/cpu/riscv/continuationFreezeThaw_riscv.inline.hpp
+++ b/src/hotspot/cpu/riscv/continuationFreezeThaw_riscv.inline.hpp
@@ -125,10 +125,14 @@
   if (real_unextended_sp != nullptr) {
     f.set_unextended_sp(real_unextended_sp); // can be null at a safepoint
   }
 }
 
+inline void FreezeBase::prepare_freeze_interpreted_top_frame(const frame& f) {
+  Unimplemented();
+}
+
 inline void FreezeBase::relativize_interpreted_frame_metadata(const frame& f, const frame& hf) {
   assert(hf.fp() == hf.unextended_sp() + (f.fp() - f.unextended_sp()), "");
   assert((f.at(frame::interpreter_frame_last_sp_offset) != 0)
     || (f.unextended_sp() == f.sp()), "");
   assert(f.fp() > (intptr_t*)f.at_relative(frame::interpreter_frame_initial_sp_offset), "");
@@ -277,10 +281,24 @@
 
 inline void ThawBase::patch_pd(frame& f, const frame& caller) {
   patch_callee_link(caller, caller.fp());
 }
 
+inline void ThawBase::patch_pd(frame& f, intptr_t* caller_sp) {
+  Unimplemented();
+}
+
+inline intptr_t* ThawBase::push_preempt_rerun_adapter(frame top, bool is_interpreted_frame) {
+  Unimplemented();
+  return nullptr;
+}
+
+inline intptr_t* ThawBase::push_preempt_monitorenter_redo(stackChunkOop chunk) {
+  Unimplemented();
+  return nullptr;
+}
+
 inline void ThawBase::derelativize_interpreted_frame_metadata(const frame& hf, const frame& f) {
   // Make sure that last_sp is kept relativized.
   assert((intptr_t*)f.at_relative(frame::interpreter_frame_last_sp_offset) == f.unextended_sp(), "");
 
   // Make sure that monitor_block_top is still relativized.
diff a/src/hotspot/cpu/riscv/continuationHelper_riscv.inline.hpp b/src/hotspot/cpu/riscv/continuationHelper_riscv.inline.hpp
--- a/src/hotspot/cpu/riscv/continuationHelper_riscv.inline.hpp
+++ b/src/hotspot/cpu/riscv/continuationHelper_riscv.inline.hpp
@@ -38,10 +38,14 @@
   return FKind::interpreted
             ? (intptr_t**)(f.fp() + frame::link_offset)
             : (intptr_t**)(f.unextended_sp() + f.cb()->frame_size() - 2);
 }
 
+static inline void patch_return_pc_with_preempt_stub(frame& f) {
+  Unimplemented();
+}
+
 inline int ContinuationHelper::frame_align_words(int size) {
 #ifdef _LP64
   return size & 1;
 #else
   return 0;
@@ -70,16 +74,16 @@
 
 inline void ContinuationHelper::set_anchor_to_entry_pd(JavaFrameAnchor* anchor, ContinuationEntry* entry) {
   anchor->set_last_Java_fp(entry->entry_fp());
 }
 
-#ifdef ASSERT
 inline void ContinuationHelper::set_anchor_pd(JavaFrameAnchor* anchor, intptr_t* sp) {
   intptr_t* fp = *(intptr_t**)(sp - 2);
   anchor->set_last_Java_fp(fp);
 }
 
+#ifdef ASSERT
 inline bool ContinuationHelper::Frame::assert_frame_laid_out(frame f) {
   intptr_t* sp = f.sp();
   address pc = *(address*)(sp - frame::sender_sp_ret_address_offset());
   intptr_t* fp = *(intptr_t**)(sp - 2);
   assert(f.raw_pc() == pc, "f.ra_pc: " INTPTR_FORMAT " actual: " INTPTR_FORMAT, p2i(f.raw_pc()), p2i(pc));
diff a/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp b/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp
--- a/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp
+++ b/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp
@@ -2449,10 +2449,15 @@
 
 uint SharedRuntime::out_preserve_stack_slots() {
   return 0;
 }
 
+VMReg SharedRuntime::thread_register() {
+  Unimplemented();
+  return nullptr;
+}
+
 #ifdef COMPILER2
 //------------------------------generate_uncommon_trap_blob--------------------
 void SharedRuntime::generate_uncommon_trap_blob() {
   // Allocate space for the code
   ResourceMark rm;
diff a/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp b/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp
--- a/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp
+++ b/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp
@@ -537,10 +537,15 @@
   __ membar(MacroAssembler::AnyAny);
   __ dispatch_via(vtos, Interpreter::_normal_table.table_for(vtos));
   return entry;
 }
 
+address TemplateInterpreterGenerator::generate_cont_preempt_rerun_interpreter_adapter() {
+  return nullptr;
+}
+
+
 // Helpers for commoning out cases in the various type of method entries.
 //
 
 
 // increment invocation count & check for overflow
diff a/src/hotspot/cpu/s390/c1_Runtime1_s390.cpp b/src/hotspot/cpu/s390/c1_Runtime1_s390.cpp
--- a/src/hotspot/cpu/s390/c1_Runtime1_s390.cpp
+++ b/src/hotspot/cpu/s390/c1_Runtime1_s390.cpp
@@ -206,10 +206,15 @@
 
 void Runtime1::initialize_pd() {
   // Nothing to do.
 }
 
+uint Runtime1::runtime_blob_current_thread_offset(frame f) {
+  Unimplemented();
+  return 0;
+}
+
 OopMapSet* Runtime1::generate_exception_throw(StubAssembler* sasm, address target, bool has_argument) {
   // Make a frame and preserve the caller's caller-save registers.
   OopMap* oop_map = save_live_registers(sasm);
   int call_offset;
   if (!has_argument) {
diff a/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp b/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp
--- a/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp
+++ b/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp
@@ -46,10 +46,14 @@
 
 void FreezeBase::adjust_interpreted_frame_unextended_sp(frame& f) {
   Unimplemented();
 }
 
+inline void FreezeBase::prepare_freeze_interpreted_top_frame(const frame& f) {
+  Unimplemented();
+}
+
 inline void FreezeBase::relativize_interpreted_frame_metadata(const frame& f, const frame& hf) {
   Unimplemented();
 }
 
 inline void FreezeBase::patch_pd(frame& hf, const frame& caller) {
@@ -81,10 +85,24 @@
 
 inline void ThawBase::patch_pd(frame& f, const frame& caller) {
   Unimplemented();
 }
 
+inline void ThawBase::patch_pd(frame& f, intptr_t* caller_sp) {
+  Unimplemented();
+}
+
+inline intptr_t* ThawBase::push_preempt_rerun_adapter(frame top, bool is_interpreted_frame) {
+  Unimplemented();
+  return nullptr;
+}
+
+inline intptr_t* ThawBase::push_preempt_monitorenter_redo(stackChunkOop chunk) {
+  Unimplemented();
+  return nullptr;
+}
+
 template <typename ConfigT>
 inline void Thaw<ConfigT>::patch_caller_links(intptr_t* sp, intptr_t* bottom) {
   Unimplemented();
 }
 
diff a/src/hotspot/cpu/s390/continuationHelper_s390.inline.hpp b/src/hotspot/cpu/s390/continuationHelper_s390.inline.hpp
--- a/src/hotspot/cpu/s390/continuationHelper_s390.inline.hpp
+++ b/src/hotspot/cpu/s390/continuationHelper_s390.inline.hpp
@@ -33,10 +33,14 @@
 static inline intptr_t** link_address(const frame& f) {
   Unimplemented();
   return nullptr;
 }
 
+static inline void patch_return_pc_with_preempt_stub(frame& f) {
+  Unimplemented();
+}
+
 inline int ContinuationHelper::frame_align_words(int size) {
   Unimplemented();
   return 0;
 }
 
@@ -60,15 +64,15 @@
 
 inline void ContinuationHelper::set_anchor_to_entry_pd(JavaFrameAnchor* anchor, ContinuationEntry* cont) {
   Unimplemented();
 }
 
-#ifdef ASSERT
 inline void ContinuationHelper::set_anchor_pd(JavaFrameAnchor* anchor, intptr_t* sp) {
   Unimplemented();
 }
 
+#ifdef ASSERT
 inline bool ContinuationHelper::Frame::assert_frame_laid_out(frame f) {
   Unimplemented();
   return false;
 }
 #endif
diff a/src/hotspot/cpu/s390/macroAssembler_s390.cpp b/src/hotspot/cpu/s390/macroAssembler_s390.cpp
--- a/src/hotspot/cpu/s390/macroAssembler_s390.cpp
+++ b/src/hotspot/cpu/s390/macroAssembler_s390.cpp
@@ -3264,13 +3264,14 @@
   Register monitor_tagged = displacedHeader; // Tagged with markWord::monitor_value.
   // The object's monitor m is unlocked iff m->owner is null,
   // otherwise m->owner may contain a thread or a stack address.
 
   // Try to CAS m->owner from null to current thread.
-  // If m->owner is null, then csg succeeds and sets m->owner=THREAD and CR=EQ.
+  // If m->owner is null, then csg succeeds and sets m->owner=THREAD_ID and CR=EQ.
   // Otherwise, register zero is filled with the current owner.
   z_lghi(zero, 0);
+  z_csg(zero, Z_R1_scratch, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner), monitor_tagged);
   z_csg(zero, Z_thread, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner), monitor_tagged);
   if (LockingMode != LM_LIGHTWEIGHT) {
     // Store a non-null value into the box.
     z_stg(box, BasicLock::displaced_header_offset_in_bytes(), box);
   }
diff a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp
--- a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp
+++ b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp
@@ -2374,10 +2374,15 @@
 
 uint SharedRuntime::out_preserve_stack_slots() {
   return frame::z_jit_out_preserve_size/VMRegImpl::stack_slot_size;
 }
 
+VMReg SharedRuntime::thread_register() {
+  Unimplemented();
+  return nullptr;
+}
+
 //
 // Frame generation for deopt and uncommon trap blobs.
 //
 static void push_skeleton_frame(MacroAssembler* masm,
                           /* Unchanged */
diff a/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp b/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp
--- a/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp
+++ b/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp
@@ -719,10 +719,15 @@
   __ call_VM(noreg, runtime_entry);
   __ dispatch_via(vtos, Interpreter::_normal_table.table_for (vtos));
   return entry;
 }
 
+address TemplateInterpreterGenerator::generate_cont_preempt_rerun_interpreter_adapter() {
+  return nullptr;
+}
+
+
 //
 // Helpers for commoning out cases in the various type of method entries.
 //
 
 // Increment invocation count & check for overflow.
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
@@ -46,10 +46,11 @@
   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_different_registers(hdr, obj, disp_hdr, tmp);
   int null_check_offset = -1;
+  Label count_locking, done;
 
   verify_oop(obj);
 
   // save object being locked into the BasicObjectLock
   movptr(Address(disp_hdr, BasicObjectLock::obj_offset()), obj);
@@ -70,11 +71,10 @@
     const Register thread = disp_hdr;
     get_thread(thread);
 #endif
     lightweight_lock(obj, hdr, thread, tmp, slow_case);
   } else  if (LockingMode == LM_LEGACY) {
-    Label done;
     // Load object header
     movptr(hdr, Address(obj, hdr_offset));
     // and mark it as unlocked
     orptr(hdr, markWord::unlocked_value);
     // save unlocked object header into the displaced header location on the stack
@@ -83,11 +83,11 @@
     // 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
-    jcc(Assembler::equal, done);
+    jcc(Assembler::equal, count_locking);
     // 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
@@ -105,14 +105,16 @@
     // 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);
   }
+  jmp(done);
 
+  bind(count_locking);
   inc_held_monitor_count();
+  bind(done);
 
   return null_check_offset;
 }
 
 void C1_MacroAssembler::unlock_object(Register hdr, Register obj, Register disp_hdr, Label& slow_case) {
@@ -151,14 +153,14 @@
     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);
+    dec_held_monitor_count();
     // done
   }
   bind(done);
-  dec_held_monitor_count();
 }
 
 
 // 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) {
diff a/src/hotspot/cpu/x86/c1_Runtime1_x86.cpp b/src/hotspot/cpu/x86/c1_Runtime1_x86.cpp
--- a/src/hotspot/cpu/x86/c1_Runtime1_x86.cpp
+++ b/src/hotspot/cpu/x86/c1_Runtime1_x86.cpp
@@ -204,32 +204,34 @@
 // Implementation of StubFrame
 
 class StubFrame: public StackObj {
  private:
   StubAssembler* _sasm;
+  bool _use_pop_on_epilog;
 
  public:
-  StubFrame(StubAssembler* sasm, const char* name, bool must_gc_arguments);
+  StubFrame(StubAssembler* sasm, const char* name, bool must_gc_arguments, bool use_pop_on_epilog = false);
   void load_argument(int offset_in_words, Register reg);
 
   ~StubFrame();
 };
 
 void StubAssembler::prologue(const char* name, bool must_gc_arguments) {
   set_info(name, must_gc_arguments);
   enter();
 }
 
-void StubAssembler::epilogue() {
-  leave();
+void StubAssembler::epilogue(bool use_pop) {
+  use_pop ? pop(rbp) : leave();
   ret(0);
 }
 
 #define __ _sasm->
 
-StubFrame::StubFrame(StubAssembler* sasm, const char* name, bool must_gc_arguments) {
+StubFrame::StubFrame(StubAssembler* sasm, const char* name, bool must_gc_arguments, bool use_pop_on_epilog) {
   _sasm = sasm;
+  _use_pop_on_epilog = use_pop_on_epilog;
   __ prologue(name, must_gc_arguments);
 }
 
 // load parameters that were stored with LIR_Assembler::store_parameter
 // Note: offsets for store_parameter and load_argument must match
@@ -237,11 +239,11 @@
   __ load_parameter(offset_in_words, reg);
 }
 
 
 StubFrame::~StubFrame() {
-  __ epilogue();
+  __ epilogue(_use_pop_on_epilog);
 }
 
 #undef __
 
 
@@ -620,10 +622,13 @@
 
 void Runtime1::initialize_pd() {
   // nothing to do
 }
 
+uint Runtime1::runtime_blob_current_thread_offset(frame f) {
+  return r15_off / 2;
+}
 
 // Target: the entry point of the method that creates and posts the exception oop.
 // has_argument: true if the exception needs arguments (passed on the stack because
 //               registers must be preserved).
 OopMapSet* Runtime1::generate_exception_throw(StubAssembler* sasm, address target, bool has_argument) {
@@ -1297,11 +1302,11 @@
     case monitorenter_nofpu_id:
       save_fpu_registers = false;
       // fall through
     case monitorenter_id:
       {
-        StubFrame f(sasm, "monitorenter", dont_gc_arguments);
+        StubFrame f(sasm, "monitorenter", dont_gc_arguments, true /* use_pop_on_epilog */);
         OopMap* map = save_live_registers(sasm, 3, save_fpu_registers);
 
         // Called with store_parameter and not C abi
 
         f.load_argument(1, rax); // rax,: object
diff a/src/hotspot/cpu/x86/c2_CodeStubs_x86.cpp b/src/hotspot/cpu/x86/c2_CodeStubs_x86.cpp
--- a/src/hotspot/cpu/x86/c2_CodeStubs_x86.cpp
+++ b/src/hotspot/cpu/x86/c2_CodeStubs_x86.cpp
@@ -71,36 +71,63 @@
   __ bind(entry());
   __ call(RuntimeAddress(StubRoutines::method_entry_barrier()));
   __ jmp(continuation(), false /* maybe_short */);
 }
 
+#ifdef _LP64
+int C2HandleAnonOMOwnerStub::max_size() const {
+  // Max size of stub has been determined by testing with 0, in which case
+  // C2CodeStubList::emit() will throw an assertion and report the actual size that
+  // is needed.
+  return DEBUG_ONLY(40) NOT_DEBUG(25);
+}
+
+void C2HandleAnonOMOwnerStub::emit(C2_MacroAssembler& masm) {
+  __ bind(entry());
+  Register mon = monitor();
+  Register t = tmp();
+  __ movptr(t, Address(r15_thread, JavaThread::lock_id_offset()));
+  __ movptr(Address(mon, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)), t);
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    __ subl(Address(r15_thread, JavaThread::lock_stack_top_offset()), oopSize);
+#ifdef ASSERT
+    __ movl(t, Address(r15_thread, JavaThread::lock_stack_top_offset()));
+    __ movptr(Address(r15_thread, t), 0);
+#endif
+  } else {
+    __ movptr(Address(mon, OM_OFFSET_NO_MONITOR_VALUE_TAG(stack_locker)), NULL_WORD);
+    __ decrementq(Address(r15_thread, JavaThread::held_monitor_count_offset()));
+  }
+  __ jmp(continuation());
+}
+#endif
+
 int C2FastUnlockLightweightStub::max_size() const {
   return 128;
 }
 
 void C2FastUnlockLightweightStub::emit(C2_MacroAssembler& masm) {
-  assert(_t == rax, "must be");
+  assert(_t1 == rax, "must be");
 
-  Label restore_held_monitor_count_and_slow_path;
+  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);
+    __ movl(_t1, Address(_thread, JavaThread::lock_stack_top_offset()));
+    __ movptr(Address(_thread, _t1), _obj);
 #endif
     __ addl(Address(_thread, JavaThread::lock_stack_top_offset()), oopSize);
   }
 
-  { // Restore held monitor count and slow path.
+  { // Handle the unlock in runtime
 
-    __ bind(restore_held_monitor_count_and_slow_path);
-    // Restore held monitor count.
-    __ increment(Address(_thread, JavaThread::held_monitor_count_offset()));
-    // increment will always result in ZF = 0 (no overflows).
+    __ bind(slow_path);
+    // set ZF=0 to indicate failure
+    __ orl(_t1, 1);
     __ jmp(slow_path_continuation());
   }
 
   { // Handle monitor medium path.
 
@@ -108,15 +135,15 @@
 
     Label fix_zf_and_unlocked;
     const Register monitor = _mark;
 
 #ifndef _LP64
-    __ jmpb(restore_held_monitor_count_and_slow_path);
+    __ jmpb(slow_path);
 #else // _LP64
     // successor null check.
     __ cmpptr(Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(succ)), NULL_WORD);
-    __ jccb(Assembler::equal, restore_held_monitor_count_and_slow_path);
+    __ jccb(Assembler::equal, slow_path);
 
     // Release lock.
     __ movptr(Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)), NULL_WORD);
 
     // Fence.
@@ -131,12 +158,13 @@
     // Try to relock, if it fails the monitor has been handed over
     // TODO: Caveat, this may fail due to deflation, which does
     //       not handle the monitor handoff. Currently only works
     //       due to the responsible thread.
     __ xorptr(rax, rax);
-    __ lock(); __ cmpxchgptr(_thread, Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)));
-    __ jccb  (Assembler::equal, restore_held_monitor_count_and_slow_path);
+    __ movptr(_t2, Address(_thread, JavaThread::lock_id_offset()));
+    __ lock(); __ cmpxchgptr(_t2, 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());
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
@@ -457,22 +457,25 @@
   Register threadReg = r15_thread;
 #else
   get_thread(scrReg);
   Register threadReg = scrReg;
 #endif
+  movptr(scrReg, Address(threadReg, JavaThread::lock_id_offset()));
   lock();
-  cmpxchgptr(threadReg, Address(boxReg, owner_offset)); // Updates tmpReg
+  cmpxchgptr(scrReg, Address(boxReg, owner_offset)); // Updates tmpReg
 
   if (RTMRetryCount > 0) {
     // success done else retry
     jccb(Assembler::equal, DONE_LABEL) ;
     bind(L_decrement_retry);
     // Spin and retry if lock is busy.
     rtm_retry_lock_on_busy(retry_on_busy_count_Reg, boxReg, tmpReg, scrReg, L_rtm_retry);
+    jmp(DONE_LABEL);
   }
   else {
     bind(L_decrement_retry);
+    jmp(DONE_LABEL);
   }
 }
 
 #endif //  INCLUDE_RTM_OPT
 
@@ -583,11 +586,11 @@
   //       = sp-proximity test hits
   //       = sp-proximity test generates false-negative
   //    -- by other
   //
 
-  Label IsInflated, DONE_LABEL, NO_COUNT, COUNT;
+  Label IsInflated, DONE_LABEL, COUNT;
 
   if (DiagnoseSyncOnValueBasedClasses != 0) {
     load_klass(tmpReg, objReg, scrReg);
     movl(tmpReg, Address(tmpReg, Klass::access_flags_offset()));
     testl(tmpReg, JVM_ACC_IS_VALUE_BASED_CLASS);
@@ -625,10 +628,11 @@
     subptr(tmpReg, rsp);
     // Next instruction set ZFlag == 1 (Success) if difference is less then one page.
     andptr(tmpReg, (int32_t) (NOT_LP64(0xFFFFF003) LP64_ONLY(7 - (int)os::vm_page_size())) );
     movptr(Address(boxReg, 0), tmpReg);
   }
+  // After recursive stack locking attempt case
   jmp(DONE_LABEL);
 
   bind(IsInflated);
   // The object is inflated. tmpReg contains pointer to ObjectMonitor* + markWord::monitor_value
 
@@ -657,69 +661,69 @@
   // Optimistic form: consider XORL tmpReg,tmpReg
   movptr(tmpReg, NULL_WORD);
 
   // Appears unlocked - try to swing _owner from null to non-null.
   // Ideally, I'd manifest "Self" with get_thread and then attempt
-  // to CAS the register containing Self into m->Owner.
+  // to CAS the register containing thread id into m->Owner.
   // But we don't have enough registers, so instead we can either try to CAS
   // rsp or the address of the box (in scr) into &m->owner.  If the CAS succeeds
-  // we later store "Self" into m->Owner.  Transiently storing a stack address
+  // we later store thread id into m->Owner.  Transiently storing a stack address
   // (rsp or the address of the box) into  m->owner is harmless.
   // Invariant: tmpReg == 0.  tmpReg is EAX which is the implicit cmpxchg comparand.
   lock();
   cmpxchgptr(scrReg, Address(boxReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)));
   movptr(Address(scrReg, 0), 3);          // box->_displaced_header = 3
   // If we weren't able to swing _owner from null to the BasicLock
   // then take the slow path.
-  jccb  (Assembler::notZero, NO_COUNT);
+  jccb  (Assembler::notZero, DONE_LABEL);
   // update _owner from BasicLock to thread
   get_thread (scrReg);                    // beware: clobbers ICCs
+  movptr(scrReg, Address(scrReg, JavaThread::lock_id_offset()));
   movptr(Address(boxReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)), scrReg);
   xorptr(boxReg, boxReg);                 // set icc.ZFlag = 1 to indicate success
+  jmp(DONE_LABEL);
 
   // If the CAS fails we can either retry or pass control to the slow path.
   // We use the latter tactic.
   // Pass the CAS result in the icc.ZFlag into DONE_LABEL
   // If the CAS was successful ...
   //   Self has acquired the lock
   //   Invariant: m->_recursions should already be 0, so we don't need to explicitly set it.
   // Intentional fall-through into DONE_LABEL ...
 #else // _LP64
+  // Unconditionally set box->_displaced_header = markWord::unused_mark().
+  // Without cast to int32_t this style of movptr will destroy r10 which is typically obj.
+  movptr(Address(boxReg, 0), checked_cast<int32_t>(markWord::unused_mark().value()));
+
   // It's inflated and we use scrReg for ObjectMonitor* in this section.
   movq(scrReg, tmpReg);
   xorq(tmpReg, tmpReg);
+  movptr(boxReg, Address(r15_thread, JavaThread::lock_id_offset()));
   lock();
-  cmpxchgptr(thread, Address(scrReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)));
-  // Unconditionally set box->_displaced_header = markWord::unused_mark().
-  // Without cast to int32_t this style of movptr will destroy r10 which is typically obj.
-  movptr(Address(boxReg, 0), checked_cast<int32_t>(markWord::unused_mark().value()));
+  cmpxchgptr(boxReg, Address(scrReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)));
+
   // Propagate ICC.ZF from CAS above into DONE_LABEL.
-  jccb(Assembler::equal, COUNT);          // CAS above succeeded; propagate ZF = 1 (success)
+  jccb(Assembler::equal, DONE_LABEL);    // CAS above succeeded; propagate ZF = 1 (success)
 
-  cmpptr(thread, rax);                // Check if we are already the owner (recursive lock)
-  jccb(Assembler::notEqual, NO_COUNT);    // If not recursive, ZF = 0 at this point (fail)
+  cmpptr(boxReg, rax);                // Check if we are already the owner (recursive lock)
+  jccb(Assembler::notEqual, DONE_LABEL);    // If not recursive, ZF = 0 at this point (fail)
   incq(Address(scrReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(recursions)));
   xorq(rax, rax); // Set ZF = 1 (success) for recursive lock, denoting locking success
+  jmp(DONE_LABEL);
 #endif // _LP64
 #if INCLUDE_RTM_OPT
   } // use_rtm()
 #endif
-  bind(DONE_LABEL);
-
-  // ZFlag == 1 count in fast path
-  // ZFlag == 0 count in slow path
-  jccb(Assembler::notZero, NO_COUNT); // jump if ZFlag == 0
 
   bind(COUNT);
   // Count monitors in fast path
   increment(Address(thread, JavaThread::held_monitor_count_offset()));
-
   xorl(tmpReg, tmpReg); // Set ZF == 1
 
-  bind(NO_COUNT);
+  bind(DONE_LABEL);
 
-  // At NO_COUNT the icc ZFlag is set as follows ...
+  // At DONE_LABEL the icc ZFlag is set as follows ...
   // fast_unlock uses the same protocol.
   // ZFlag == 1 -> Success
   // ZFlag == 0 -> Failure - force control through the slow path
 }
 
@@ -753,16 +757,16 @@
 // could reasonably *avoid* checking owner in fast_unlock().
 // 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) {
+void C2_MacroAssembler::fast_unlock(Register objReg, Register boxReg, Register tmpReg, Register scrReg, bool use_rtm) {
   assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking should use fast_unlock_lightweight");
   assert(boxReg == rax, "");
   assert_different_registers(objReg, boxReg, tmpReg);
 
-  Label DONE_LABEL, Stacked, COUNT, NO_COUNT;
+  Label DONE_LABEL, Stacked, COUNT;
 
 #if INCLUDE_RTM_OPT
   if (UseRTMForStackLocks && use_rtm) {
     assert(LockingMode != LM_MONITOR, "LockingMode == 0 (LM_MONITOR) and +UseRTMForStackLocks are mutually exclusive");
     Label L_regular_unlock;
@@ -776,19 +780,34 @@
   }
 #endif
 
   if (LockingMode == LM_LEGACY) {
     cmpptr(Address(boxReg, 0), NULL_WORD);                            // Examine the displaced header
-    jcc   (Assembler::zero, COUNT);                                   // 0 indicates recursive stack-lock
+    jcc   (Assembler::zero, DONE_LABEL);                              // 0 indicates recursive stack-lock
   }
   movptr(tmpReg, Address(objReg, oopDesc::mark_offset_in_bytes()));   // Examine the object's markword
   if (LockingMode != LM_MONITOR) {
     testptr(tmpReg, markWord::monitor_value);                         // Inflated?
     jcc(Assembler::zero, Stacked);
   }
 
   // It's inflated.
+  // If the owner is ANONYMOUS, we need to fix it -  in an outline stub.
+  cmpptr(Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)), (int32_t) ObjectMonitor::ANONYMOUS_OWNER);
+#ifdef _LP64
+  if (!Compile::current()->output()->in_scratch_emit_size()) {
+    C2HandleAnonOMOwnerStub* stub = new (Compile::current()->comp_arena()) C2HandleAnonOMOwnerStub(tmpReg, boxReg);
+    Compile::current()->output()->add_stub(stub);
+    jcc(Assembler::equal, stub->entry());
+    bind(stub->continuation());
+  } else
+#endif
+  {
+    // We can't easily implement this optimization on 32 bit because we don't have a thread register.
+    // Call the slow-path instead.
+    jcc(Assembler::notEqual, DONE_LABEL);
+  }
 
 #if INCLUDE_RTM_OPT
   if (use_rtm) {
     Label L_regular_inflated_unlock;
     int owner_offset = OM_OFFSET_NO_MONITOR_VALUE_TAG(owner);
@@ -839,13 +858,15 @@
   cmpptr(Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(recursions)), 0);
   jccb(Assembler::equal, LNotRecursive);
 
   // Recursive inflated unlock
   decq(Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(recursions)));
-  jmpb(LSuccess);
+  xorl(tmpReg, tmpReg); // Set ZF == 1
+  jmp(DONE_LABEL);
 
   bind(LNotRecursive);
+
   movptr(boxReg, Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(cxq)));
   orptr(boxReg, Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(EntryList)));
   jccb  (Assembler::notZero, CheckSucc);
   // Without cast to int32_t this style of movptr will destroy r10 which is typically obj.
   movptr(Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)), NULL_WORD);
@@ -889,12 +910,13 @@
   // coherence traffic on the lock *and* artificially extended the critical section
   // length while by virtue of passing control into the slow path.
 
   // box is really RAX -- the following CMPXCHG depends on that binding
   // cmpxchg R,[M] is equivalent to rax = CAS(M,rax,R)
+  movptr(scrReg, Address(r15_thread, JavaThread::lock_id_offset()));
   lock();
-  cmpxchgptr(r15_thread, Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)));
+  cmpxchgptr(scrReg, Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)));
   // There's no successor so we tried to regrab the lock.
   // If that didn't work, then another thread grabbed the
   // lock so we're done (and exit was a success).
   jccb  (Assembler::notEqual, LSuccess);
   // Intentional fall-through into slow path
@@ -911,42 +933,35 @@
   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);
-
-  // ZFlag == 1 count in fast path
-  // ZFlag == 0 count in slow path
-  jccb(Assembler::notZero, NO_COUNT);
-
-  bind(COUNT);
-  // Count monitors in fast path
+    jccb(Assembler::notZero, DONE_LABEL);
+    // Count monitors in fast path
 #ifndef _LP64
-  get_thread(tmpReg);
-  decrementl(Address(tmpReg, JavaThread::held_monitor_count_offset()));
+    get_thread(tmpReg);
+    decrementl(Address(tmpReg, JavaThread::held_monitor_count_offset()));
 #else // _LP64
-  decrementq(Address(r15_thread, JavaThread::held_monitor_count_offset()));
+    decrementq(Address(r15_thread, JavaThread::held_monitor_count_offset()));
 #endif
+    xorl(tmpReg, tmpReg); // Set ZF == 1
+  }
 
-  xorl(tmpReg, tmpReg); // Set ZF == 1
-
-  bind(NO_COUNT);
+  // ZFlag == 1 -> Success
+  // ZFlag == 0 -> Failure - force control through the slow path
+  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.
+  // Finish fast lock successfully.
   Label locked;
   // Finish fast lock unsuccessfully. MUST jump with ZF == 0
   Label slow_path;
 
   if (DiagnoseSyncOnValueBasedClasses != 0) {
@@ -991,36 +1006,35 @@
 
     bind(push);
     // After successful lock, push object on lock-stack.
     movptr(Address(thread, top), obj);
     addl(Address(thread, JavaThread::lock_stack_top_offset()), oopSize);
+    xorl(rax_reg, rax_reg);
     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)));
+    movptr(box, Address(thread, JavaThread::lock_id_offset()));
+    lock(); cmpxchgptr(box, Address(tagged_monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)));
     jccb(Assembler::equal, locked);
 
     // Check if recursive.
-    cmpptr(thread, rax_reg);
+    cmpptr(box, rax_reg);
     jccb(Assembler::notEqual, slow_path);
 
     // Recursive.
     increment(Address(tagged_monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(recursions)));
+    xorl(rax_reg, rax_reg);
   }
 
   bind(locked);
-  increment(Address(thread, JavaThread::held_monitor_count_offset()));
-  // Set ZF = 1
-  xorl(rax_reg, rax_reg);
-
 #ifdef ASSERT
   // Check that locked label is reached with ZF set.
   Label zf_correct;
   Label zf_bad_zero;
   jcc(Assembler::zero, zf_correct);
@@ -1037,31 +1051,28 @@
   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) {
+void C2_MacroAssembler::fast_unlock_lightweight(Register obj, Register reg_rax, Register t1, Register t2, Register thread) {
   assert(LockingMode == LM_LIGHTWEIGHT, "must be");
   assert(reg_rax == rax, "Used for CAS");
-  assert_different_registers(obj, reg_rax, t);
+  assert_different_registers(obj, reg_rax, t1, t2);
 
   // Handle inflated monitor.
   Label inflated, inflated_check_lock_stack;
   // Finish fast unlock successfully.  MUST jump with ZF == 1
   Label unlocked;
 
-  // Assume success.
-  decrement(Address(thread, JavaThread::held_monitor_count_offset()));
-
-  const Register mark = t;
+  const Register mark = t1;
   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);
+    stub = new (Compile::current()->comp_arena()) C2FastUnlockLightweightStub(obj, mark, reg_rax, t2, 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();
@@ -1150,11 +1161,11 @@
     jmpb(unlocked);
 
     // Recursive unlock.
     bind(recursive);
     decrement(Address(monitor, OM_OFFSET_NO_MONITOR_VALUE_TAG(recursions)));
-    xorl(t, t);
+    xorl(t1, t1);
 #endif
   }
 
   bind(unlocked);
   if (stub != nullptr) {
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
@@ -39,15 +39,15 @@
                  Register scr, Register cx1, Register cx2, Register thread,
                  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_unlock(Register obj, Register box, Register tmp, Register scr, 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);
+  void fast_unlock_lightweight(Register obj, Register reg_rax, Register t1, Register t2, 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,
diff a/src/hotspot/cpu/x86/continuationFreezeThaw_x86.inline.hpp b/src/hotspot/cpu/x86/continuationFreezeThaw_x86.inline.hpp
--- a/src/hotspot/cpu/x86/continuationFreezeThaw_x86.inline.hpp
+++ b/src/hotspot/cpu/x86/continuationFreezeThaw_x86.inline.hpp
@@ -124,10 +124,16 @@
   if (real_unextended_sp != nullptr) {
     f.set_unextended_sp(real_unextended_sp); // can be null at a safepoint
   }
 }
 
+inline void FreezeBase::prepare_freeze_interpreted_top_frame(const frame& f) {
+  assert(*f.addr_at(frame::interpreter_frame_last_sp_offset) == 0, "should be null for top frame");
+  intptr_t* lspp = f.addr_at(frame::interpreter_frame_last_sp_offset);
+  *lspp = f.unextended_sp() - f.fp();
+}
+
 inline void FreezeBase::relativize_interpreted_frame_metadata(const frame& f, const frame& hf) {
   assert(hf.fp() == hf.unextended_sp() + (f.fp() - f.unextended_sp()), "");
   assert((f.at(frame::interpreter_frame_last_sp_offset) != 0)
     || (f.unextended_sp() == f.sp()), "");
   assert(f.fp() > (intptr_t*)f.at_relative(frame::interpreter_frame_initial_sp_offset), "");
@@ -223,11 +229,11 @@
     return f;
   } else {
     int fsize = FKind::size(hf);
     intptr_t* frame_sp = caller.unextended_sp() - fsize;
     if (bottom || caller.is_interpreted_frame()) {
-      int argsize = hf.compiled_frame_stack_argsize();
+      int argsize = FKind::stack_argsize(hf);
 
       fsize += argsize;
       frame_sp   -= argsize;
       caller.set_sp(caller.sp() - argsize);
       assert(caller.sp() == frame_sp + (fsize-argsize), "");
@@ -240,12 +246,13 @@
     intptr_t* fp;
     if (PreserveFramePointer) {
       // we need to recreate a "real" frame pointer, pointing into the stack
       fp = frame_sp + FKind::size(hf) - frame::sender_sp_offset;
     } else {
-       // we need to re-read fp because it may be an oop and we might have fixed the frame.
-      fp = *(intptr_t**)(hf.sp() - frame::sender_sp_offset);
+      fp = FKind::stub
+        ? frame_sp + fsize - frame::sender_sp_offset // fp always points to the address below the pushed return pc. We need correct address.
+        : *(intptr_t**)(hf.sp() - frame::sender_sp_offset); // we need to re-read fp because it may be an oop and we might have fixed the frame.
     }
     return frame(frame_sp, frame_sp, fp, hf.pc(), hf.cb(), hf.oop_map(), false); // TODO PERF : this computes deopt state; is it necessary?
   }
 }
 
@@ -264,10 +271,62 @@
 
 inline void ThawBase::patch_pd(frame& f, const frame& caller) {
   patch_callee_link(caller, caller.fp());
 }
 
+inline void ThawBase::patch_pd(frame& f, intptr_t* caller_sp) {
+  intptr_t* fp = caller_sp - frame::sender_sp_offset;
+  patch_callee_link(f, fp);
+}
+
+inline intptr_t* ThawBase::push_preempt_rerun_adapter(frame top, bool is_interpreted_frame) {
+  intptr_t* sp = top.sp();
+  intptr_t* fp = sp - frame::sender_sp_offset;
+  address pc = is_interpreted_frame ? Interpreter::cont_preempt_rerun_interpreter_adapter()
+                                    : StubRoutines::cont_preempt_rerun_compiler_adapter();
+
+#ifdef ASSERT
+  RegisterMap map(JavaThread::current(),
+                  RegisterMap::UpdateMap::skip,
+                  RegisterMap::ProcessFrames::skip,
+                  RegisterMap::WalkContinuation::skip);
+  frame caller = top.sender(&map);
+  intptr_t link_addr = (intptr_t)ContinuationHelper::Frame::callee_link_address(caller);
+  assert(sp[-2] == link_addr, "wrong link address: " INTPTR_FORMAT " != " INTPTR_FORMAT, sp[-2], link_addr);
+#endif
+
+  sp -= frame::metadata_words;
+  *(address*)(sp - frame::sender_sp_ret_address_offset()) = pc;
+  *(intptr_t**)(sp - frame::sender_sp_offset) = fp;
+
+  log_develop_trace(continuations, preempt)("push_preempt_rerun_%s_adapter() initial sp: " INTPTR_FORMAT " final sp: " INTPTR_FORMAT " fp: " INTPTR_FORMAT,
+    is_interpreted_frame ? "interpreter" : "safepointblob", p2i(sp + frame::metadata_words), p2i(sp), p2i(fp));
+  return sp;
+}
+
+inline intptr_t* ThawBase::push_preempt_monitorenter_redo(stackChunkOop chunk) {
+  frame enterSpecial = new_entry_frame();
+  intptr_t* sp = enterSpecial.sp();
+
+  // First push the return barrier frame
+  sp -= frame::metadata_words;
+  sp[1] = (intptr_t)StubRoutines::cont_returnBarrier();
+  sp[0] = (intptr_t)enterSpecial.fp();
+
+  // Now push the ObjectMonitor*
+  sp -= frame::metadata_words;
+  sp[1] = (intptr_t)chunk->objectMonitor(); // alignment
+  sp[0] = (intptr_t)chunk->objectMonitor();
+
+  // Finally arrange to return to the monitorenter_redo stub
+  sp[-1] = (intptr_t)StubRoutines::cont_preempt_monitorenter_redo();
+  sp[-2] = (intptr_t)enterSpecial.fp();
+
+  log_develop_trace(continuations, preempt)("push_preempt_monitorenter_redo initial sp: " INTPTR_FORMAT " final sp: " INTPTR_FORMAT, p2i(sp + 2 * frame::metadata_words), p2i(sp));
+  return sp;
+}
+
 inline void ThawBase::derelativize_interpreted_frame_metadata(const frame& hf, const frame& f) {
   // Make sure that last_sp is kept relativized.
   assert((intptr_t*)f.at_relative(frame::interpreter_frame_last_sp_offset) == f.unextended_sp(), "");
 
   // Make sure that monitor_block_top is still relativized.
diff a/src/hotspot/cpu/x86/continuationHelper_x86.inline.hpp b/src/hotspot/cpu/x86/continuationHelper_x86.inline.hpp
--- a/src/hotspot/cpu/x86/continuationHelper_x86.inline.hpp
+++ b/src/hotspot/cpu/x86/continuationHelper_x86.inline.hpp
@@ -38,10 +38,17 @@
   return FKind::interpreted
     ? (intptr_t**)(f.fp() + frame::link_offset)
     : (intptr_t**)(f.unextended_sp() + f.cb()->frame_size() - frame::sender_sp_offset);
 }
 
+static inline void patch_return_pc_with_preempt_stub(frame& f) {
+  // Patch the pc of the now old last Java frame (we already set the anchor to enterSpecial)
+  // so that when target goes back to Java it will actually return to the preempt cleanup stub.
+  intptr_t* sp = f.sp();
+  sp[-1] = (intptr_t)StubRoutines::cont_preempt_stub();
+}
+
 inline int ContinuationHelper::frame_align_words(int size) {
 #ifdef _LP64
   return size & 1;
 #else
   return 0;
@@ -70,16 +77,16 @@
 
 inline void ContinuationHelper::set_anchor_to_entry_pd(JavaFrameAnchor* anchor, ContinuationEntry* entry) {
   anchor->set_last_Java_fp(entry->entry_fp());
 }
 
-#ifdef ASSERT
 inline void ContinuationHelper::set_anchor_pd(JavaFrameAnchor* anchor, intptr_t* sp) {
   intptr_t* fp = *(intptr_t**)(sp - frame::sender_sp_offset);
   anchor->set_last_Java_fp(fp);
 }
 
+#ifdef ASSERT
 inline bool ContinuationHelper::Frame::assert_frame_laid_out(frame f) {
   intptr_t* sp = f.sp();
   address pc = *(address*)(sp - frame::sender_sp_ret_address_offset());
   intptr_t* fp = *(intptr_t**)(sp - frame::sender_sp_offset);
   assert(f.raw_pc() == pc, "f.ra_pc: " INTPTR_FORMAT " actual: " INTPTR_FORMAT, p2i(f.raw_pc()), p2i(pc));
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
@@ -1152,13 +1152,15 @@
 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 (LockingMode == LM_MONITOR) {
+    push_cont_fastpath();
     call_VM(noreg,
             CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
             lock_reg);
+    pop_cont_fastpath();
   } else {
     Label count_locking, done, slow_case;
 
     const Register swap_reg = rax; // Must use rax for cmpxchg instruction
     const Register tmp_reg = rbx;
@@ -1238,28 +1240,31 @@
       andptr(swap_reg, zero_bits - (int)os::vm_page_size());
 
       // Save the test result, for recursive case, the result is zero
       movptr(Address(lock_reg, mark_offset), swap_reg);
       jcc(Assembler::notZero, slow_case);
-
-      bind(count_locking);
     }
+    jmp(done);
+
+    bind(count_locking);
     inc_held_monitor_count();
     jmp(done);
 
     bind(slow_case);
 
+    push_cont_fastpath();
     // Call the runtime routine for slow case
     if (LockingMode == LM_LIGHTWEIGHT) {
       call_VM(noreg,
               CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter_obj),
               obj_reg);
     } else {
       call_VM(noreg,
               CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
               lock_reg);
     }
+    pop_cont_fastpath();
     bind(done);
   }
 }
 
 
@@ -1280,11 +1285,11 @@
          "The argument is only for looks. It must be c_rarg1");
 
   if (LockingMode == LM_MONITOR) {
     call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit), lock_reg);
   } else {
-    Label count_locking, done, slow_case;
+    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
 
@@ -1318,22 +1323,20 @@
 
       // Test for recursion
       testptr(header_reg, header_reg);
 
       // zero for recursive case
-      jcc(Assembler::zero, count_locking);
+      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::notZero, slow_case);
-
-      bind(count_locking);
+      dec_held_monitor_count();
     }
-    dec_held_monitor_count();
     jmp(done);
 
     bind(slow_case);
     // Call the runtime routine for slow case.
     movptr(Address(lock_reg, BasicObjectLock::obj_offset()), obj_reg); // restore obj
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
@@ -2055,10 +2055,15 @@
 
 uint SharedRuntime::out_preserve_stack_slots() {
   return 0;
 }
 
+VMReg SharedRuntime::thread_register() {
+  Unimplemented();
+  return nullptr;
+}
+
 //------------------------------generate_deopt_blob----------------------------
 void SharedRuntime::generate_deopt_blob() {
   // allocate space for the code
   ResourceMark rm;
   // setup code generation tools
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
@@ -162,10 +162,11 @@
   // values on its own
 
   static int rax_offset_in_bytes(void)    { return BytesPerInt * rax_off; }
   static int rdx_offset_in_bytes(void)    { return BytesPerInt * rdx_off; }
   static int rbx_offset_in_bytes(void)    { return BytesPerInt * rbx_off; }
+  static int r15_offset_in_bytes(void)    { return BytesPerInt * r15_off; }
   static int xmm0_offset_in_bytes(void)   { return BytesPerInt * xmm0_off; }
   static int return_offset_in_bytes(void) { return BytesPerInt * return_off; }
 
   // During deoptimization only the result registers need to be restored,
   // all the other values have already been extracted.
@@ -1344,11 +1345,11 @@
 //   rsp: pointer to the spilled rbp in the entry frame
 //
 // Kills:
 //   rbx
 //
-void static continuation_enter_cleanup(MacroAssembler* masm) {
+static void continuation_enter_cleanup(MacroAssembler* masm) {
 #ifdef ASSERT
   Label L_good_sp;
   __ cmpptr(rsp, Address(r15_thread, JavaThread::cont_entry_offset()));
   __ jcc(Assembler::equal, L_good_sp);
   __ stop("Incorrect rsp at continuation_enter_cleanup");
@@ -1534,10 +1535,11 @@
 
   // --- Thawing path
 
   __ bind(L_thaw);
 
+  ContinuationEntry::_thaw_call_pc_offset = __ pc() - start;
   __ call(RuntimeAddress(StubRoutines::cont_thaw()));
 
   ContinuationEntry::_return_pc_offset = __ pc() - start;
   oop_maps->add_gc_map(__ pc() - start, map->deep_copy());
   __ post_call_nop();
@@ -1636,10 +1638,14 @@
 
   __ leave();
   __ ret(0);
 }
 
+void SharedRuntime::continuation_enter_cleanup(MacroAssembler* masm) {
+  ::continuation_enter_cleanup(masm);
+}
+
 static void gen_special_dispatch(MacroAssembler* masm,
                                  const methodHandle& method,
                                  const BasicType* sig_bt,
                                  const VMRegPair* regs) {
   verify_oop_args(masm, method, sig_bt, regs);
@@ -2207,10 +2213,12 @@
       __ 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);
     }
+    __ jmp (lock_done);
+
     __ bind(count_mon);
     __ inc_held_monitor_count();
 
     // Slow path will re-enter here
     __ bind(lock_done);
@@ -2322,11 +2330,10 @@
     if (LockingMode == LM_LEGACY) {
       Label not_recur;
       // Simple recursive lock?
       __ cmpptr(Address(rsp, lock_slot_offset * VMRegImpl::stack_slot_size), NULL_WORD);
       __ jcc(Assembler::notEqual, not_recur);
-      __ dec_held_monitor_count();
       __ jmpb(fast_done);
       __ bind(not_recur);
     }
 
     // Must save rax if it is live now because cmpxchg must use it
@@ -2348,11 +2355,10 @@
       __ jcc(Assembler::notEqual, slow_path_unlock);
       __ dec_held_monitor_count();
     } else {
       assert(LockingMode == LM_LIGHTWEIGHT, "must be");
       __ lightweight_unlock(obj_reg, swap_reg, r15_thread, lock_reg, slow_path_unlock);
-      __ dec_held_monitor_count();
     }
 
     // slow path re-enters here
     __ bind(unlock_done);
     if (ret_type != T_FLOAT && ret_type != T_DOUBLE && ret_type != T_VOID) {
@@ -2424,11 +2430,14 @@
     __ mov(c_rarg0, obj_reg);
     __ mov(c_rarg1, lock_reg);
     __ mov(c_rarg2, r15_thread);
 
     // Not a leaf but we have last_Java_frame setup as we want
+    // Force freeze slow path on ObjectMonitor::enter() for now which will fail with freeze_pinned_native.
+    __ push_cont_fastpath();
     __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::complete_monitor_locking_C), 3);
+    __ pop_cont_fastpath();
     restore_args(masm, total_c_args, c_arg, out_regs);
 
 #ifdef ASSERT
     { Label L;
     __ cmpptr(Address(r15_thread, in_bytes(Thread::pending_exception_offset())), NULL_WORD);
@@ -2539,10 +2548,14 @@
 // return address.
 uint SharedRuntime::in_preserve_stack_slots() {
   return 4 + 2 * VerifyStackAtCalls;
 }
 
+VMReg SharedRuntime::thread_register() {
+  return r15_thread->as_VMReg();
+}
+
 //------------------------------generate_deopt_blob----------------------------
 void SharedRuntime::generate_deopt_blob() {
   // Allocate space for the code
   ResourceMark rm;
   // Setup code generation tools
diff a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp
--- a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp
+++ b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp
@@ -32,10 +32,11 @@
 #include "gc/shared/gc_globals.hpp"
 #include "memory/universe.hpp"
 #include "prims/jvmtiExport.hpp"
 #include "prims/upcallLinker.hpp"
 #include "runtime/arguments.hpp"
+#include "runtime/continuationEntry.hpp"
 #include "runtime/javaThread.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "runtime/stubRoutines.hpp"
 #include "stubGenerator_x86_64.hpp"
 #ifdef COMPILER2
@@ -3782,10 +3783,136 @@
 
 address StubGenerator::generate_cont_returnBarrier_exception() {
   return generate_cont_thaw("Cont thaw return barrier exception", Continuation::thaw_return_barrier_exception);
 }
 
+address StubGenerator::generate_cont_preempt_stub() {
+  if (!Continuations::enabled()) return nullptr;
+  StubCodeMark mark(this, "StubRoutines","Continuation preempt stub");
+  address start = __ pc();
+
+#ifdef ASSERT
+  __ push(rax);
+  { Label L;
+    __ get_thread(rax);
+    __ cmpptr(r15_thread, rax);
+    __ jcc(Assembler::equal, L);
+    __ stop("r15 should have been preserved across VM call");
+    __ bind(L);
+  }
+  __ pop(rax);
+#endif
+
+  __ reset_last_Java_frame(true);
+
+  // reset _preempting flag
+#ifdef ASSERT
+  { Label L;
+    __ movbool(rscratch1, Address(r15_thread, JavaThread::preempting_offset()));
+    __ testbool(rscratch1);
+    __ jcc(Assembler::notZero, L);
+    __ stop("preempting flag should be set");
+    __ bind(L);
+  }
+#endif
+  __ movbool(Address(r15_thread, JavaThread::preempting_offset()), false);
+
+  // Set rsp to enterSpecial frame
+  __ movptr(rsp, Address(r15_thread, JavaThread::cont_entry_offset()));
+
+  Label preemption_cancelled;
+  __ movbool(rscratch1, Address(r15_thread, JavaThread::preemption_cancelled_offset()));
+  __ testbool(rscratch1);
+  __ jcc(Assembler::notZero, preemption_cancelled);
+
+  // Remove enterSpecial frame from the stack and return to Continuation.run()
+  SharedRuntime::continuation_enter_cleanup(_masm);
+  __ pop(rbp);
+  __ ret(0);
+
+  __ bind(preemption_cancelled);
+  __ lea(rbp, Address(rsp, checked_cast<int32_t>(ContinuationEntry::size())));
+  __ movptr(rscratch1, ExternalAddress((address)&ContinuationEntry::_thaw_call_pc));
+  __ jmp(rscratch1);
+
+  return start;
+}
+
+address StubGenerator::generate_cont_preempt_monitorenter_redo() {
+  if (!Continuations::enabled()) return nullptr;
+  StubCodeMark mark(this, "StubRoutines","Continuation monitorenter redo stub");
+  address start = __ pc();
+
+#ifdef ASSERT
+  __ push(rax);
+  { Label L;
+    __ get_thread(rax);
+    __ cmpptr(r15_thread, rax);
+    __ jcc(Assembler::equal, L);
+    __ stop("r15 should have been preserved across VM call");
+    __ bind(L);
+  }
+  __ pop(rax);
+#endif
+
+  const Register mon_reg = c_rarg1;
+  __ pop(mon_reg);
+  __ pop(mon_reg);
+
+#ifdef ASSERT
+  { Label L;
+    __ testptr(mon_reg, mon_reg);
+    __ jcc(Assembler::notEqual, L);
+    __ stop("ObjectMonitor to use is null");
+    __ bind(L);
+  }
+#endif // ASSERT
+
+  __ mov(c_rarg0, r15_thread);
+  __ subptr(rsp, frame::arg_reg_save_area_bytes);
+  __ call(RuntimeAddress(CAST_FROM_FN_PTR(address, SharedRuntime::redo_monitorenter)));
+  __ addptr(rsp, frame::arg_reg_save_area_bytes);
+
+  Label failAcquire;
+  __ movbool(rscratch1, Address(r15_thread, JavaThread::preempting_offset()));
+  __ testbool(rscratch1);
+  __ jcc(Assembler::notEqual, failAcquire);
+  // We have the lock now, just return to caller (we will actually hit the
+  // return barrier to thaw more frames)
+  __ pop(rbp);
+  __ ret(0);
+
+  __ bind(failAcquire);
+  __ movbool(Address(r15_thread, JavaThread::preempting_offset()), false);
+  // Set rsp to enterSpecial frame
+  __ movptr(rsp, Address(r15_thread, JavaThread::cont_entry_offset()));
+  // Remove enterSpecial frame from the stack and return to Continuation.run()
+  SharedRuntime::continuation_enter_cleanup(_masm);
+  __ pop(rbp);
+  __ ret(0);
+
+  return start;
+}
+
+address StubGenerator::generate_cont_preempt_rerun_compiler_adapter() {
+  if (!Continuations::enabled()) return nullptr;
+  StubCodeMark mark(this, "StubRoutines", "Continuation preempt safepoint blob adapter");
+  address start = __ pc();
+
+  // The safepoint blob handler expects that rbx, being a callee saved register, will be preserved
+  // during the VM call. It is used to check if the return pc back to Java was modified in the runtime.
+  // If it wasn't, the return pc is modified so on return the poll instruction is skipped. Saving this
+  // additional value of rbx during freeze will complicate too much the code, so we just zero it here
+  // so that the comparison fails and the skip is not attempted in case the pc was indeed changed.
+  __ movptr(rbx, NULL_WORD);
+
+  __ pop(rbp);
+  __ ret(0);
+
+  return start;
+}
+
 #if INCLUDE_JFR
 
 // For c2: c_rarg0 is junk, call to runtime to write a checkpoint.
 // It returns a jobject handle to the event writer.
 // The handle is dereferenced and the return value is the event writer oop.
@@ -4135,10 +4262,13 @@
 void StubGenerator::generate_continuation_stubs() {
   // Continuation stubs:
   StubRoutines::_cont_thaw          = generate_cont_thaw();
   StubRoutines::_cont_returnBarrier = generate_cont_returnBarrier();
   StubRoutines::_cont_returnBarrierExc = generate_cont_returnBarrier_exception();
+  StubRoutines::_cont_preempt_stub = generate_cont_preempt_stub();
+  StubRoutines::_cont_preempt_monitorenter_redo = generate_cont_preempt_monitorenter_redo();
+  StubRoutines::_cont_preempt_rerun_compiler_adapter = generate_cont_preempt_rerun_compiler_adapter();
 
   JFR_ONLY(generate_jfr_stubs();)
 }
 
 #if INCLUDE_JFR
diff a/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp b/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp
--- a/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp
+++ b/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp
@@ -578,10 +578,14 @@
 
   // TODO: will probably need multiple return barriers depending on return type
   address generate_cont_returnBarrier();
   address generate_cont_returnBarrier_exception();
 
+  address generate_cont_preempt_stub();
+  address generate_cont_preempt_monitorenter_redo();
+  address generate_cont_preempt_rerun_compiler_adapter();
+
 #if INCLUDE_JFR
   void generate_jfr_stubs();
   // For c2: c_rarg0 is junk, call to runtime to write a checkpoint.
   // It returns a jobject handle to the event writer.
   // The handle is dereferenced and the return value is the event writer oop.
diff a/src/hotspot/cpu/x86/stubRoutines_x86.hpp b/src/hotspot/cpu/x86/stubRoutines_x86.hpp
--- a/src/hotspot/cpu/x86/stubRoutines_x86.hpp
+++ b/src/hotspot/cpu/x86/stubRoutines_x86.hpp
@@ -32,11 +32,11 @@
 static bool returns_to_call_stub(address return_pc) { return return_pc == _call_stub_return_address; }
 
 enum platform_dependent_constants {
   // simply increase sizes if too small (assembler will crash if too small)
   _initial_stubs_code_size      = 20000 WINDOWS_ONLY(+1000),
-  _continuation_stubs_code_size =  1000 LP64_ONLY(+1000),
+  _continuation_stubs_code_size =  1000 LP64_ONLY(+2000),
   // AVX512 intrinsics add more code in 64-bit VM,
   // Windows have more code to save/restore registers
   _compiler_stubs_code_size     = 20000 LP64_ONLY(+39000) WINDOWS_ONLY(+2000),
   _final_stubs_code_size        = 10000 LP64_ONLY(+20000) WINDOWS_ONLY(+2000) ZGC_ONLY(+20000)
 };
diff a/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp b/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp
--- a/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp
+++ b/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp
@@ -385,10 +385,34 @@
 
   __ dispatch_via(vtos, Interpreter::_normal_table.table_for(vtos));
   return entry;
 }
 
+address TemplateInterpreterGenerator::generate_cont_preempt_rerun_interpreter_adapter() {
+  if (!Continuations::enabled()) return nullptr;
+  address start = __ pc();
+
+  __ pop(rbp);
+
+  // We will return to the intermediate call made in call_VM skipping the restoration
+  // of bcp and locals done in InterpreterMacroAssembler::call_VM_base, so fix them here.
+  __ restore_bcp();
+  __ restore_locals();
+
+  // Get return address before adjusting rsp
+  __ movptr(rax, Address(rsp, 0));
+
+  // Restore stack bottom
+  __ movptr(rcx, Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize));
+  __ lea(rsp, Address(rbp, rcx, Address::times_ptr));
+  // and NULL it as marker that esp is now tos until next java call
+  __ movptr(Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize), NULL_WORD);
+
+  __ jmp(rax);
+
+  return start;
+}
 
 
 // Helpers for commoning out cases in the various type of method entries.
 //
 
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
@@ -786,10 +786,12 @@
   // x86_64 supports fast class initialization checks
   static bool supports_fast_class_init_checks() {
     return LP64_ONLY(true) NOT_LP64(false); // not implemented on x86_32
   }
 
+  static bool supports_cont_preemption() { return true; }
+
   // x86_64 supports secondary supers table
   constexpr static bool supports_secondary_supers_table() {
     return LP64_ONLY(true) NOT_LP64(false); // not implemented on x86_32
   }
 
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
@@ -13640,18 +13640,18 @@
                  $scr$$Register, noreg, noreg, $thread$$Register, nullptr, nullptr, nullptr, false, false);
   %}
   ins_pipe(pipe_slow);
 %}
 
-instruct cmpFastUnlock(eFlagsReg cr, eRegP object, eAXRegP box, eRegP tmp ) %{
+instruct cmpFastUnlock(eFlagsReg cr, eRegP object, eAXRegP box, eRegP tmp, eRegP scr) %{
   predicate(LockingMode != LM_LIGHTWEIGHT);
   match(Set cr (FastUnlock object box));
-  effect(TEMP tmp, USE_KILL box);
+  effect(TEMP tmp, TEMP scr, 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());
+    __ fast_unlock($object$$Register, $box$$Register, $tmp$$Register, $scr$$Register, ra_->C->use_rtm());
   %}
   ins_pipe(pipe_slow);
 %}
 
 instruct cmpFastLockLightweight(eFlagsReg cr, eRegP object, eBXRegP box, eAXRegI eax_reg, eRegP tmp, eRegP thread) %{
@@ -13665,19 +13665,19 @@
     __ 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) %{
+instruct cmpFastUnlockLightweight(eFlagsReg cr, eRegP object, eAXRegP eax_reg, eRegP tmp, eRegP scr, eRegP thread) %{
   predicate(LockingMode == LM_LIGHTWEIGHT);
   match(Set cr (FastUnlock object eax_reg));
-  effect(TEMP tmp, USE_KILL eax_reg, TEMP thread);
+  effect(TEMP tmp, TEMP scr, 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);
+    __ fast_unlock_lightweight($object$$Register, $eax_reg$$Register, $tmp$$Register, $scr$$Register, $thread$$Register);
   %}
   ins_pipe(pipe_slow);
 %}
 
 instruct mask_all_evexL_LT32(kReg dst, eRegL src) %{
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
@@ -12287,18 +12287,18 @@
                  $scr$$Register, noreg, noreg, r15_thread, nullptr, nullptr, nullptr, false, false);
   %}
   ins_pipe(pipe_slow);
 %}
 
-instruct cmpFastUnlock(rFlagsReg cr, rRegP object, rax_RegP box, rRegP tmp) %{
+instruct cmpFastUnlock(rFlagsReg cr, rRegP object, rax_RegP box, rRegP tmp, rRegP scr) %{
   predicate(LockingMode != LM_LIGHTWEIGHT);
   match(Set cr (FastUnlock object box));
-  effect(TEMP tmp, USE_KILL box);
+  effect(TEMP tmp, TEMP scr, 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());
+    __ fast_unlock($object$$Register, $box$$Register, $tmp$$Register, $scr$$Register, ra_->C->use_rtm());
   %}
   ins_pipe(pipe_slow);
 %}
 
 instruct cmpFastLockLightweight(rFlagsReg cr, rRegP object, rbx_RegP box, rax_RegI rax_reg, rRegP tmp) %{
@@ -12311,18 +12311,18 @@
     __ 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) %{
+instruct cmpFastUnlockLightweight(rFlagsReg cr, rRegP object, rax_RegP rax_reg, rRegP tmp, rRegP scr) %{
   predicate(LockingMode == LM_LIGHTWEIGHT);
   match(Set cr (FastUnlock object rax_reg));
-  effect(TEMP tmp, USE_KILL rax_reg);
+  effect(TEMP tmp, TEMP scr, 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);
+    __ fast_unlock_lightweight($object$$Register, $rax_reg$$Register, $tmp$$Register, $scr$$Register, r15_thread);
   %}
   ins_pipe(pipe_slow);
 %}
 
 
diff a/src/hotspot/cpu/zero/continuationFreezeThaw_zero.inline.hpp b/src/hotspot/cpu/zero/continuationFreezeThaw_zero.inline.hpp
--- a/src/hotspot/cpu/zero/continuationFreezeThaw_zero.inline.hpp
+++ b/src/hotspot/cpu/zero/continuationFreezeThaw_zero.inline.hpp
@@ -46,10 +46,14 @@
 
 void FreezeBase::adjust_interpreted_frame_unextended_sp(frame& f) {
   Unimplemented();
 }
 
+inline void FreezeBase::prepare_freeze_interpreted_top_frame(const frame& f) {
+  Unimplemented();
+}
+
 inline void FreezeBase::relativize_interpreted_frame_metadata(const frame& f, const frame& hf) {
   Unimplemented();
 }
 
 inline void FreezeBase::patch_pd(frame& hf, const frame& caller) {
@@ -81,10 +85,24 @@
 
 inline void ThawBase::patch_pd(frame& f, const frame& caller) {
   Unimplemented();
 }
 
+inline void ThawBase::patch_pd(frame& f, intptr_t* caller_sp) {
+  Unimplemented();
+}
+
+inline intptr_t* ThawBase::push_preempt_rerun_adapter(frame top, bool is_interpreted_frame) {
+  Unimplemented();
+  return nullptr;
+}
+
+inline intptr_t* ThawBase::push_preempt_monitorenter_redo(stackChunkOop chunk) {
+  Unimplemented();
+  return nullptr;
+}
+
 template <typename ConfigT>
 inline void Thaw<ConfigT>::patch_caller_links(intptr_t* sp, intptr_t* bottom) {
   Unimplemented();
 }
 
diff a/src/hotspot/cpu/zero/continuationHelper_zero.inline.hpp b/src/hotspot/cpu/zero/continuationHelper_zero.inline.hpp
--- a/src/hotspot/cpu/zero/continuationHelper_zero.inline.hpp
+++ b/src/hotspot/cpu/zero/continuationHelper_zero.inline.hpp
@@ -31,10 +31,14 @@
 static inline intptr_t** link_address(const frame& f) {
   Unimplemented();
   return nullptr;
 }
 
+static inline void patch_return_pc_with_preempt_stub(frame& f) {
+  Unimplemented();
+}
+
 inline int ContinuationHelper::frame_align_words(int size) {
   Unimplemented();
   return 0;
 }
 
@@ -58,15 +62,15 @@
 
 inline void ContinuationHelper::set_anchor_to_entry_pd(JavaFrameAnchor* anchor, ContinuationEntry* cont) {
   Unimplemented();
 }
 
-#ifdef ASSERT
 inline void ContinuationHelper::set_anchor_pd(JavaFrameAnchor* anchor, intptr_t* sp) {
   Unimplemented();
 }
 
+#ifdef ASSERT
 inline bool ContinuationHelper::Frame::assert_frame_laid_out(frame f) {
   Unimplemented();
   return false;
 }
 #endif
diff a/src/hotspot/share/c1/c1_MacroAssembler.hpp b/src/hotspot/share/c1/c1_MacroAssembler.hpp
--- a/src/hotspot/share/c1/c1_MacroAssembler.hpp
+++ b/src/hotspot/share/c1/c1_MacroAssembler.hpp
@@ -87,9 +87,9 @@
   int call_RT(Register oop_result1, Register metadata_result, address entry, Register arg1);
   int call_RT(Register oop_result1, Register metadata_result, address entry, Register arg1, Register arg2);
   int call_RT(Register oop_result1, Register metadata_result, address entry, Register arg1, Register arg2, Register arg3);
 
   void prologue(const char* name, bool must_gc_arguments);
-  void epilogue();
+  void epilogue(bool use_pop = false);
 };
 
 #endif // SHARE_C1_C1_MACROASSEMBLER_HPP
diff a/src/hotspot/share/c1/c1_Runtime1.hpp b/src/hotspot/share/c1/c1_Runtime1.hpp
--- a/src/hotspot/share/c1/c1_Runtime1.hpp
+++ b/src/hotspot/share/c1/c1_Runtime1.hpp
@@ -167,10 +167,12 @@
  public:
   // initialization
   static void initialize(BufferBlob* blob);
   static void initialize_pd();
 
+  static uint runtime_blob_current_thread_offset(frame f);
+
   // stubs
   static CodeBlob* blob_for (StubID id);
   static address   entry_for(StubID id)          { return blob_for(id)->code_begin(); }
   static const char* name_for (StubID id);
   static const char* name_for_address(address entry);
diff a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp
--- a/src/hotspot/share/classfile/javaClasses.cpp
+++ b/src/hotspot/share/classfile/javaClasses.cpp
@@ -1620,10 +1620,11 @@
 bool java_lang_Thread::is_in_VTMS_transition(oop java_thread) {
   return java_thread->bool_field_volatile(_jvmti_is_in_VTMS_transition_offset);
 }
 
 void java_lang_Thread::set_is_in_VTMS_transition(oop java_thread, bool val) {
+  assert(is_in_VTMS_transition(java_thread) != val, "already %s transition", val ? "inside" : "outside");
   java_thread->bool_field_put_volatile(_jvmti_is_in_VTMS_transition_offset, val);
 }
 
 int java_lang_Thread::is_in_VTMS_transition_offset() {
   return _jvmti_is_in_VTMS_transition_offset;
@@ -1955,16 +1956,22 @@
 
 int java_lang_VirtualThread::static_vthread_scope_offset;
 int java_lang_VirtualThread::_carrierThread_offset;
 int java_lang_VirtualThread::_continuation_offset;
 int java_lang_VirtualThread::_state_offset;
+int java_lang_VirtualThread::_next_offset;
+int java_lang_VirtualThread::_onWaitingList_offset;
+int java_lang_VirtualThread::_recheckInterval_offset;
 
 #define VTHREAD_FIELDS_DO(macro) \
   macro(static_vthread_scope_offset,       k, "VTHREAD_SCOPE",      continuationscope_signature, true);  \
   macro(_carrierThread_offset,             k, "carrierThread",      thread_signature,            false); \
   macro(_continuation_offset,              k, "cont",               continuation_signature,      false); \
-  macro(_state_offset,                     k, "state",              int_signature,               false)
+  macro(_state_offset,                     k, "state",              int_signature,               false); \
+  macro(_next_offset,                      k, "next",               vthread_signature,           false); \
+  macro(_onWaitingList_offset,             k, "onWaitingList",      byte_signature,              false); \
+  macro(_recheckInterval_offset,           k, "recheckInterval",    byte_signature,              false);
 
 
 void java_lang_VirtualThread::compute_offsets() {
   InstanceKlass* k = vmClasses::VirtualThread_klass();
   VTHREAD_FIELDS_DO(FIELD_COMPUTE_OFFSET);
@@ -1986,10 +1993,47 @@
 
 int java_lang_VirtualThread::state(oop vthread) {
   return vthread->int_field_acquire(_state_offset);
 }
 
+void java_lang_VirtualThread::set_state(oop vthread, int state) {
+  vthread->release_int_field_put(_state_offset, state);
+}
+
+oop java_lang_VirtualThread::next(oop vthread) {
+  return vthread->obj_field(_next_offset);
+}
+
+void java_lang_VirtualThread::set_next(oop vthread, oop next_vthread) {
+  vthread->obj_field_put(_next_offset, next_vthread);
+}
+
+bool java_lang_VirtualThread::set_onWaitingList(oop vthread, OopHandle& list_head) {
+  uint8_t* addr = vthread->field_addr<uint8_t>(_onWaitingList_offset);
+  uint8_t value = Atomic::load(addr);
+  assert(value == 0x00 || value == 0x01, "invariant");
+  if (value == 0x00) {
+    value = Atomic::cmpxchg(addr, (uint8_t)0x00, (uint8_t)0x01);
+    if (value == 0x00) {
+      for (;;) {
+        oop head = list_head.resolve();
+        java_lang_VirtualThread::set_next(vthread, head);
+        if (list_head.cmpxchg(head, vthread) == head) return true;
+      }
+    }
+  }
+  return false; // already on waiting list
+}
+
+jbyte java_lang_VirtualThread::recheckInterval(oop vthread) {
+  return vthread->byte_field(_recheckInterval_offset);
+}
+
+void java_lang_VirtualThread::set_recheckInterval(oop vthread, jbyte value) {
+  vthread->release_byte_field_put(_recheckInterval_offset, value);
+}
+
 JavaThreadStatus java_lang_VirtualThread::map_state_to_thread_status(int state) {
   JavaThreadStatus status = JavaThreadStatus::NEW;
   switch (state & ~SUSPENDED) {
     case NEW:
       status = JavaThreadStatus::NEW;
@@ -1999,29 +2043,39 @@
     case PARKING:
     case TIMED_PARKING:
     case UNPARKED:
     case YIELDING:
     case YIELDED:
+    case BLOCKING:
+    case UNBLOCKED:
       status = JavaThreadStatus::RUNNABLE;
       break;
     case PARKED:
     case PINNED:
       status = JavaThreadStatus::PARKED;
       break;
     case TIMED_PARKED:
     case TIMED_PINNED:
       status = JavaThreadStatus::PARKED_TIMED;
       break;
+    case BLOCKED:
+      status = JavaThreadStatus::BLOCKED_ON_MONITOR_ENTER;
+      break;
     case TERMINATED:
       status = JavaThreadStatus::TERMINATED;
       break;
     default:
       ShouldNotReachHere();
   }
   return status;
 }
 
+bool java_lang_VirtualThread::is_preempted(oop vthread) {
+  oop continuation = java_lang_VirtualThread::continuation(vthread);
+  return jdk_internal_vm_Continuation::is_preempted(continuation);
+}
+
 #if INCLUDE_CDS
 void java_lang_VirtualThread::serialize_offsets(SerializeClosure* f) {
    VTHREAD_FIELDS_DO(FIELD_SERIALIZE_OFFSET);
 }
 #endif
diff a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp
--- a/src/hotspot/share/classfile/javaClasses.hpp
+++ b/src/hotspot/share/classfile/javaClasses.hpp
@@ -519,10 +519,13 @@
  private:
   static int static_vthread_scope_offset;
   static int _carrierThread_offset;
   static int _continuation_offset;
   static int _state_offset;
+  static int _next_offset;
+  static int _onWaitingList_offset;
+  static int _recheckInterval_offset;
   JFR_ONLY(static int _jfr_epoch_offset;)
  public:
   enum {
     NEW           = 0,
     STARTED       = 1,
@@ -534,10 +537,13 @@
     TIMED_PARKED  = 7,
     TIMED_PINNED  = 8,
     UNPARKED      = 9,
     YIELDING      = 10,
     YIELDED       = 11,
+    BLOCKING      = 12,
+    BLOCKED       = 13,
+    UNBLOCKED     = 14,
     TERMINATED    = 99,
 
     // additional state bits
     SUSPENDED    = 1 << 8,   // suspended when unmounted
   };
@@ -553,10 +559,17 @@
 
   static oop vthread_scope();
   static oop carrier_thread(oop vthread);
   static oop continuation(oop vthread);
   static int state(oop vthread);
+  static void set_state(oop vthread, int state);
+  static oop next(oop vthread);
+  static void set_next(oop vthread, oop next_vthread);
+  static bool set_onWaitingList(oop vthread, OopHandle& list_head);
+  static jbyte recheckInterval(oop vthread);
+  static void set_recheckInterval(oop vthread, jbyte value);
+  static bool is_preempted(oop vthread);
   static JavaThreadStatus map_state_to_thread_status(int state);
 };
 
 
 // Interface to java.lang.Throwable objects
diff a/src/hotspot/share/classfile/vmIntrinsics.cpp b/src/hotspot/share/classfile/vmIntrinsics.cpp
--- a/src/hotspot/share/classfile/vmIntrinsics.cpp
+++ b/src/hotspot/share/classfile/vmIntrinsics.cpp
@@ -128,10 +128,11 @@
   case vmIntrinsics::_doubleToRawLongBits:
   case vmIntrinsics::_longBitsToDouble:
   case vmIntrinsics::_currentCarrierThread:
   case vmIntrinsics::_currentThread:
   case vmIntrinsics::_setCurrentThread:
+  case vmIntrinsics::_setLockId:
   case vmIntrinsics::_scopedValueCache:
   case vmIntrinsics::_setScopedValueCache:
   case vmIntrinsics::_dabs:
   case vmIntrinsics::_fabs:
   case vmIntrinsics::_iabs:
diff a/src/hotspot/share/classfile/vmIntrinsics.hpp b/src/hotspot/share/classfile/vmIntrinsics.hpp
--- a/src/hotspot/share/classfile/vmIntrinsics.hpp
+++ b/src/hotspot/share/classfile/vmIntrinsics.hpp
@@ -595,10 +595,12 @@
   do_intrinsic(_notifyJvmtiVThreadEnd, java_lang_VirtualThread, notifyJvmtiEnd_name, void_method_signature, F_RN)       \
   do_intrinsic(_notifyJvmtiVThreadMount, java_lang_VirtualThread, notifyJvmtiMount_name, bool_void_signature, F_RN)     \
   do_intrinsic(_notifyJvmtiVThreadUnmount, java_lang_VirtualThread, notifyJvmtiUnmount_name, bool_void_signature, F_RN) \
   do_intrinsic(_notifyJvmtiVThreadHideFrames, java_lang_VirtualThread, notifyJvmtiHideFrames_name, bool_void_signature, F_SN) \
   do_intrinsic(_notifyJvmtiVThreadDisableSuspend, java_lang_VirtualThread, notifyJvmtiDisableSuspend_name, bool_void_signature, F_SN) \
+  do_intrinsic(_setLockId, java_lang_VirtualThread, setLockId_name, long_void_signature, F_SN)                          \
+  do_name(     setLockId_name,                          "setLockId")                                                    \
                                                                                                                         \
   /* support for UnsafeConstants */                                                                                     \
   do_class(jdk_internal_misc_UnsafeConstants,      "jdk/internal/misc/UnsafeConstants")                                 \
                                                                                                                         \
   /* support for Unsafe */                                                                                              \
diff a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp
--- a/src/hotspot/share/classfile/vmSymbols.hpp
+++ b/src/hotspot/share/classfile/vmSymbols.hpp
@@ -543,10 +543,12 @@
   template(data_cache_line_flush_size_name,           "DATA_CACHE_LINE_FLUSH_SIZE")               \
   template(during_unsafe_access_name,                 "during_unsafe_access")                     \
   template(checkIndex_name,                           "checkIndex")                               \
   template(jfr_epoch_name,                            "jfr_epoch")                                \
   template(maxThawingSize_name,                       "maxThawingSize")                           \
+  template(lockStackSize_name,                        "lockStackSize")                            \
+  template(objectMonitor_name,                        "objectMonitor")                            \
                                                                                                   \
   /* name symbols needed by intrinsics */                                                         \
   VM_INTRINSICS_DO(VM_INTRINSIC_IGNORE, VM_SYMBOL_IGNORE, template, VM_SYMBOL_IGNORE, VM_ALIAS_IGNORE) \
                                                                                                   \
   /* common signatures names */                                                                   \
@@ -796,10 +798,13 @@
                                                                                                                   \
   /* Thread.dump_to_file jcmd */                                                                                  \
   template(jdk_internal_vm_ThreadDumper,           "jdk/internal/vm/ThreadDumper")                                \
   template(dumpThreads_name,                       "dumpThreads")                                                 \
   template(dumpThreadsToJson_name,                 "dumpThreadsToJson")                                           \
+                                                                                                                  \
+  /* Thread.vthread_scheduler */                                                                                  \
+  template(printDefaultScheduler_name,             "printDefaultScheduler")                                       \
 
   /*end*/
 
 // enum for figuring positions and size of Symbol::_vm_symbols[]
 enum class vmSymbolID : int {
diff a/src/hotspot/share/compiler/compilerEvent.cpp b/src/hotspot/share/compiler/compilerEvent.cpp
--- a/src/hotspot/share/compiler/compilerEvent.cpp
+++ b/src/hotspot/share/compiler/compilerEvent.cpp
@@ -55,66 +55,66 @@
 };
 
 Semaphore PhaseTypeGuard::_mutex_semaphore(1);
 
 // Table for mapping compiler phases names to int identifiers.
-static GrowableArray<const char*>* phase_names = nullptr;
+static GrowableArray<const char*>* cphase_names = nullptr;
 
 class CompilerPhaseTypeConstant : public JfrSerializer {
  public:
   void serialize(JfrCheckpointWriter& writer) {
     PhaseTypeGuard guard;
-    assert(phase_names != nullptr, "invariant");
-    assert(phase_names->is_nonempty(), "invariant");
-    const u4 nof_entries = phase_names->length();
+    assert(cphase_names != nullptr, "invariant");
+    assert(cphase_names->is_nonempty(), "invariant");
+    const u4 nof_entries = cphase_names->length();
     writer.write_count(nof_entries);
     for (u4 i = 0; i < nof_entries; i++) {
       writer.write_key(i);
-      writer.write(phase_names->at(i));
+      writer.write(cphase_names->at(i));
     }
   }
 };
 
-static int lookup_phase(const char* phase_name) {
-  for (int i = 0; i < phase_names->length(); i++) {
-    const char* name = phase_names->at(i);
-    if (strcmp(name, phase_name) == 0) {
+static int lookup_phase(const char* cphase_name) {
+  for (int i = 0; i < cphase_names->length(); i++) {
+    const char* name = cphase_names->at(i);
+    if (strcmp(name, cphase_name) == 0) {
       return i;
     }
   }
   return -1;
 }
 
-int CompilerEvent::PhaseEvent::get_phase_id(const char* phase_name, bool may_exist, bool use_strdup, bool sync) {
+int CompilerEvent::PhaseEvent::get_phase_id(const char* cphase_name, bool may_exist, bool use_strdup, bool sync) {
   int index;
   bool register_jfr_serializer = false;
   {
     PhaseTypeGuard guard(sync);
-    if (phase_names == nullptr) {
-      phase_names = new (mtInternal) GrowableArray<const char*>(100, mtCompiler);
+    if (cphase_names == nullptr) {
+      cphase_names = new (mtInternal) GrowableArray<const char*>(100, mtCompiler);
       register_jfr_serializer = true;
     } else if (may_exist) {
-      index = lookup_phase(phase_name);
+      index = lookup_phase(cphase_name);
       if (index != -1) {
         return index;
       }
     } else {
-      assert((index = lookup_phase(phase_name)) == -1, "phase name \"%s\" already registered: %d", phase_name, index);
+      assert((index = lookup_phase(cphase_name)) == -1, "phase name \"%s\" already registered: %d", cphase_name, index);
     }
 
-    index = phase_names->length();
-    phase_names->append(use_strdup ? os::strdup(phase_name) : phase_name);
+    index = cphase_names->length();
+    cphase_names->append(use_strdup ? os::strdup(cphase_name) : cphase_name);
   }
   if (register_jfr_serializer) {
     JfrSerializer::register_serializer(TYPE_COMPILERPHASETYPE, false, new CompilerPhaseTypeConstant());
   } else if (Jfr::is_recording()) {
     // serialize new phase.
     JfrCheckpointWriter writer;
     writer.write_type(TYPE_COMPILERPHASETYPE);
     writer.write_count(1);
     writer.write_key(index);
-    writer.write(phase_name);
+    writer.write(cphase_name);
   }
   return index;
 }
 
 // As part of event commit, a Method* is tagged as a function of an epoch.
diff a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h
--- a/src/hotspot/share/include/jvm.h
+++ b/src/hotspot/share/include/jvm.h
@@ -1149,10 +1149,19 @@
 JVM_VirtualThreadHideFrames(JNIEnv* env, jclass clazz, jboolean hide);
 
 JNIEXPORT void JNICALL
 JVM_VirtualThreadDisableSuspend(JNIEnv* env, jclass clazz, jboolean enter);
 
+JNIEXPORT void JNICALL
+JVM_VirtualThreadPinnedEvent(jint reasonCode, jstring reasonString);
+    
+JNIEXPORT void JNICALL
+JVM_SetLockId(JNIEnv* env, jclass clazz, jlong tid);
+
+JNIEXPORT jobject JNICALL
+JVM_TakeVirtualThreadListToUnblock(JNIEnv* env, jclass ignored);
+
 /*
  * Core reflection support.
  */
 JNIEXPORT jint JNICALL
 JVM_GetClassFileVersion(JNIEnv *env, jclass current);
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
@@ -740,15 +740,16 @@
   current->last_frame().interpreter_frame_verify_monitor(elem);
 #endif
   Handle h_obj(current, elem->obj());
   assert(Universe::heap()->is_in_or_null(h_obj()),
          "must be null or an object");
+  ThreadOnMonitorEnter tme(current);
   ObjectSynchronizer::enter(h_obj, elem->lock(), current);
   assert(Universe::heap()->is_in_or_null(elem->obj()),
          "must be null or an object");
 #ifdef ASSERT
-  current->last_frame().interpreter_frame_verify_monitor(elem);
+  if (!current->preempting()) 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*.
@@ -761,10 +762,11 @@
 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");
+  ThreadOnMonitorEnter tme(current);
   ObjectSynchronizer::enter(h_obj, nullptr, current);
   return;
 JRT_END
 
 JRT_LEAF(void, InterpreterRuntime::monitorexit(BasicObjectLock* elem))
diff a/src/hotspot/share/interpreter/templateInterpreter.cpp b/src/hotspot/share/interpreter/templateInterpreter.cpp
--- a/src/hotspot/share/interpreter/templateInterpreter.cpp
+++ b/src/hotspot/share/interpreter/templateInterpreter.cpp
@@ -210,10 +210,11 @@
 address    TemplateInterpreter::_throw_ArithmeticException_entry            = nullptr;
 address    TemplateInterpreter::_throw_ClassCastException_entry             = nullptr;
 address    TemplateInterpreter::_throw_NullPointerException_entry           = nullptr;
 address    TemplateInterpreter::_throw_StackOverflowError_entry             = nullptr;
 address    TemplateInterpreter::_throw_exception_entry                      = nullptr;
+address    TemplateInterpreter::_cont_preempt_rerun_interpreter_adapter     = nullptr;
 
 #ifndef PRODUCT
 EntryPoint TemplateInterpreter::_trace_code;
 #endif // !PRODUCT
 EntryPoint TemplateInterpreter::_return_entry[TemplateInterpreter::number_of_return_entries];
diff a/src/hotspot/share/interpreter/templateInterpreter.hpp b/src/hotspot/share/interpreter/templateInterpreter.hpp
--- a/src/hotspot/share/interpreter/templateInterpreter.hpp
+++ b/src/hotspot/share/interpreter/templateInterpreter.hpp
@@ -109,10 +109,12 @@
   static address    _throw_NullPointerException_entry;
   static address    _throw_exception_entry;
 
   static address    _throw_StackOverflowError_entry;
 
+  static address    _cont_preempt_rerun_interpreter_adapter;
+
   static address    _remove_activation_entry;                   // continuation address if an exception is not handled by current frame
   static address    _remove_activation_preserving_args_entry;   // continuation address when current frame is being popped
 
 #ifndef PRODUCT
   static EntryPoint _trace_code;
@@ -152,10 +154,12 @@
   static address    throw_exception_entry()                     { return _throw_exception_entry; }
   static address    throw_ArithmeticException_entry()           { return _throw_ArithmeticException_entry; }
   static address    throw_NullPointerException_entry()          { return _throw_NullPointerException_entry; }
   static address    throw_StackOverflowError_entry()            { return _throw_StackOverflowError_entry; }
 
+  static address    cont_preempt_rerun_interpreter_adapter()    { return _cont_preempt_rerun_interpreter_adapter; }
+
   // Code generation
 #ifndef PRODUCT
   static address    trace_code    (TosState state)              { return _trace_code.entry(state); }
 #endif // !PRODUCT
   static address*   dispatch_table(TosState state)              { return _active_table.table_for(state); }
diff a/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp b/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp
--- a/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp
+++ b/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp
@@ -173,11 +173,13 @@
     Interpreter::_throw_ClassCastException_entry             = generate_ClassCastException_handler();
     Interpreter::_throw_NullPointerException_entry           = generate_exception_handler("java/lang/NullPointerException", nullptr);
     Interpreter::_throw_StackOverflowError_entry             = generate_StackOverflowError_handler();
   }
 
-
+  { CodeletMark cm(_masm, "preemption rerun adapter");
+    Interpreter::_cont_preempt_rerun_interpreter_adapter = generate_cont_preempt_rerun_interpreter_adapter();
+  }
 
 #define method_entry(kind)                                                                          \
   { CodeletMark cm(_masm, "method entry point (kind = " #kind ")");                                 \
     Interpreter::_entry_table[Interpreter::kind] = generate_method_entry(Interpreter::kind, false); \
   }
diff a/src/hotspot/share/interpreter/templateInterpreterGenerator.hpp b/src/hotspot/share/interpreter/templateInterpreterGenerator.hpp
--- a/src/hotspot/share/interpreter/templateInterpreterGenerator.hpp
+++ b/src/hotspot/share/interpreter/templateInterpreterGenerator.hpp
@@ -54,10 +54,11 @@
   address generate_ArrayIndexOutOfBounds_handler();
   address generate_return_entry_for(TosState state, int step, size_t index_size);
   address generate_earlyret_entry_for(TosState state);
   address generate_deopt_entry_for(TosState state, int step, address continuation = nullptr);
   address generate_safept_entry_for(TosState state, address runtime_entry);
+  address generate_cont_preempt_rerun_interpreter_adapter();
   void    generate_throw_exception();
 
   void lock_method();
 
   void bang_stack_shadow_pages(bool native_call);
diff a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml
--- a/src/hotspot/share/jfr/metadata/metadata.xml
+++ b/src/hotspot/share/jfr/metadata/metadata.xml
@@ -154,10 +154,15 @@
 
   <Event name="ContinuationThawSlow" experimental="true" category="Java Virtual Machine, Runtime" label="Continuation Thaw Slow" thread="true" stackTrace="false" startTime="false">
     <Field type="ulong" name="id" label="Continuation ID" />
   </Event>
 
+  <Event name="VirtualThreadPinned" category="Java Virtual Machine, Runtime" label="Virtual Thread Pinned" thread="true" stackTrace="true" startTime="false">
+    <Field type="string" name="pinnedReason" label="Pinned Reason" />
+    <Field type="Thread" name="carrierThread" label="Carrier Thread" />
+  </Event>
+
   <Event name="ReservedStackActivation" category="Java Virtual Machine, Runtime" label="Reserved Stack Activation"
     description="Activation of Reserved Stack Area caused by stack overflow with ReservedStackAccess annotated method in call stack" thread="true" stackTrace="true"
     startTime="false">
     <Field type="Method" name="method" label="Java Method" />
   </Event>
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
@@ -35,10 +35,11 @@
 #include "oops/methodCounters.hpp"
 #include "oops/objArrayKlass.hpp"
 #include "prims/jvmtiThreadState.hpp"
 #include "runtime/deoptimization.hpp"
 #include "runtime/flags/jvmFlag.hpp"
+#include "runtime/objectMonitor.hpp"
 #include "runtime/osThread.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "runtime/stubRoutines.hpp"
 #include "runtime/vm_version.hpp"
 #if INCLUDE_G1GC
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
@@ -1101,10 +1101,26 @@
                                jt->name(), external_name(), init_thread_name());
       }
 
       wait = true;
       jt->set_class_to_be_initialized(this);
+
+#if INCLUDE_JFR
+      ContinuationEntry* ce = jt->last_continuation();
+      if (ce != nullptr && ce->is_virtual_thread()) {
+        EventVirtualThreadPinned e;
+        if (e.should_commit()) {
+          ResourceMark rm(jt);
+          char reason[256];
+          jio_snprintf(reason, sizeof reason, "Waiting for initialization of klass %s", external_name());
+          e.set_pinnedReason(reason);
+          e.set_carrierThread(JFR_JVM_THREAD_ID(THREAD));
+          e.commit();
+        }
+      }
+ #endif
+
       ml.wait();
       jt->set_class_to_be_initialized(nullptr);
     }
 
     // Step 3
diff a/src/hotspot/share/oops/instanceStackChunkKlass.cpp b/src/hotspot/share/oops/instanceStackChunkKlass.cpp
--- a/src/hotspot/share/oops/instanceStackChunkKlass.cpp
+++ b/src/hotspot/share/oops/instanceStackChunkKlass.cpp
@@ -161,10 +161,23 @@
   }
   OopIterateStackChunkFrameClosure frame_closure(closure, mr);
   chunk->iterate_stack(&frame_closure);
 }
 
+template <typename OopT>
+void InstanceStackChunkKlass::oop_oop_iterate_lockstack(stackChunkOop chunk, OopIterateClosure* closure, MemRegion mr) {
+  StackChunkOopIterateFilterClosure<OopIterateClosure> cl(closure, mr);
+  if (chunk->has_bitmap()) {
+    chunk->iterate_lockstack<OopT>(&cl);
+  } else {
+    chunk->iterate_lockstack<oop>(&cl);
+  }
+}
+
+template void InstanceStackChunkKlass::oop_oop_iterate_lockstack<oop>(stackChunkOop chunk, OopIterateClosure* closure, MemRegion mr);
+template void InstanceStackChunkKlass::oop_oop_iterate_lockstack<narrowOop>(stackChunkOop chunk, OopIterateClosure* closure, MemRegion mr);
+
 #ifdef ASSERT
 
 class DescribeStackChunkClosure {
   stackChunkOop _chunk;
   FrameValues _values;
@@ -222,11 +235,11 @@
   template <ChunkFrames frame_kind, typename RegisterMapT>
   bool do_frame(const StackChunkFrameStream<frame_kind>& fs, const RegisterMapT* map) {
     frame f = fs.to_frame();
     _st->print_cr("-- frame sp: " PTR_FORMAT " interpreted: %d size: %d argsize: %d",
                   p2i(fs.sp()), fs.is_interpreted(), f.frame_size(),
-                  fs.is_interpreted() ? 0 : f.compiled_frame_stack_argsize());
+                  fs.is_interpreted() || fs.is_stub() ? 0 : f.compiled_frame_stack_argsize());
   #ifdef ASSERT
     f.print_value_on(_st, nullptr);
   #else
     f.print_on(_st);
   #endif
diff a/src/hotspot/share/oops/instanceStackChunkKlass.hpp b/src/hotspot/share/oops/instanceStackChunkKlass.hpp
--- a/src/hotspot/share/oops/instanceStackChunkKlass.hpp
+++ b/src/hotspot/share/oops/instanceStackChunkKlass.hpp
@@ -175,10 +175,13 @@
   inline void oop_oop_iterate_stack_bounded(stackChunkOop chunk, OopClosureType* closure, MemRegion mr);
 
   template <typename T, class OopClosureType>
   inline void oop_oop_iterate_stack_with_bitmap(stackChunkOop chunk, OopClosureType* closure, intptr_t* start, intptr_t* end);
 
+  template <typename OopT>
+  void oop_oop_iterate_lockstack(stackChunkOop chunk, OopIterateClosure* closure, MemRegion mr);
+
   void do_methods(stackChunkOop chunk, OopIterateClosure* cl);
 
   void oop_oop_iterate_stack_slow(stackChunkOop chunk, OopIterateClosure* closure, MemRegion mr);
 };
 
diff a/src/hotspot/share/oops/instanceStackChunkKlass.inline.hpp b/src/hotspot/share/oops/instanceStackChunkKlass.inline.hpp
--- a/src/hotspot/share/oops/instanceStackChunkKlass.inline.hpp
+++ b/src/hotspot/share/oops/instanceStackChunkKlass.inline.hpp
@@ -63,18 +63,20 @@
   if (Devirtualizer::do_metadata(closure)) {
     Devirtualizer::do_klass(closure, this);
   }
   oop_oop_iterate_stack<T>(chunk, closure);
   oop_oop_iterate_header<T>(chunk, closure);
+  oop_oop_iterate_lockstack<T>(chunk, closure, chunk->range());
 }
 
 template <typename T, class OopClosureType>
 void InstanceStackChunkKlass::oop_oop_iterate_reverse(oop obj, OopClosureType* closure) {
   assert(!Devirtualizer::do_metadata(closure), "Code to handle metadata is not implemented");
   stackChunkOop chunk = stackChunkOopDesc::cast(obj);
   oop_oop_iterate_stack<T>(chunk, closure);
   oop_oop_iterate_header<T>(chunk, closure);
+  oop_oop_iterate_lockstack<T>(chunk, closure, chunk->range());
 }
 
 template <typename T, class OopClosureType>
 void InstanceStackChunkKlass::oop_oop_iterate_bounded(oop obj, OopClosureType* closure, MemRegion mr) {
   stackChunkOop chunk = stackChunkOopDesc::cast(obj);
@@ -83,10 +85,11 @@
       Devirtualizer::do_klass(closure, this);
     }
   }
   oop_oop_iterate_stack_bounded<T>(chunk, closure, mr);
   oop_oop_iterate_header_bounded<T>(chunk, closure, mr);
+  oop_oop_iterate_lockstack<T>(chunk, closure, mr);
 }
 
 template <typename T, class OopClosureType>
 void InstanceStackChunkKlass::oop_oop_iterate_header(stackChunkOop chunk, OopClosureType* closure) {
   T* parent_addr = chunk->field_addr<T>(jdk_internal_vm_StackChunk::parent_offset());
diff a/src/hotspot/share/oops/oopHandle.hpp b/src/hotspot/share/oops/oopHandle.hpp
--- a/src/hotspot/share/oops/oopHandle.hpp
+++ b/src/hotspot/share/oops/oopHandle.hpp
@@ -69,10 +69,11 @@
   inline void release(OopStorage* storage);
 
   inline void replace(oop obj);
 
   inline oop xchg(oop new_value);
+  inline oop cmpxchg(oop old_value, oop new_value);
 
   oop* ptr_raw() const { return _obj; }
 };
 
 #endif // SHARE_OOPS_OOPHANDLE_HPP
diff a/src/hotspot/share/oops/oopHandle.inline.hpp b/src/hotspot/share/oops/oopHandle.inline.hpp
--- a/src/hotspot/share/oops/oopHandle.inline.hpp
+++ b/src/hotspot/share/oops/oopHandle.inline.hpp
@@ -63,6 +63,10 @@
 
 inline oop OopHandle::xchg(oop new_value) {
   return NativeAccess<MO_SEQ_CST>::oop_atomic_xchg(_obj, new_value);
 }
 
+inline oop OopHandle::cmpxchg(oop old_value, oop new_value) {
+  return NativeAccess<MO_SEQ_CST>::oop_atomic_cmpxchg(_obj, old_value, new_value);
+}
+
 #endif // SHARE_OOPS_OOPHANDLE_INLINE_HPP
diff a/src/hotspot/share/oops/stackChunkOop.cpp b/src/hotspot/share/oops/stackChunkOop.cpp
--- a/src/hotspot/share/oops/stackChunkOop.cpp
+++ b/src/hotspot/share/oops/stackChunkOop.cpp
@@ -63,10 +63,25 @@
       visitor.oops_do(&_f, _map, _f.oop_map());
     }
   }
 };
 
+class LockStackOopIterator : public OopIterator {
+private:
+  const stackChunkOop _chunk;
+public:
+  LockStackOopIterator(const stackChunkOop chunk) : _chunk(chunk) {}
+
+  virtual void oops_do(OopClosure* cl) override {
+    int cnt = _chunk->lockStackSize();
+    oop* lockstack_start = (oop*)_chunk->start_address();
+    for (int i = 0; i < cnt; i++) {
+      cl->do_oop(&lockstack_start[i]);
+    }
+  }
+};
+
 frame stackChunkOopDesc::top_frame(RegisterMap* map) {
   assert(!is_empty(), "");
   StackChunkFrameStream<ChunkFrames::Mixed> fs(this);
 
   map->set_stack_chunk(this);
@@ -222,10 +237,18 @@
     FrameOopIterator<RegisterMapT> iterator(fr, map);
     bs_chunk->encode_gc_mode(_chunk, &iterator);
 
     return true;
   }
+
+  bool do_lockstack() {
+    BarrierSetStackChunk* bs_chunk = BarrierSet::barrier_set()->barrier_set_stack_chunk();
+    LockStackOopIterator iterator(_chunk);
+    bs_chunk->encode_gc_mode(_chunk, &iterator);
+
+    return true;
+  }
 };
 
 bool stackChunkOopDesc::try_acquire_relativization() {
   for (;;) {
     // We use an acquiring load when reading the flags to ensure that if we leave this
@@ -296,10 +319,11 @@
   }
 
   DerivedPointersSupport::RelativizeClosure derived_cl;
   EncodeGCModeConcurrentFrameClosure<decltype(derived_cl)> frame_cl(this, &derived_cl);
   iterate_stack(&frame_cl);
+  frame_cl.do_lockstack();
 
   release_relativization();
 }
 
 class TransformStackChunkClosure {
@@ -318,10 +342,18 @@
     FrameOopIterator<RegisterMapT> iterator(fr, map);
     bs_chunk->encode_gc_mode(_chunk, &iterator);
 
     return true;
   }
+
+  bool do_lockstack() {
+    BarrierSetStackChunk* bs_chunk = BarrierSet::barrier_set()->barrier_set_stack_chunk();
+    LockStackOopIterator iterator(_chunk);
+    bs_chunk->encode_gc_mode(_chunk, &iterator);
+
+    return true;
+  }
 };
 
 void stackChunkOopDesc::transform() {
   assert(!is_gc_mode(), "Should only be called once per chunk");
   set_gc_mode(true);
@@ -330,10 +362,11 @@
   set_has_bitmap(true);
   bitmap().clear();
 
   TransformStackChunkClosure closure(this);
   iterate_stack(&closure);
+  closure.do_lockstack();
 }
 
 template <stackChunkOopDesc::BarrierType barrier, bool compressedOopsWithBitmap>
 class BarrierClosure: public OopClosure {
   NOT_PRODUCT(intptr_t* _sp;)
@@ -406,10 +439,39 @@
 }
 
 template void stackChunkOopDesc::fix_thawed_frame(const frame& f, const RegisterMap* map);
 template void stackChunkOopDesc::fix_thawed_frame(const frame& f, const SmallRegisterMap* map);
 
+void stackChunkOopDesc::copy_lockstack(oop* dst) {
+  int cnt = lockStackSize();
+
+  if (!(is_gc_mode() || requires_barriers())) {
+    oop* lockstack_start = (oop*)start_address();
+    for (int i = 0; i < cnt; i++) {
+      dst[i] = lockstack_start[i];
+      assert(oopDesc::is_oop(dst[i]), "not an oop");
+    }
+    return;
+  }
+
+  if (has_bitmap() && UseCompressedOops) {
+    intptr_t* lockstack_start = start_address();
+    for (int i = 0; i < cnt; i++) {
+      oop mon_owner = HeapAccess<>::oop_load((narrowOop*)&lockstack_start[i]);
+      assert(oopDesc::is_oop(mon_owner), "not an oop");
+      dst[i] = mon_owner;
+    }
+  } else {
+    intptr_t* lockstack_start = start_address();
+    for (int i = 0; i < cnt; i++) {
+      oop mon_owner = HeapAccess<>::oop_load((oop*)&lockstack_start[i]);
+      assert(oopDesc::is_oop(mon_owner), "not an oop");
+      dst[i] = mon_owner;
+    }
+  }
+}
+
 void stackChunkOopDesc::print_on(bool verbose, outputStream* st) const {
   if (*((juint*)this) == badHeapWordVal) {
     st->print_cr("BAD WORD");
   } else {
     InstanceStackChunkKlass::print_chunk(const_cast<stackChunkOopDesc*>(this), verbose, st);
@@ -454,13 +516,13 @@
   int _num_oops;
   int _num_frames;
   int _num_interpreted_frames;
   int _num_i2c;
 
-  VerifyStackChunkFrameClosure(stackChunkOop chunk, int num_frames, int size)
+  VerifyStackChunkFrameClosure(stackChunkOop chunk)
     : _chunk(chunk), _sp(nullptr), _cb(nullptr), _callee_interpreted(false),
-      _size(size), _argsize(0), _num_oops(0), _num_frames(num_frames), _num_interpreted_frames(0), _num_i2c(0) {}
+      _size(0), _argsize(0), _num_oops(0), _num_frames(0), _num_interpreted_frames(0), _num_i2c(0) {}
 
   template <ChunkFrames frame_kind, typename RegisterMapT>
   bool do_frame(const StackChunkFrameStream<frame_kind>& f, const RegisterMapT* map) {
     _sp = f.sp();
     _cb = f.cb();
@@ -550,15 +612,12 @@
   int size = stack_size() - argsize() - sp();
   assert(size >= 0, "");
   assert((size == 0) == is_empty(), "");
 
   const StackChunkFrameStream<ChunkFrames::Mixed> first(this);
-  const bool has_safepoint_stub_frame = first.is_stub();
 
-  VerifyStackChunkFrameClosure closure(this,
-                                       has_safepoint_stub_frame ? 1 : 0, // Iterate_stack skips the safepoint stub
-                                       has_safepoint_stub_frame ? first.frame_size() : 0);
+  VerifyStackChunkFrameClosure closure(this);
   iterate_stack(&closure);
 
   assert(!is_empty() || closure._cb == nullptr, "");
   if (closure._cb != nullptr && closure._cb->is_nmethod()) {
     assert(argsize() ==
diff a/src/hotspot/share/oops/stackChunkOop.hpp b/src/hotspot/share/oops/stackChunkOop.hpp
--- a/src/hotspot/share/oops/stackChunkOop.hpp
+++ b/src/hotspot/share/oops/stackChunkOop.hpp
@@ -56,10 +56,12 @@
   static const uint8_t FLAG_HAS_INTERPRETED_FRAMES = 1 << 0;
   static const uint8_t FLAG_CLAIM_RELATIVIZE = 1 << 1; // Only one thread claims relativization of derived pointers
   static const uint8_t FLAG_NOTIFY_RELATIVIZE = 1 << 2; // Someone is waiting for relativization to complete
   static const uint8_t FLAG_GC_MODE = 1 << 3; // Once true it and FLAG_HAS_INTERPRETED_FRAMES can't change
   static const uint8_t FLAG_HAS_BITMAP = 1 << 4; // Can only be true if FLAG_GC_MODE is true
+  static const uint8_t FLAG_PREEMPTED = 1 << 5; // Continuation was preempted
+  static const uint8_t FLAG_HAS_LOCKSTACK = 1 << 6; // LockStack was copied into stackChunk
 
   bool try_acquire_relativization();
   void release_relativization();
 
 public:
@@ -89,10 +91,16 @@
   inline void release_set_flags(uint8_t value);
 
   inline int max_thawing_size() const;
   inline void set_max_thawing_size(int value);
 
+  inline uint8_t lockStackSize() const;
+  inline void set_lockStackSize(uint8_t value);
+
+  inline ObjectMonitor* objectMonitor() const;
+  inline void set_objectMonitor(ObjectMonitor* monitor);
+
   inline oop cont() const;
   template<typename P>
   inline oop cont() const;
   inline void set_cont(oop value);
   template<typename P>
@@ -124,10 +132,17 @@
   inline void clear_flags();
 
   inline bool has_mixed_frames() const;
   inline void set_has_mixed_frames(bool value);
 
+  inline bool is_preempted() const;
+  inline void set_is_preempted(bool value);
+  inline bool preempted_on_monitorenter() const;
+
+  inline bool has_lockStack() const;
+  inline void set_has_lockStack(bool value);
+
   inline bool is_gc_mode() const;
   inline bool is_gc_mode_acquire() const;
   inline void set_gc_mode(bool value);
 
   inline bool has_bitmap() const;
@@ -144,10 +159,15 @@
   inline void do_barriers(const StackChunkFrameStream<frames>& f, const RegisterMapT* map);
 
   template <typename RegisterMapT>
   void fix_thawed_frame(const frame& f, const RegisterMapT* map);
 
+  void copy_lockstack(oop* start);
+
+  template <typename OopT, class StackChunkLockStackClosureType>
+  inline void iterate_lockstack(StackChunkLockStackClosureType* closure);
+
   template <class StackChunkFrameClosureType>
   inline void iterate_stack(StackChunkFrameClosureType* closure);
 
   // Derived pointers are relativized, with respect to their oop base.
   void relativize_derived_pointers_concurrently();
diff a/src/hotspot/share/oops/stackChunkOop.inline.hpp b/src/hotspot/share/oops/stackChunkOop.inline.hpp
--- a/src/hotspot/share/oops/stackChunkOop.inline.hpp
+++ b/src/hotspot/share/oops/stackChunkOop.inline.hpp
@@ -86,10 +86,16 @@
 inline void stackChunkOopDesc::set_max_thawing_size(int value)  {
   assert(value >= 0, "size must be >= 0");
   jdk_internal_vm_StackChunk::set_maxThawingSize(this, (jint)value);
 }
 
+inline uint8_t stackChunkOopDesc::lockStackSize() const         { return jdk_internal_vm_StackChunk::lockStackSize(as_oop()); }
+inline void stackChunkOopDesc::set_lockStackSize(uint8_t value) { jdk_internal_vm_StackChunk::set_lockStackSize(this, value); }
+
+inline ObjectMonitor* stackChunkOopDesc::objectMonitor() const       { return (ObjectMonitor*)jdk_internal_vm_StackChunk::objectMonitor(as_oop()); }
+inline void stackChunkOopDesc::set_objectMonitor(ObjectMonitor* mon) { jdk_internal_vm_StackChunk::set_objectMonitor(this, (address)mon); }
+
 inline oop stackChunkOopDesc::cont() const                {
   if (UseZGC && !ZGenerational) {
     assert(!UseCompressedOops, "Non-generational ZGC does not support compressed oops");
     // The state of the cont oop is used by XCollectedHeap::requires_barriers,
     // to determine the age of the stackChunkOopDesc. For that to work, it is
@@ -164,14 +170,21 @@
   set_flags(0);
 }
 
 inline bool stackChunkOopDesc::has_mixed_frames() const { return is_flag(FLAG_HAS_INTERPRETED_FRAMES); }
 inline void stackChunkOopDesc::set_has_mixed_frames(bool value) {
-  assert((flags() & ~FLAG_HAS_INTERPRETED_FRAMES) == 0, "other flags should not be set");
+  assert((flags() & ~(FLAG_HAS_INTERPRETED_FRAMES | FLAG_PREEMPTED)) == 0, "other flags should not be set");
   set_flag(FLAG_HAS_INTERPRETED_FRAMES, value);
 }
 
+inline bool stackChunkOopDesc::is_preempted() const         { return is_flag(FLAG_PREEMPTED); }
+inline void stackChunkOopDesc::set_is_preempted(bool value) { set_flag(FLAG_PREEMPTED, value); }
+inline bool stackChunkOopDesc::preempted_on_monitorenter() const   { return objectMonitor() != nullptr; }
+
+inline bool stackChunkOopDesc::has_lockStack() const         { return is_flag(FLAG_HAS_LOCKSTACK); }
+inline void stackChunkOopDesc::set_has_lockStack(bool value) { set_flag(FLAG_HAS_LOCKSTACK, value); }
+
 inline bool stackChunkOopDesc::is_gc_mode() const                  { return is_flag(FLAG_GC_MODE); }
 inline bool stackChunkOopDesc::is_gc_mode_acquire() const          { return is_flag_acquire(FLAG_GC_MODE); }
 inline void stackChunkOopDesc::set_gc_mode(bool value)             { set_flag(FLAG_GC_MODE, value); }
 
 inline bool stackChunkOopDesc::has_bitmap() const                  { return is_flag(FLAG_HAS_BITMAP); }
@@ -190,10 +203,22 @@
     f.handle_deopted();
   }
   do_barriers0<barrier>(f, map);
 }
 
+template <typename OopT, class StackChunkLockStackClosureType>
+inline void stackChunkOopDesc::iterate_lockstack(StackChunkLockStackClosureType* closure) {
+  if (LockingMode != LM_LIGHTWEIGHT) {
+    return;
+  }
+  int cnt = lockStackSize();
+  intptr_t* lockstart_addr = start_address();
+  for (int i = 0; i < cnt; i++) {
+    closure->do_oop((OopT*)&lockstart_addr[i]);
+  }
+}
+
 template <class StackChunkFrameClosureType>
 inline void stackChunkOopDesc::iterate_stack(StackChunkFrameClosureType* closure) {
   has_mixed_frames() ? iterate_stack<ChunkFrames::Mixed>(closure)
                      : iterate_stack<ChunkFrames::CompiledOnly>(closure);
 }
@@ -210,19 +235,17 @@
     RegisterMap full_map(nullptr,
                          RegisterMap::UpdateMap::include,
                          RegisterMap::ProcessFrames::skip,
                          RegisterMap::WalkContinuation::include);
     full_map.set_include_argument_oops(false);
+    closure->do_frame(f, map);
 
     f.next(&full_map);
-
-    assert(!f.is_done(), "");
-    assert(f.is_compiled(), "");
+    if (f.is_done()) return;
 
     should_continue = closure->do_frame(f, &full_map);
-    f.next(map);
-    f.handle_deopted(); // the stub caller might be deoptimized (as it's not at a call)
+    f.next(&map);
   }
   assert(!f.is_stub(), "");
 
   for(; should_continue && !f.is_done(); f.next(map)) {
     if (frame_kind == ChunkFrames::Mixed) {
@@ -279,11 +302,11 @@
 inline MemRegion stackChunkOopDesc::range() {
   return MemRegion((HeapWord*)this, size());
 }
 
 inline int stackChunkOopDesc::relativize_usp_offset(const frame& fr, const int usp_offset_in_bytes) const {
-  assert(fr.is_compiled_frame() || fr.cb()->is_safepoint_stub(), "");
+  assert(fr.is_compiled_frame() || fr.cb()->is_runtime_stub(), "");
   assert(is_in_chunk(fr.unextended_sp()), "");
 
   intptr_t* base = fr.real_fp(); // equal to the caller's sp
   intptr_t* loc = (intptr_t*)((address)fr.unextended_sp() + usp_offset_in_bytes);
   assert(base > loc, "");
diff a/src/hotspot/share/opto/c2_CodeStubs.hpp b/src/hotspot/share/opto/c2_CodeStubs.hpp
--- a/src/hotspot/share/opto/c2_CodeStubs.hpp
+++ b/src/hotspot/share/opto/c2_CodeStubs.hpp
@@ -99,18 +99,19 @@
 
 class C2FastUnlockLightweightStub : public C2CodeStub {
 private:
   Register _obj;
   Register _mark;
-  Register _t;
+  Register _t1;
+  Register _t2;
   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) {}
+  C2FastUnlockLightweightStub(Register obj, Register mark, Register t1, Register t2, Register thread) : C2CodeStub(),
+    _obj(obj), _mark(mark), _t1(t1), _t2(t2), _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; }
diff a/src/hotspot/share/opto/c2compiler.cpp b/src/hotspot/share/opto/c2compiler.cpp
--- a/src/hotspot/share/opto/c2compiler.cpp
+++ b/src/hotspot/share/opto/c2compiler.cpp
@@ -727,10 +727,11 @@
   case vmIntrinsics::_storeStoreFence:
   case vmIntrinsics::_fullFence:
   case vmIntrinsics::_currentCarrierThread:
   case vmIntrinsics::_currentThread:
   case vmIntrinsics::_setCurrentThread:
+  case vmIntrinsics::_setLockId:
   case vmIntrinsics::_scopedValueCache:
   case vmIntrinsics::_setScopedValueCache:
 #ifdef JFR_HAVE_INTRINSICS
   case vmIntrinsics::_counterTime:
   case vmIntrinsics::_getEventWriter:
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
@@ -476,10 +476,11 @@
   case vmIntrinsics::_onSpinWait:               return inline_onspinwait();
 
   case vmIntrinsics::_currentCarrierThread:     return inline_native_currentCarrierThread();
   case vmIntrinsics::_currentThread:            return inline_native_currentThread();
   case vmIntrinsics::_setCurrentThread:         return inline_native_setCurrentThread();
+  case vmIntrinsics::_setLockId:                return inline_native_setLockId();
 
   case vmIntrinsics::_scopedValueCache:          return inline_native_scopedValueCache();
   case vmIntrinsics::_setScopedValueCache:       return inline_native_setScopedValueCache();
 
 #if INCLUDE_JVMTI
@@ -3636,14 +3637,27 @@
   Node* thread_obj_handle
     = make_load(nullptr, p, p->bottom_type()->is_ptr(), T_OBJECT, MemNode::unordered);
   thread_obj_handle = _gvn.transform(thread_obj_handle);
   const TypePtr *adr_type = _gvn.type(thread_obj_handle)->isa_ptr();
   access_store_at(nullptr, thread_obj_handle, adr_type, arr, _gvn.type(arr), T_OBJECT, IN_NATIVE | MO_UNORDERED);
+
+  // Change the lock_id of the JavaThread
+  Node* tid = load_field_from_object(arr, "tid", "J");
+  Node* thread_id_offset = basic_plus_adr(thread, in_bytes(JavaThread::lock_id_offset()));
+  Node* tid_memory = store_to_memory(control(), thread_id_offset, tid, T_LONG, Compile::AliasIdxRaw, MemNode::unordered, true);
+
   JFR_ONLY(extend_setCurrentThread(thread, arr);)
   return true;
 }
 
+bool LibraryCallKit::inline_native_setLockId() {
+  Node* thread = _gvn.transform(new ThreadLocalNode());
+  Node* thread_id_offset = basic_plus_adr(thread, in_bytes(JavaThread::lock_id_offset()));
+  Node* tid_memory = store_to_memory(control(), thread_id_offset, ConvL2X(argument(0)), T_LONG, Compile::AliasIdxRaw, MemNode::unordered, true);
+  return true;
+}
+
 const Type* LibraryCallKit::scopedValueCache_type() {
   ciKlass* objects_klass = ciObjArrayKlass::make(env()->Object_klass());
   const TypeOopPtr* etype = TypeOopPtr::make_from_klass(env()->Object_klass());
   const TypeAry* arr0 = TypeAry::make(etype, TypeInt::POS);
 
diff a/src/hotspot/share/opto/library_call.hpp b/src/hotspot/share/opto/library_call.hpp
--- a/src/hotspot/share/opto/library_call.hpp
+++ b/src/hotspot/share/opto/library_call.hpp
@@ -234,10 +234,11 @@
   bool inline_unsafe_setMemory();
 
   bool inline_native_currentCarrierThread();
   bool inline_native_currentThread();
   bool inline_native_setCurrentThread();
+  bool inline_native_setLockId();
 
   bool inline_native_scopedValueCache();
   const Type* scopedValueCache_type();
   Node* scopedValueCache_helper();
   bool inline_native_setScopedValueCache();
diff a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp
--- a/src/hotspot/share/prims/jvm.cpp
+++ b/src/hotspot/share/prims/jvm.cpp
@@ -3112,10 +3112,14 @@
 
 JVM_ENTRY(void, JVM_SetCurrentThread(JNIEnv* env, jobject thisThread,
                                      jobject theThread))
   oop threadObj = JNIHandles::resolve(theThread);
   thread->set_vthread(threadObj);
+
+  // Set lock id of new current Thread
+  thread->set_lock_id(java_lang_Thread::thread_id(threadObj));
+
   JFR_ONLY(Jfr::on_set_current_thread(thread, threadObj);)
 JVM_END
 
 JVM_ENTRY(jlong, JVM_GetNextThreadIdOffset(JNIEnv* env, jclass threadClass))
   return ThreadIdentifier::unsafe_offset();
@@ -3998,10 +4002,47 @@
          "nested or unbalanced monitor enter/exit is not allowed");
   thread->toggle_is_disable_suspend();
 #endif
 JVM_END
 
+JVM_ENTRY_NO_ENV(void, JVM_VirtualThreadPinnedEvent(jint reasonCode, jstring reasonString))
+#if INCLUDE_JFR
+  EventVirtualThreadPinned e;
+  if (e.should_commit()) {
+    ResourceMark rm(THREAD);
+    // ignore reason code for now
+    const char *reason = java_lang_String::as_utf8_string(JNIHandles::resolve_non_null(reasonString));
+    e.set_pinnedReason(reason);
+    e.set_carrierThread(JFR_JVM_THREAD_ID(THREAD));
+    e.commit();
+  }
+#endif
+JVM_END
+
+JVM_ENTRY_NO_ENV(void, JVM_SetLockId(JNIEnv* env, jclass clazz, jlong tid))
+  thread->set_lock_id(tid);
+JVM_END
+
+JVM_ENTRY(jobject, JVM_TakeVirtualThreadListToUnblock(JNIEnv* env, jclass ignored))
+  ParkEvent* parkEvent = ObjectMonitor::vthread_unparker_ParkEvent();
+  assert(parkEvent != nullptr, "not initialized");
+
+  OopHandle& list_head = ObjectMonitor::vthread_cxq_head();
+  oop vthread_head = nullptr;
+  while (true) {
+    if (list_head.peek() != nullptr) {
+      for (;;) {
+        oop head = list_head.resolve();
+        if (list_head.cmpxchg(head, nullptr) == head) {
+          return JNIHandles::make_local(THREAD, head);
+        }
+      }
+    }
+    ThreadBlockInVM tbivm(THREAD);
+    parkEvent->park();
+  }
+JVM_END
 /*
  * Return the current class's class file version.  The low order 16 bits of the
  * returned jint contain the class's major version.  The high order 16 bits
  * contain the class's minor version.
  */
diff a/src/hotspot/share/prims/jvmtiEnv.cpp b/src/hotspot/share/prims/jvmtiEnv.cpp
--- a/src/hotspot/share/prims/jvmtiEnv.cpp
+++ b/src/hotspot/share/prims/jvmtiEnv.cpp
@@ -1361,22 +1361,20 @@
   if (err != JVMTI_ERROR_NONE) {
     delete owned_monitors_list;
     return err;
   }
 
-  if (java_thread != nullptr) {
-    Handle thread_handle(calling_thread, thread_oop);
-    EscapeBarrier eb(true, calling_thread, java_thread);
-    if (!eb.deoptimize_objects(MaxJavaStackTraceDepth)) {
-      delete owned_monitors_list;
-      return JVMTI_ERROR_OUT_OF_MEMORY;
-    }
-    // get owned monitors info with handshake
-    GetOwnedMonitorInfoClosure op(this, calling_thread, owned_monitors_list);
-    JvmtiHandshake::execute(&op, &tlh, java_thread, thread_handle);
-    err = op.result();
+  Handle thread_handle(calling_thread, thread_oop);
+  EscapeBarrier eb(java_thread != nullptr, calling_thread, java_thread);
+  if (!eb.deoptimize_objects(MaxJavaStackTraceDepth)) {
+    delete owned_monitors_list;
+    return JVMTI_ERROR_OUT_OF_MEMORY;
   }
+  // get owned monitors info with handshake
+  GetOwnedMonitorInfoClosure op(this, calling_thread, owned_monitors_list);
+  JvmtiHandshake::execute(&op, &tlh, java_thread, thread_handle);
+  err = op.result();
 
   jint owned_monitor_count = owned_monitors_list->length();
   if (err == JVMTI_ERROR_NONE) {
     if ((err = allocate(owned_monitor_count * sizeof(jobject *),
                       (unsigned char**)owned_monitors_ptr)) == JVMTI_ERROR_NONE) {
@@ -1419,22 +1417,20 @@
   if (err != JVMTI_ERROR_NONE) {
     delete owned_monitors_list;
     return err;
   }
 
-  if (java_thread != nullptr) {
-    Handle thread_handle(calling_thread, thread_oop);
-    EscapeBarrier eb(true, calling_thread, java_thread);
-    if (!eb.deoptimize_objects(MaxJavaStackTraceDepth)) {
-      delete owned_monitors_list;
-      return JVMTI_ERROR_OUT_OF_MEMORY;
-    }
-    // get owned monitors info with handshake
-    GetOwnedMonitorInfoClosure op(this, calling_thread, owned_monitors_list);
-    JvmtiHandshake::execute(&op, &tlh, java_thread, thread_handle);
-    err = op.result();
+  Handle thread_handle(calling_thread, thread_oop);
+  EscapeBarrier eb(java_thread != nullptr, calling_thread, java_thread);
+  if (!eb.deoptimize_objects(MaxJavaStackTraceDepth)) {
+    delete owned_monitors_list;
+    return JVMTI_ERROR_OUT_OF_MEMORY;
   }
+  // get owned monitors info with handshake
+  GetOwnedMonitorInfoClosure op(this, calling_thread, owned_monitors_list);
+  JvmtiHandshake::execute(&op, &tlh, java_thread, thread_handle);
+  err = op.result();
 
   jint owned_monitor_count = owned_monitors_list->length();
   if (err == JVMTI_ERROR_NONE) {
     if ((err = allocate(owned_monitor_count * sizeof(jvmtiMonitorStackDepthInfo),
                         (unsigned char**)monitor_info_ptr)) == JVMTI_ERROR_NONE) {
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
@@ -783,10 +783,11 @@
 }
 
 jint
 JvmtiEnvBase::get_vthread_state(oop thread_oop, JavaThread* java_thread) {
   jint state = 0;
+  bool is_mon_responsible = java_lang_VirtualThread::recheckInterval(thread_oop) > 0;
   bool ext_suspended = JvmtiVTSuspender::is_vthread_suspended(thread_oop);
   jint interrupted = java_lang_Thread::interrupted(thread_oop);
 
   if (java_thread != nullptr) {
     // If virtual thread is blocked on a monitor enter the BLOCKED_ON_MONITOR_ENTER bit
@@ -795,10 +796,12 @@
     oop ct_oop = java_lang_VirtualThread::carrier_thread(thread_oop);
     jint filtered_bits = JVMTI_THREAD_STATE_SUSPENDED | JVMTI_THREAD_STATE_INTERRUPTED;
 
     // This call can trigger a safepoint, so thread_oop must not be used after it.
     state = get_thread_state_base(ct_oop, java_thread) & ~filtered_bits;
+  } else if (is_mon_responsible) {
+    state = (jint) JavaThreadStatus::BLOCKED_ON_MONITOR_ENTER;
   } else {
     int vt_state = java_lang_VirtualThread::state(thread_oop);
     state = (jint)java_lang_VirtualThread::map_state_to_thread_status(vt_state);
   }
   // Ensure the thread has not exited after retrieving suspended/interrupted values.
@@ -1012,40 +1015,47 @@
 
   return err;
 }
 
 jvmtiError
-JvmtiEnvBase::get_owned_monitors(JavaThread* calling_thread, JavaThread* java_thread, javaVFrame* jvf,
-                                 GrowableArray<jvmtiMonitorStackDepthInfo*> *owned_monitors_list) {
+JvmtiEnvBase::get_owned_monitors(JavaThread* calling_thread, JavaThread* carrier, javaVFrame* jvf,
+                                 GrowableArray<jvmtiMonitorStackDepthInfo*> *owned_monitors_list, oop vthread) {
   jvmtiError err = JVMTI_ERROR_NONE;
   Thread *current_thread = Thread::current();
-  assert(java_thread->is_handshake_safe_for(current_thread),
+  assert(carrier == nullptr || carrier->is_handshake_safe_for(current_thread),
          "call by myself or at handshake");
 
   int depth = 0;
   for ( ; jvf != nullptr; jvf = jvf->java_sender()) {
     if (MaxJavaStackTraceDepth == 0 || depth++ < MaxJavaStackTraceDepth) {  // check for stack too deep
       // Add locked objects for this frame into list.
-      err = get_locked_objects_in_frame(calling_thread, java_thread, jvf, owned_monitors_list, depth - 1);
+      err = get_locked_objects_in_frame(calling_thread, carrier, jvf, owned_monitors_list, depth - 1, vthread);
       if (err != JVMTI_ERROR_NONE) {
         return err;
       }
     }
   }
 
+  if (carrier == nullptr) {
+    // vthread gets pinned if monitors are acquired via jni MonitorEnter
+    // so nothing else to do for unmounted case.
+    return err;
+  }
+
   // Get off stack monitors. (e.g. acquired via jni MonitorEnter).
   JvmtiMonitorClosure jmc(calling_thread, owned_monitors_list, this);
-  ObjectSynchronizer::owned_monitors_iterate(&jmc, java_thread);
+  ObjectSynchronizer::owned_monitors_iterate(&jmc, carrier);
   err = jmc.error();
 
   return err;
 }
 
 // Save JNI local handles for any objects that this frame owns.
 jvmtiError
-JvmtiEnvBase::get_locked_objects_in_frame(JavaThread* calling_thread, JavaThread* java_thread,
-                                 javaVFrame *jvf, GrowableArray<jvmtiMonitorStackDepthInfo*>* owned_monitors_list, jint stack_depth) {
+JvmtiEnvBase::get_locked_objects_in_frame(JavaThread* calling_thread, JavaThread* target,
+                                 javaVFrame *jvf, GrowableArray<jvmtiMonitorStackDepthInfo*>* owned_monitors_list,
+                                 jint stack_depth, oop vthread) {
   jvmtiError err = JVMTI_ERROR_NONE;
   Thread* current_thread = Thread::current();
   ResourceMark rm(current_thread);
   HandleMark   hm(current_thread);
 
@@ -1058,24 +1068,32 @@
   {
     // The ObjectMonitor* can't be async deflated since we are either
     // at a safepoint or the calling thread is operating on itself so
     // it cannot leave the underlying wait() call.
     // Save object of current wait() call (if any) for later comparison.
-    ObjectMonitor *mon = java_thread->current_waiting_monitor();
-    if (mon != nullptr) {
-      wait_obj = mon->object();
+    if (target != nullptr) {
+      ObjectMonitor *mon = target->current_waiting_monitor();
+      if (mon != nullptr) wait_obj = mon->object();
     }
   }
   oop pending_obj = nullptr;
   {
     // The ObjectMonitor* can't be async deflated since we are either
     // at a safepoint or the calling thread is operating on itself so
     // it cannot leave the underlying enter() call.
     // Save object of current enter() call (if any) for later comparison.
-    ObjectMonitor *mon = java_thread->current_pending_monitor();
-    if (mon != nullptr) {
-      pending_obj = mon->object();
+    if (target != nullptr) {
+      ObjectMonitor *mon = target->current_pending_monitor();
+      if (mon != nullptr) pending_obj = mon->object();
+    } else {
+      assert(vthread != nullptr, "no vthread oop");
+      oop oopCont = java_lang_VirtualThread::continuation(vthread);
+      assert(oopCont != nullptr, "vthread with no continuation");
+      stackChunkOop chunk = jdk_internal_vm_Continuation::tail(oopCont);
+      assert(chunk != nullptr, "unmounted vthread should have a chunk");
+      ObjectMonitor *mon = chunk->objectMonitor();
+      if (mon != nullptr) pending_obj = mon->object();
     }
   }
 
   for (int i = 0; i < mons->length(); i++) {
     MonitorInfo *mi = mons->at(i);
@@ -2485,22 +2503,22 @@
   }
 }
 
 void
 GetOwnedMonitorInfoClosure::do_vthread(Handle target_h) {
-  assert(_target_jt != nullptr, "sanity check");
   Thread* current = Thread::current();
   ResourceMark rm(current); // vframes are resource allocated
   HandleMark hm(current);
 
   javaVFrame *jvf = JvmtiEnvBase::get_vthread_jvf(target_h());
 
-  if (!_target_jt->is_exiting() && _target_jt->threadObj() != nullptr) {
+  if (_target_jt == nullptr || (!_target_jt->is_exiting() && _target_jt->threadObj() != nullptr)) {
     _result = ((JvmtiEnvBase *)_env)->get_owned_monitors(_calling_thread,
                                                          _target_jt,
                                                          jvf,
-                                                         _owned_monitors_list);
+                                                         _owned_monitors_list,
+                                                         target_h());
   }
 }
 
 void
 GetCurrentContendedMonitorClosure::do_thread(Thread *target) {
@@ -2514,10 +2532,17 @@
 }
 
 void
 GetCurrentContendedMonitorClosure::do_vthread(Handle target_h) {
   if (_target_jt == nullptr) {
+    oop cont = java_lang_VirtualThread::continuation(target_h());
+    assert(cont != nullptr, "vthread with no continuation");
+    stackChunkOop chunk = jdk_internal_vm_Continuation::tail(cont);
+    assert(chunk != nullptr, "unmounted vthread should have a chunk");
+    if (chunk->objectMonitor() != nullptr) {
+      *_owned_monitor_ptr = JNIHandles::make_local(_calling_thread, chunk->objectMonitor()->object());
+    }
     _result = JVMTI_ERROR_NONE; // target virtual thread is unmounted
     return;
   }
   // mounted virtual thread case
   do_thread(_target_jt);
@@ -2627,10 +2652,11 @@
   _result = ((JvmtiEnvBase*)_env)->get_frame_location(target_h(), _depth,
                                                       _method_ptr, _location_ptr);
 }
 
 void
+
 VirtualThreadGetThreadClosure::do_thread(Thread *target) {
   assert(target->is_Java_thread(), "just checking");
   JavaThread *jt = JavaThread::cast(target);
   oop carrier_thread = java_lang_VirtualThread::carrier_thread(_vthread_h());
   *_carrier_thread_ptr = (jthread)JNIHandles::make_local(jt, carrier_thread);
diff a/src/hotspot/share/prims/jvmtiEnvBase.hpp b/src/hotspot/share/prims/jvmtiEnvBase.hpp
--- a/src/hotspot/share/prims/jvmtiEnvBase.hpp
+++ b/src/hotspot/share/prims/jvmtiEnvBase.hpp
@@ -358,11 +358,11 @@
   jint count_locked_objects(JavaThread *java_thread, Handle hobj);
   jvmtiError get_locked_objects_in_frame(JavaThread *calling_thread,
                                    JavaThread* java_thread,
                                    javaVFrame *jvf,
                                    GrowableArray<jvmtiMonitorStackDepthInfo*>* owned_monitors_list,
-                                   jint depth);
+                                   jint depth, oop vthread = nullptr);
  public:
   static javaVFrame* jvf_for_thread_and_depth(JavaThread* java_thread, jint depth);
 
   // get a field descriptor for the specified class and field
   static bool get_field_descriptor(Klass* k, jfieldID field, fieldDescriptor* fd);
@@ -422,12 +422,12 @@
                              jvmtiFrameInfo* frame_buffer, jint* count_ptr);
   jvmtiError get_current_contended_monitor(JavaThread* calling_thread, JavaThread* java_thread,
                                            jobject* monitor_ptr, bool is_virtual);
   jvmtiError get_owned_monitors(JavaThread* calling_thread, JavaThread* java_thread,
                                 GrowableArray<jvmtiMonitorStackDepthInfo*> *owned_monitors_list);
-  jvmtiError get_owned_monitors(JavaThread* calling_thread, JavaThread* java_thread, javaVFrame* jvf,
-                                GrowableArray<jvmtiMonitorStackDepthInfo*> *owned_monitors_list);
+  jvmtiError get_owned_monitors(JavaThread* calling_thread, JavaThread* carrier, javaVFrame* jvf,
+                                GrowableArray<jvmtiMonitorStackDepthInfo*> *owned_monitors_list, oop vthread);
   static jvmtiError check_top_frame(Thread* current_thread, JavaThread* java_thread,
                                     jvalue value, TosState tos, Handle* ret_ob_h);
   jvmtiError force_early_return(jthread thread, jvalue value, TosState tos);
 };
 
diff a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp
--- a/src/hotspot/share/prims/jvmtiExport.cpp
+++ b/src/hotspot/share/prims/jvmtiExport.cpp
@@ -1364,14 +1364,13 @@
 
   JvmtiThreadState *state = get_jvmti_thread_state(thread);
   if (state == nullptr) {
     return;
   }
-  if (thread->is_in_tmp_VTMS_transition()) {
-    return; // skip ClassLoad events in tmp VTMS transition
+  if (thread->is_in_any_VTMS_transition()) {
+    return; // skip ClassLoad events in VTMS transition
   }
-  assert(!thread->is_in_any_VTMS_transition(), "class load events are not allowed in any VTMS transition");
 
   EVT_TRIG_TRACE(JVMTI_EVENT_CLASS_LOAD, ("[%s] Trg Class Load triggered",
                       JvmtiTrace::safe_get_thread_name(thread)));
   JvmtiEnvThreadStateIterator it(state);
   for (JvmtiEnvThreadState* ets = it.first(); ets != nullptr; ets = it.next(ets)) {
@@ -1402,14 +1401,13 @@
 
   JvmtiThreadState *state = get_jvmti_thread_state(thread);
   if (state == nullptr) {
     return;
   }
-  if (thread->is_in_tmp_VTMS_transition()) {
-    return; // skip ClassPrepare events in tmp VTMS transition
+  if (thread->is_in_any_VTMS_transition()) {
+    return; // skip ClassPrepare events in VTMS transition
   }
-  assert(!thread->is_in_any_VTMS_transition(), "class prepare events are not allowed in any VTMS transition");
 
   EVT_TRIG_TRACE(JVMTI_EVENT_CLASS_PREPARE, ("[%s] Trg Class Prepare triggered",
                       JvmtiTrace::safe_get_thread_name(thread)));
   JvmtiEnvThreadStateIterator it(state);
   for (JvmtiEnvThreadState* ets = it.first(); ets != nullptr; ets = it.next(ets)) {
@@ -1676,12 +1674,13 @@
   }
   JavaThread *thread = JavaThread::current();
   HandleMark hm(thread);
   EVT_TRIG_TRACE(EXT_EVENT_VIRTUAL_THREAD_UNMOUNT, ("[%p] Trg Virtual Thread Unmount event triggered", vthread));
 
-  JvmtiThreadState *state = get_jvmti_thread_state(thread);
-  if (state == nullptr) {
+  // On preemption JVMTI state rebinding has already happened so get it always direclty from the oop.
+  JvmtiThreadState *state = java_lang_Thread::jvmti_thread_state(JNIHandles::resolve(vthread));
+  if (state == NULL) {
     return;
   }
 
   if (state->is_enabled((jvmtiEvent)EXT_EVENT_VIRTUAL_THREAD_UNMOUNT)) {
     JvmtiEnvThreadStateIterator it(state);
@@ -1696,11 +1695,11 @@
 
         JvmtiVirtualThreadEventMark jem(thread);
         JvmtiJavaThreadEventTransition jet(thread);
         jvmtiExtensionEvent callback = env->ext_callbacks()->VirtualThreadUnmount;
         if (callback != nullptr) {
-          (*callback)(env->jvmti_external(), jem.jni_env(), jem.jni_thread());
+          (*callback)(env->jvmti_external(), jem.jni_env(), vthread);
         }
       }
     }
   }
 }
diff a/src/hotspot/share/prims/jvmtiThreadState.cpp b/src/hotspot/share/prims/jvmtiThreadState.cpp
--- a/src/hotspot/share/prims/jvmtiThreadState.cpp
+++ b/src/hotspot/share/prims/jvmtiThreadState.cpp
@@ -665,10 +665,16 @@
 JvmtiVTMSTransitionDisabler::VTMS_unmount_end(jobject vthread) {
   JavaThread* thread = JavaThread::current();
   assert(thread->is_in_VTMS_transition(), "sanity check");
   assert(!thread->is_in_tmp_VTMS_transition(), "sanity check");
   finish_VTMS_transition(vthread, /* is_mount */ false);
+
+  if (JvmtiExport::should_post_vthread_unmount() && thread->jvmti_unmount_event_pending()) {
+    assert(java_lang_VirtualThread::is_preempted(JNIHandles::resolve(vthread)), "should be marked preempted");
+    JvmtiExport::post_vthread_unmount(vthread);
+    thread->set_jvmti_unmount_event_pending(false);
+  }
 }
 
 
 //
 // Virtual Threads Suspend/Resume management
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
@@ -182,10 +182,13 @@
   static bool supports_on_spin_wait() { return false; }
 
   // Does platform support fast class initialization checks for static methods?
   static bool supports_fast_class_init_checks() { return false; }
 
+  // Does platform support continuation preemption?
+  static bool supports_cont_preemption() { 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; }
diff a/src/hotspot/share/runtime/basicLock.hpp b/src/hotspot/share/runtime/basicLock.hpp
--- a/src/hotspot/share/runtime/basicLock.hpp
+++ b/src/hotspot/share/runtime/basicLock.hpp
@@ -70,10 +70,11 @@
   oop       _obj;                                     // object holds the lock;
 
  public:
   // Manipulation
   oop      obj() const                                { return _obj;  }
+  oop*     obj_adr()                                  { return &_obj; }
   void set_obj(oop obj)                               { _obj = obj; }
   BasicLock* lock()                                   { return &_lock; }
 
   // Note: Use frame::interpreter_frame_monitor_size() for the size of BasicObjectLocks
   //       in interpreter activation frames since it includes machine-specific padding.
diff a/src/hotspot/share/runtime/continuation.cpp b/src/hotspot/share/runtime/continuation.cpp
--- a/src/hotspot/share/runtime/continuation.cpp
+++ b/src/hotspot/share/runtime/continuation.cpp
@@ -24,17 +24,20 @@
 
 #include "precompiled.hpp"
 #include "classfile/vmSymbols.hpp"
 #include "gc/shared/barrierSetNMethod.hpp"
 #include "oops/method.inline.hpp"
+#include "oops/oop.inline.hpp"
+#include "prims/jvmtiThreadState.inline.hpp"
 #include "runtime/continuation.hpp"
 #include "runtime/continuationEntry.inline.hpp"
 #include "runtime/continuationHelper.inline.hpp"
 #include "runtime/continuationJavaClasses.inline.hpp"
 #include "runtime/continuationWrapper.inline.hpp"
 #include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/javaThread.inline.hpp"
+#include "runtime/jniHandles.inline.hpp"
 #include "runtime/osThread.hpp"
 #include "runtime/vframe.inline.hpp"
 #include "runtime/vframe_hp.hpp"
 
 // defined in continuationFreezeThaw.cpp
@@ -52,10 +55,126 @@
      THROW_MSG(vmSymbols::java_lang_IllegalStateException(), "pin underflow");
   }
 }
 JVM_END
 
+#if INCLUDE_JVMTI
+class JvmtiUnmountBeginMark : public StackObj {
+  JavaThread* _target;
+  int _preempt_result;
+
+ public:
+  JvmtiUnmountBeginMark(JavaThread* t) :
+    _target(t), _preempt_result(freeze_pinned_native) {
+    assert(!_target->is_in_any_VTMS_transition(), "must be");
+
+    if (JvmtiVTMSTransitionDisabler::VTMS_notify_jvmti_events()) {
+      JvmtiVTMSTransitionDisabler::start_VTMS_transition(JNIHandles::make_local(_target->vthread()), /* is_mount */ false);
+    } else {
+      _target->set_is_in_VTMS_transition(true);
+      java_lang_Thread::set_is_in_VTMS_transition(_target->vthread(), true);
+    }
+  }
+  ~JvmtiUnmountBeginMark() {
+    assert(!_target->is_suspended(), "must be");
+
+    assert(_target->is_in_VTMS_transition(), "must be");
+    assert(java_lang_Thread::is_in_VTMS_transition(_target->vthread()), "must be");
+
+    // Read it again since for late binding agents the flag could have
+    // been set while blocked in the allocation path during freeze.
+    bool jvmti_present = JvmtiVTMSTransitionDisabler::VTMS_notify_jvmti_events();
+
+    if (_preempt_result != freeze_ok) {
+      // Undo transition
+      if (jvmti_present) {
+        JvmtiVTMSTransitionDisabler::finish_VTMS_transition(JNIHandles::make_local(_target->vthread()), false);
+      } else {
+        _target->set_is_in_VTMS_transition(false);
+        java_lang_Thread::set_is_in_VTMS_transition(_target->vthread(), false);
+      }
+    } else {
+      if (jvmti_present) {
+        _target->rebind_to_jvmti_thread_state_of(_target->threadObj());
+        _target->set_jvmti_unmount_event_pending(true);
+      }
+    }
+  }
+  void set_preempt_result(int res) { _preempt_result = res; }
+};
+
+static bool is_safe_vthread_to_preempt_for_jvmti(JavaThread* target, oop vthread) {
+  assert(!target->has_pending_popframe(), "should be true; no support for vthreads yet");
+  JvmtiThreadState* state = target->jvmti_thread_state();
+  assert(state == nullptr || !state->is_earlyret_pending(), "should be true; no support for vthreads yet");
+
+  if (target->is_in_any_VTMS_transition()) {
+    // We caught target at the end of a mount transition (is_in_VTMS_transition()) or at the
+    // beginning or end of a temporary switch to carrier thread (is_in_tmp_VTMS_transition()).
+    return false;
+  }
+  return true;
+}
+#endif
+
+static bool is_safe_vthread_to_preempt(JavaThread* target, oop vthread) {
+  if (!java_lang_VirtualThread::is_instance(vthread) ||                               // inside transition
+      java_lang_VirtualThread::state(vthread) != java_lang_VirtualThread::RUNNING) {  // inside transition
+    return false;
+  }
+  return JVMTI_ONLY(is_safe_vthread_to_preempt_for_jvmti(target, vthread)) NOT_JVMTI(true);
+}
+
+typedef int (*FreezeContFnT)(JavaThread*, intptr_t*);
+
+int Continuation::try_preempt(JavaThread* target, oop continuation, bool set_state_yielding) {
+  assert(target->is_on_monitorenter(), "must be");
+  assert(target->has_last_Java_frame(), "must be");
+  assert(!target->preempting(), "must be");
+  assert(!target->is_suspended() || target->is_disable_suspend(), "must be");
+  assert(!target->has_pending_exception(), "must be");
+  assert(target->last_continuation() != nullptr, "must be");
+  assert(target->last_continuation()->cont_oop(target) == continuation, "must be");
+  assert(!is_continuation_preempted(continuation), "shouldn't be");
+  assert(Continuation::continuation_scope(continuation) == java_lang_VirtualThread::vthread_scope(), "must be");
+
+  if (!VM_Version::supports_cont_preemption()) {
+    return unsupported;
+  }
+
+  if (is_continuation_done(continuation)) {
+    return freeze_not_mounted;
+  }
+
+  // Continuation is mounted and it's not done so check if it's safe to preempt.
+  if (!is_safe_vthread_to_preempt(target, target->vthread())) {
+    return freeze_pinned_native;
+  }
+
+#if INCLUDE_JVMTI
+  JvmtiUnmountBeginMark jubm(target);
+#endif
+  target->set_preempting(true);
+  int res = CAST_TO_FN_PTR(FreezeContFnT, freeze_preempt_entry())(target, target->last_Java_sp());
+  log_trace(continuations, preempt)("try_preempt: %d", res);
+  JVMTI_ONLY(jubm.set_preempt_result(res);)
+  if (res != freeze_ok) {
+    target->set_preempting(false);
+  } else if (set_state_yielding) {
+    java_lang_VirtualThread::set_state(target->vthread(), java_lang_VirtualThread::YIELDING);
+  }
+  return res;
+}
+
+bool Continuation::is_continuation_preempted(oop cont) {
+  return jdk_internal_vm_Continuation::is_preempted(cont);
+}
+
+bool Continuation::is_continuation_done(oop cont) {
+  return jdk_internal_vm_Continuation::done(cont);
+}
+
 #ifndef PRODUCT
 static jlong java_tid(JavaThread* thread) {
   return java_lang_Thread::thread_id(thread->threadObj());
 }
 #endif
diff a/src/hotspot/share/runtime/continuation.hpp b/src/hotspot/share/runtime/continuation.hpp
--- a/src/hotspot/share/runtime/continuation.hpp
+++ b/src/hotspot/share/runtime/continuation.hpp
@@ -24,10 +24,11 @@
 
 #ifndef SHARE_VM_RUNTIME_CONTINUATION_HPP
 #define SHARE_VM_RUNTIME_CONTINUATION_HPP
 
 #include "jni.h"
+#include "classfile/javaClasses.hpp"
 #include "memory/allStatic.hpp"
 #include "oops/oopsHierarchy.hpp"
 
 class ContinuationEntry;
 class frame;
@@ -45,10 +46,22 @@
 void continuations_init();
 
 class javaVFrame;
 class JavaThread;
 
+// should match Continuation.toPreemptStatus() in Continuation.java
+enum freeze_result {
+  freeze_ok = 0,
+  freeze_ok_bottom = 1,
+  freeze_pinned_cs = 2,
+  freeze_pinned_native = 3,
+  freeze_pinned_monitor = 4,
+  freeze_exception = 5,
+  freeze_not_mounted = 6,
+  unsupported = 7
+};
+
 class Continuation : AllStatic {
 public:
 
   enum thaw_kind {
     thaw_top = 0,
@@ -67,18 +80,24 @@
   }
 
   static void init();
 
   static address freeze_entry();
+  static address freeze_preempt_entry();
   static int prepare_thaw(JavaThread* thread, bool return_barrier);
   static address thaw_entry();
 
+  static int try_preempt(JavaThread* target, oop continuation, bool set_state_yielding = false);
+
   static ContinuationEntry* get_continuation_entry_for_continuation(JavaThread* thread, oop continuation);
   static ContinuationEntry* get_continuation_entry_for_sp(JavaThread* thread, intptr_t* const sp);
   static ContinuationEntry* get_continuation_entry_for_entry_frame(JavaThread* thread, const frame& f);
 
   static bool is_continuation_mounted(JavaThread* thread, oop continuation);
+  static bool is_continuation_preempted(oop cont);
+  static bool is_continuation_done(oop cont);
+
 
   static bool is_cont_barrier_frame(const frame& f);
   static bool is_return_barrier_entry(const address pc);
   static bool is_continuation_enterSpecial(const frame& f);
   static bool is_continuation_entry_frame(const frame& f, const RegisterMap *map);
diff a/src/hotspot/share/runtime/continuationEntry.cpp b/src/hotspot/share/runtime/continuationEntry.cpp
--- a/src/hotspot/share/runtime/continuationEntry.cpp
+++ b/src/hotspot/share/runtime/continuationEntry.cpp
@@ -34,20 +34,24 @@
 #include "runtime/stackFrameStream.inline.hpp"
 #include "runtime/stackWatermarkSet.inline.hpp"
 #include "runtime/stubRoutines.hpp"
 
 int ContinuationEntry::_return_pc_offset = 0;
+int ContinuationEntry::_thaw_call_pc_offset = 0;
 address ContinuationEntry::_return_pc = nullptr;
+address ContinuationEntry::_thaw_call_pc = nullptr;
 nmethod* ContinuationEntry::_enter_special = nullptr;
 int ContinuationEntry::_interpreted_entry_offset = 0;
 
 void ContinuationEntry::set_enter_code(nmethod* nm, int interpreted_entry_offset) {
   assert(_return_pc_offset != 0, "");
   _return_pc = nm->code_begin() + _return_pc_offset;
+  _thaw_call_pc = nm->code_begin() + _thaw_call_pc_offset;
 
   _enter_special = nm;
   _interpreted_entry_offset = interpreted_entry_offset;
+
   assert(_enter_special->code_contains(compiled_entry()),    "entry not in enterSpecial");
   assert(_enter_special->code_contains(interpreted_entry()), "entry not in enterSpecial");
   assert(interpreted_entry() < compiled_entry(), "unexpected code layout");
 }
 
diff a/src/hotspot/share/runtime/continuationEntry.hpp b/src/hotspot/share/runtime/continuationEntry.hpp
--- a/src/hotspot/share/runtime/continuationEntry.hpp
+++ b/src/hotspot/share/runtime/continuationEntry.hpp
@@ -54,10 +54,12 @@
   }
 #endif
 
 public:
   static int _return_pc_offset; // friend gen_continuation_enter
+  static int _thaw_call_pc_offset;
+  static address _thaw_call_pc;
   static void set_enter_code(nmethod* nm, int interpreted_entry_offset);
   static bool is_interpreted_call(address call_address);
 
 private:
   static address _return_pc;
diff a/src/hotspot/share/runtime/continuationFreezeThaw.cpp b/src/hotspot/share/runtime/continuationFreezeThaw.cpp
--- a/src/hotspot/share/runtime/continuationFreezeThaw.cpp
+++ b/src/hotspot/share/runtime/continuationFreezeThaw.cpp
@@ -53,10 +53,11 @@
 #include "runtime/frame.inline.hpp"
 #include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/javaThread.inline.hpp"
 #include "runtime/jniHandles.inline.hpp"
 #include "runtime/keepStackGCProcessed.hpp"
+#include "runtime/objectMonitor.inline.hpp"
 #include "runtime/orderAccess.hpp"
 #include "runtime/prefetch.inline.hpp"
 #include "runtime/smallRegisterMap.inline.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "runtime/stackChunkFrameStream.inline.hpp"
@@ -64,10 +65,11 @@
 #include "runtime/stackOverflow.hpp"
 #include "runtime/stackWatermarkSet.inline.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/exceptions.hpp"
 #include "utilities/macros.hpp"
+#include "utilities/vmError.hpp"
 #if INCLUDE_ZGC
 #include "gc/z/zStackChunkGCData.inline.hpp"
 #endif
 
 #include <type_traits>
@@ -196,31 +198,12 @@
 #else
 static void verify_continuation(oop continuation) { }
 #define assert_pfl(p, ...)
 #endif
 
-// should match Continuation.preemptStatus() in Continuation.java
-enum freeze_result {
-  freeze_ok = 0,
-  freeze_ok_bottom = 1,
-  freeze_pinned_cs = 2,
-  freeze_pinned_native = 3,
-  freeze_pinned_monitor = 4,
-  freeze_exception = 5
-};
-
-const char* freeze_result_names[6] = {
-  "freeze_ok",
-  "freeze_ok_bottom",
-  "freeze_pinned_cs",
-  "freeze_pinned_native",
-  "freeze_pinned_monitor",
-  "freeze_exception"
-};
-
 static freeze_result is_pinned0(JavaThread* thread, oop cont_scope, bool safepoint);
-template<typename ConfigT> static inline int freeze_internal(JavaThread* current, intptr_t* const sp);
+template<typename ConfigT, bool preempt> static inline int freeze_internal(JavaThread* thread, intptr_t* const sp);
 
 static inline int prepare_thaw_internal(JavaThread* thread, bool return_barrier);
 template<typename ConfigT> static inline intptr_t* thaw_internal(JavaThread* thread, const Continuation::thaw_kind kind);
 
 
@@ -243,12 +226,15 @@
 
 template<typename ConfigT>
 static JRT_LEAF(intptr_t*, thaw(JavaThread* thread, int kind))
   // TODO: JRT_LEAF and NoHandleMark is problematic for JFR events.
   // vFrameStreamCommon allocates Handles in RegisterMap for continuations.
+  // Also the preemption case with JVMTI events enabled might safepoint so
+  // undo the NoSafepointVerifier here and rely on handling by ContinuationWrapper.
   // JRT_ENTRY instead?
   ResetNoHandleMark rnhm;
+  debug_only(PauseNoSafepointVerifier pnsv(&__nsv);)
 
   // we might modify the code cache via BarrierSetNMethod::nmethod_entry_barrier
   MACOS_AARCH64_ONLY(ThreadWXEnable __wx(WXWrite, thread));
   return ConfigT::thaw(thread, (Continuation::thaw_kind)kind);
 JRT_END
@@ -267,11 +253,15 @@
 public:
   typedef Config<oops, BarrierSetT> SelfT;
   using OopT = std::conditional_t<oops == oop_kind::NARROW, narrowOop, oop>;
 
   static int freeze(JavaThread* thread, intptr_t* const sp) {
-    return freeze_internal<SelfT>(thread, sp);
+    return freeze_internal<SelfT, false>(thread, sp);
+  }
+
+  static int freeze_preempt(JavaThread* thread, intptr_t* const sp) {
+    return freeze_internal<SelfT, true>(thread, sp);
   }
 
   static intptr_t* thaw(JavaThread* thread, Continuation::thaw_kind kind) {
     return thaw_internal<SelfT>(thread, kind);
   }
@@ -291,29 +281,31 @@
 static oop get_continuation(JavaThread* thread) {
   assert(thread != nullptr, "");
   assert(thread->threadObj() != nullptr, "");
   return java_lang_Thread::continuation(thread->threadObj());
 }
+#endif // ASSERT
 
 inline void clear_anchor(JavaThread* thread) {
   thread->frame_anchor()->clear();
 }
 
-static void set_anchor(JavaThread* thread, intptr_t* sp) {
-  address pc = ContinuationHelper::return_address_at(
-                 sp - frame::sender_sp_ret_address_offset());
+static void set_anchor(JavaThread* thread, intptr_t* sp, address pc = nullptr) {
+  if (pc == nullptr) {
+    pc = ContinuationHelper::return_address_at(
+           sp - frame::sender_sp_ret_address_offset());
+  }
   assert(pc != nullptr, "");
 
   JavaFrameAnchor* anchor = thread->frame_anchor();
   anchor->set_last_Java_sp(sp);
   anchor->set_last_Java_pc(pc);
   ContinuationHelper::set_anchor_pd(anchor, sp);
 
   assert(thread->has_last_Java_frame(), "");
   assert(thread->last_frame().cb() != nullptr, "");
 }
-#endif // ASSERT
 
 static void set_anchor_to_entry(JavaThread* thread, ContinuationEntry* entry) {
   JavaFrameAnchor* anchor = thread->frame_anchor();
   anchor->set_last_Java_sp(entry->entry_sp());
   anchor->set_last_Java_pc(entry->entry_pc());
@@ -321,10 +313,35 @@
 
   assert(thread->has_last_Java_frame(), "");
   assert(thread->last_frame().cb() != nullptr, "");
 }
 
+#ifdef ASSERT
+static int monitors_to_fix_on_stack(JavaThread* thread) {
+  ResourceMark rm(JavaThread::current());
+  ContinuationEntry* ce = thread->last_continuation();
+  RegisterMap map(thread,
+                  RegisterMap::UpdateMap::include,
+                  RegisterMap::ProcessFrames::include,
+                  RegisterMap::WalkContinuation::include);
+  map.set_include_argument_oops(false);
+  ResourceHashtable<oopDesc*, bool> rhtable;
+  int monitor_count = 0;
+  for (frame f = thread->last_frame(); Continuation::is_frame_in_continuation(thread, f); f = f.sender(&map)) {
+    if (f.is_interpreted_frame()) {
+      frame abs = !f.is_heap_frame() ? f : map.stack_chunk()->derelativize(f);
+      monitor_count += ContinuationHelper::InterpretedFrame::monitors_to_fix(thread, abs, rhtable, map.stack_chunk()());
+    } else if (f.is_compiled_frame()) {
+      monitor_count += ContinuationHelper::CompiledFrame::monitors_to_fix(thread, &map, f, rhtable);
+    } else if (f.is_native_frame()) {
+      monitor_count += ContinuationHelper::NativeFrame::monitors_to_fix(thread, f, rhtable);
+    }
+  }
+  return monitor_count;
+}
+#endif
+
 #if CONT_JFR
 class FreezeThawJfrInfo : public StackObj {
   short _e_size;
   short _e_num_interpreted_frames;
  public:
@@ -352,15 +369,22 @@
 class FreezeBase : public StackObj {
 protected:
   JavaThread* const _thread;
   ContinuationWrapper& _cont;
   bool _barriers; // only set when we allocate a chunk
-  const bool _preempt; // used only on the slow path
-  const intptr_t * const _frame_sp; // Top frame sp for this freeze
 
   intptr_t* _bottom_address;
 
+  const bool _preempt;
+  // Used on preemption only
+  frame _last_frame;
+  oop _monitorenter_obj;
+
+  // Used to support freezing with held monitors
+  int _monitors_to_fix;
+  int _monitors_in_lockstack;
+
   int _freeze_size; // total size of all frames plus metadata in words.
   int _total_align_size;
 
   intptr_t* _cont_stack_top;
   intptr_t* _cont_stack_bottom;
@@ -376,21 +400,24 @@
   JvmtiSampledObjectAllocEventCollector* _jvmti_event_collector;
 
   NOT_PRODUCT(int _frames;)
   DEBUG_ONLY(intptr_t* _last_write;)
 
-  inline FreezeBase(JavaThread* thread, ContinuationWrapper& cont, intptr_t* sp);
+  inline FreezeBase(JavaThread* thread, ContinuationWrapper& cont, intptr_t* sp, bool preempt);
 
 public:
   NOINLINE freeze_result freeze_slow();
   void freeze_fast_existing_chunk();
 
   CONT_JFR_ONLY(FreezeThawJfrInfo& jfr_info() { return _jfr_info; })
   void set_jvmti_event_collector(JvmtiSampledObjectAllocEventCollector* jsoaec) { _jvmti_event_collector = jsoaec; }
 
   inline int size_if_fast_freeze_available();
 
+  inline frame& last_frame() { return _last_frame; }
+  inline void set_last_frame() { _last_frame = _thread->last_frame(); }
+
 #ifdef ASSERT
   bool check_valid_fast_path();
 #endif
 
 protected:
@@ -408,13 +435,13 @@
   int cont_size() { return pointer_delta_as_int(_cont_stack_bottom, _cont_stack_top); }
 
 private:
   // slow path
   frame freeze_start_frame();
-  frame freeze_start_frame_safepoint_stub(frame f);
+  frame freeze_start_frame_on_preempt();
   NOINLINE freeze_result recurse_freeze(frame& f, frame& caller, int callee_argsize, bool callee_interpreted, bool top);
-  inline frame freeze_start_frame_yield_stub(frame f);
+  inline frame freeze_start_frame_yield_stub();
   template<typename FKind>
   inline freeze_result recurse_freeze_java_frame(const frame& f, frame& caller, int fsize, int argsize);
   inline void before_freeze_java_frame(const frame& f, const frame& caller, int fsize, int argsize, bool is_bottom_frame);
   inline void after_freeze_java_frame(const frame& hf, bool is_bottom_frame);
   freeze_result finalize_freeze(const frame& callee, frame& caller, int argsize);
@@ -422,19 +449,25 @@
   NOINLINE freeze_result recurse_freeze_interpreted_frame(frame& f, frame& caller, int callee_argsize, bool callee_interpreted);
   freeze_result recurse_freeze_compiled_frame(frame& f, frame& caller, int callee_argsize, bool callee_interpreted);
   NOINLINE freeze_result recurse_freeze_stub_frame(frame& f, frame& caller);
   NOINLINE void finish_freeze(const frame& f, const frame& top);
 
+  void fix_monitors_in_interpreted_frame(frame& f);
+  template <typename RegisterMapT>
+  void fix_monitors_in_compiled_frame(frame& f, RegisterMapT* map);
+  void fix_monitors_in_fast_path();
+
   inline bool stack_overflow();
 
   static frame sender(const frame& f) { return f.is_interpreted_frame() ? sender<ContinuationHelper::InterpretedFrame>(f)
                                                                         : sender<ContinuationHelper::NonInterpretedUnknownFrame>(f); }
   template<typename FKind> static inline frame sender(const frame& f);
   template<typename FKind> frame new_heap_frame(frame& f, frame& caller);
   inline void set_top_frame_metadata_pd(const frame& hf);
   inline void patch_pd(frame& callee, const frame& caller);
   void adjust_interpreted_frame_unextended_sp(frame& f);
+  static inline void prepare_freeze_interpreted_top_frame(const frame& f);
   static inline void relativize_interpreted_frame_metadata(const frame& f, const frame& hf);
 
 protected:
   void freeze_fast_copy(stackChunkOop chunk, int chunk_start_sp CONT_JFR_ONLY(COMMA bool chunk_is_allocated));
   bool freeze_fast_new_chunk(stackChunkOop chunk);
@@ -452,21 +485,21 @@
 class Freeze : public FreezeBase {
 private:
   stackChunkOop allocate_chunk(size_t stack_size);
 
 public:
-  inline Freeze(JavaThread* thread, ContinuationWrapper& cont, intptr_t* frame_sp)
-    : FreezeBase(thread, cont, frame_sp) {}
+  inline Freeze(JavaThread* thread, ContinuationWrapper& cont, intptr_t* frame_sp, bool preempt)
+    : FreezeBase(thread, cont, frame_sp, preempt) {}
 
   freeze_result try_freeze_fast();
 
 protected:
   virtual stackChunkOop allocate_chunk_slow(size_t stack_size) override { return allocate_chunk(stack_size); }
 };
 
-FreezeBase::FreezeBase(JavaThread* thread, ContinuationWrapper& cont, intptr_t* frame_sp) :
-    _thread(thread), _cont(cont), _barriers(false), _preempt(false), _frame_sp(frame_sp) {
+FreezeBase::FreezeBase(JavaThread* thread, ContinuationWrapper& cont, intptr_t* frame_sp, bool preempt) :
+    _thread(thread), _cont(cont), _barriers(false), _preempt(preempt), _last_frame(false /* no initialization */) {
   DEBUG_ONLY(_jvmti_event_collector = nullptr;)
 
   assert(_thread != nullptr, "");
   assert(_thread->last_continuation()->entry_sp() == _cont.entrySP(), "");
 
@@ -493,28 +526,163 @@
 #if !defined(PPC64) || defined(ZERO)
   static const int doYield_stub_frame_size = frame::metadata_words;
 #else
   static const int doYield_stub_frame_size = frame::native_abi_reg_args_size >> LogBytesPerWord;
 #endif
-  assert(SharedRuntime::cont_doYield_stub()->frame_size() == doYield_stub_frame_size, "");
+  // With preemption doYield() might not have been resolved yet
+  assert(_preempt || SharedRuntime::cont_doYield_stub()->frame_size() == doYield_stub_frame_size, "");
 
   // properties of the continuation on the stack; all sizes are in words
-  _cont_stack_top    = frame_sp + doYield_stub_frame_size; // we don't freeze the doYield stub frame
+  _cont_stack_top    = frame_sp + (!preempt ? doYield_stub_frame_size : 0); // we don't freeze the doYield stub frame
   _cont_stack_bottom = _cont.entrySP() + (_cont.argsize() == 0 ? frame::metadata_words_at_top : 0)
       - ContinuationHelper::frame_align_words(_cont.argsize()); // see alignment in thaw
 
   log_develop_trace(continuations)("freeze size: %d argsize: %d top: " INTPTR_FORMAT " bottom: " INTPTR_FORMAT,
     cont_size(), _cont.argsize(), p2i(_cont_stack_top), p2i(_cont_stack_bottom));
   assert(cont_size() > 0, "");
+
+  if (LockingMode == LM_LEGACY) {
+    _monitors_to_fix = thread->held_monitor_count();
+    assert(_monitors_to_fix >= 0, "invariant");
+    _monitors_in_lockstack = 0;
+    assert(_thread->lock_stack().monitor_count() == 0, "should be set only for LM_LIGHTWEIGHT");
+  } else {
+    _monitors_in_lockstack = _thread->lock_stack().monitor_count();
+    assert(_monitors_in_lockstack >= 0 && _monitors_in_lockstack <= 8, "_monitors_in_lockstack=%d", _monitors_in_lockstack);
+    _monitors_to_fix = _monitors_in_lockstack;
+    assert(thread->held_monitor_count() == 0, "should be set only for LM_LEGACY");
+  }
+  DEBUG_ONLY(int verified_cnt = monitors_to_fix_on_stack(_thread);)
+  assert(verified_cnt == _monitors_to_fix || thread->obj_locker_count() > 0 ||
+         (LockingMode == LM_LIGHTWEIGHT && verified_cnt == _thread->lock_stack().unique_count()),
+         "wrong monitor count. Found %d in the stack but counter is %d", verified_cnt, _monitors_to_fix);
 }
 
 void FreezeBase::init_rest() { // we want to postpone some initialization after chunk handling
   _freeze_size = 0;
   _total_align_size = 0;
   NOT_PRODUCT(_frames = 0;)
 }
 
+void FreezeBase::fix_monitors_in_interpreted_frame(frame& f) {
+  assert(LockingMode == LM_LEGACY, "invariant");
+  BasicObjectLock* first_mon = f.interpreter_frame_monitor_begin();
+  BasicObjectLock* last_mon = f.interpreter_frame_monitor_end();
+  assert(last_mon <= first_mon, "must be");
+
+  if (first_mon == last_mon) {
+    // No monitors in this frame
+    return;
+  }
+
+  for (BasicObjectLock* current = f.previous_monitor_in_interpreter_frame(first_mon);
+       current >= last_mon; current = f.previous_monitor_in_interpreter_frame(current)) {
+    oop obj = current->obj();
+    if (obj == nullptr) {
+      continue;
+    }
+    if (obj == _monitorenter_obj) {
+      assert(_preempt, "should be preemption on monitorenter case");
+      assert(obj->mark().monitor() != nullptr, "failed to acquire the lock but its not inflated");
+      continue;
+    }
+    markWord mark = obj->mark();
+    if (mark.has_monitor() && !mark.monitor()->is_owner_anonymous()) {
+      // Good for freezing, nothing to do.
+      assert(mark.monitor()->is_owner(_thread), "invariant");
+      continue;
+    }
+    // Found locked monitor that needs fixing. Inflate will fix the owner for stack-locked case or inflated but anonymously owned.
+    ObjectMonitor* om = ObjectSynchronizer::inflate(_thread, obj, ObjectSynchronizer::InflateCause::inflate_cause_cont_freeze);
+    assert(om != nullptr, "invariant");
+    assert(om->is_owner(_thread), "invariant");
+    if (--_monitors_to_fix == 0) break;
+  }
+  assert(_monitors_to_fix >= 0, "invariant");
+}
+
+template <typename RegisterMapT>
+void FreezeBase::fix_monitors_in_compiled_frame(frame& f, RegisterMapT* map) {
+  assert(LockingMode == LM_LEGACY, "invariant");
+  assert(!f.is_interpreted_frame(), "");
+  assert(ContinuationHelper::CompiledFrame::is_instance(f), "");
+
+  nmethod* nm = f.cb()->as_nmethod();
+
+  if (!nm->has_monitors()) {
+    // No monitors in this frame
+    return;
+  }
+
+  for (ScopeDesc* scope = nm->scope_desc_at(f.pc()); scope != nullptr; scope = scope->sender()) {
+    GrowableArray<MonitorValue*>* mons = scope->monitors();
+    if (mons == nullptr || mons->is_empty()) {
+      continue;
+    }
+
+    for (int index = (mons->length()-1); index >= 0; index--) { // see compiledVFrame::monitors()
+      MonitorValue* mon = mons->at(index);
+      if (mon->eliminated()) {
+        continue; // we ignore eliminated monitors
+      }
+      ScopeValue* ov = mon->owner();
+      StackValue* owner_sv = StackValue::create_stack_value(&f, map, ov); // it is an oop
+      oop obj = owner_sv->get_obj()();
+      if (obj == nullptr) {
+        continue;
+      }
+      if (obj == _monitorenter_obj) {
+        assert(_preempt, "should be preemption on monitorenter case");
+        assert(obj->mark().monitor() != nullptr, "failed to acquire the lock but its not inflated");
+        continue;
+      }
+      markWord mark = obj->mark();
+      if (mark.has_monitor() && !mark.monitor()->is_owner_anonymous()) {
+        // Good for freezing, nothing to do.
+        assert(mark.monitor()->is_owner(_thread), "invariant");
+        continue;
+      }
+      // Found locked monitor that needs fixing. Inflate will fix the owner for stack-locked case or inflated but anonymously owned.
+      ObjectMonitor* om = ObjectSynchronizer::inflate(_thread, obj, ObjectSynchronizer::InflateCause::inflate_cause_cont_freeze);
+      assert(om != nullptr, "invariant");
+      assert(om->is_owner(_thread), "invariant");
+      if (--_monitors_to_fix == 0) break;
+    }
+  }
+  assert(_monitors_to_fix >= 0, "invariant");
+}
+
+void FreezeBase::fix_monitors_in_fast_path() {
+  if (LockingMode == LM_LIGHTWEIGHT) {
+    stackChunkOop chunk = _cont.tail();
+    assert(chunk->sp_address() - chunk->start_address() >= _monitors_in_lockstack, "no room for lockstack");
+    _thread->lock_stack().move_to_address((oop*)chunk->start_address());
+
+    chunk->set_lockStackSize((uint8_t)_monitors_in_lockstack);
+    chunk->set_has_lockStack(true);
+
+    DEBUG_ONLY(_monitors_to_fix -= _monitors_in_lockstack;)
+  } else {
+    assert(LockingMode == LM_LEGACY, "invariant");
+    _monitorenter_obj = _thread->is_on_monitorenter() ? _thread->current_pending_monitor()->object() : nullptr;
+
+    ResourceMark rm(_thread);
+    RegisterMap map(JavaThread::current(),
+                  RegisterMap::UpdateMap::include,
+                  RegisterMap::ProcessFrames::skip, // already processed in unwind_frames()
+                  RegisterMap::WalkContinuation::skip);
+    map.set_include_argument_oops(false);
+    frame freezed_top(_cont_stack_top);
+    frame first = !_preempt ? freezed_top : freezed_top.sender(&map);
+    for (frame f = first; _monitors_to_fix > 0 && Continuation::is_frame_in_continuation(_cont.entry(), f); f = f.sender(&map)) {
+      fix_monitors_in_compiled_frame(f, &map);
+    }
+  }
+  assert(_monitors_to_fix == 0, "missing monitors in stack");
+  assert(_thread->held_monitor_count() == 0, "should be 0 now");
+}
+
 void FreezeBase::copy_to_chunk(intptr_t* from, intptr_t* to, int size) {
   stackChunkOop chunk = _cont.tail();
   chunk->copy_from_stack_to_chunk(from, to, size);
   CONT_JFR_ONLY(_jfr_info.record_size_copied(size);)
 
@@ -541,11 +709,11 @@
   assert(_thread->cont_fastpath(), "");
 
   DEBUG_ONLY(_fast_freeze_size = size_if_fast_freeze_available();)
   assert(_fast_freeze_size == 0, "");
 
-  stackChunkOop chunk = allocate_chunk(cont_size() + frame::metadata_words);
+  stackChunkOop chunk = allocate_chunk(cont_size() + frame::metadata_words + _monitors_in_lockstack);
   if (freeze_fast_new_chunk(chunk)) {
     return freeze_ok;
   }
   if (_thread->has_pending_exception()) {
     return freeze_exception;
@@ -574,10 +742,12 @@
   // although that would require changing stackChunkOopDesc::is_empty
   if (chunk_sp < chunk->stack_size()) {
     total_size_needed -= _cont.argsize() + frame::metadata_words_at_top;
   }
 
+  total_size_needed += _monitors_in_lockstack;
+
   int chunk_free_room = chunk_sp - frame::metadata_words_at_bottom;
   bool available = chunk_free_room >= total_size_needed;
   log_develop_trace(continuations)("chunk available: %s size: %d argsize: %d top: " INTPTR_FORMAT " bottom: " INTPTR_FORMAT,
     available ? "yes" : "no" , total_size_needed, _cont.argsize(), p2i(_cont_stack_top), p2i(_cont_stack_bottom));
   return available ? total_size_needed : 0;
@@ -656,11 +826,11 @@
   chunk->set_max_thawing_size(cont_size());
   chunk->set_argsize(_cont.argsize());
 
   // in a fresh chunk, we freeze *with* the bottom-most frame's stack arguments.
   // They'll then be stored twice: in the chunk and in the parent chunk's top frame
-  const int chunk_start_sp = cont_size() + frame::metadata_words;
+  const int chunk_start_sp = cont_size() + frame::metadata_words + _monitors_in_lockstack;
   assert(chunk_start_sp == chunk->stack_size(), "");
 
   DEBUG_ONLY(_orig_chunk_sp = chunk->start_address() + chunk_start_sp;)
 
   freeze_fast_copy(chunk, chunk_start_sp CONT_JFR_ONLY(COMMA true));
@@ -685,11 +855,11 @@
     p2i((oopDesc*)chunk), chunk->stack_size(), chunk_start_sp, _cont.argsize());
   assert(chunk_start_sp <= chunk->stack_size(), "");
   assert(chunk_start_sp >= cont_size(), "no room in the chunk");
 
   const int chunk_new_sp = chunk_start_sp - cont_size(); // the chunk's new sp, after freeze
-  assert(!(_fast_freeze_size > 0) || _orig_chunk_sp - (chunk->start_address() + chunk_new_sp) == _fast_freeze_size, "");
+  assert(!(_fast_freeze_size > 0) || (_orig_chunk_sp - (chunk->start_address() + chunk_new_sp)) == (_fast_freeze_size - _monitors_in_lockstack), "");
 
   intptr_t* chunk_top = chunk->start_address() + chunk_new_sp;
 #ifdef ASSERT
   if (!_empty) {
     intptr_t* retaddr_slot = (_orig_chunk_sp
@@ -726,10 +896,15 @@
   chunk->set_sp(chunk_new_sp);
   // set chunk->pc to the return address of the topmost frame in the chunk
   chunk->set_pc(ContinuationHelper::return_address_at(
                   _cont_stack_top - frame::sender_sp_ret_address_offset()));
 
+  // Fix monitors after unwinding frames to make sure oops are valid.
+  if (_monitors_to_fix > 0) {
+    fix_monitors_in_fast_path();
+  }
+
   _cont.write();
 
   log_develop_trace(continuations)("FREEZE CHUNK #" INTPTR_FORMAT " (young)", _cont.hash());
   LogTarget(Trace, continuations) lt;
   if (lt.develop_is_enabled()) {
@@ -784,53 +959,44 @@
   freeze_result res = recurse_freeze(f, caller, 0, false, true);
 
   if (res == freeze_ok) {
     finish_freeze(f, caller);
     _cont.write();
+    assert(_monitors_to_fix == 0, "missing monitors in stack");
+    assert(_thread->held_monitor_count() == 0, "should be 0 now");
   }
 
   return res;
 }
 
 frame FreezeBase::freeze_start_frame() {
-  frame f = _thread->last_frame();
   if (LIKELY(!_preempt)) {
-    return freeze_start_frame_yield_stub(f);
+    return freeze_start_frame_yield_stub();
   } else {
-    return freeze_start_frame_safepoint_stub(f);
+    return freeze_start_frame_on_preempt();
   }
 }
 
-frame FreezeBase::freeze_start_frame_yield_stub(frame f) {
+frame FreezeBase::freeze_start_frame_yield_stub() {
+  frame f = _thread->last_frame();
   assert(SharedRuntime::cont_doYield_stub()->contains(f.pc()), "must be");
   f = sender<ContinuationHelper::NonInterpretedUnknownFrame>(f);
   assert(Continuation::is_frame_in_continuation(_thread->last_continuation(), f), "");
   return f;
 }
 
-frame FreezeBase::freeze_start_frame_safepoint_stub(frame f) {
-#if (defined(X86) || defined(AARCH64) || defined(RISCV64)) && !defined(ZERO)
-  f.set_fp(f.real_fp()); // f.set_fp(*Frame::callee_link_address(f)); // ????
-#else
-  Unimplemented();
-#endif
-  if (!Interpreter::contains(f.pc())) {
-    assert(ContinuationHelper::Frame::is_stub(f.cb()), "must be");
-    assert(f.oop_map() != nullptr, "must be");
-
-    if (Interpreter::contains(ContinuationHelper::StubFrame::return_pc(f))) {
-      f = sender<ContinuationHelper::StubFrame>(f); // Safepoint stub in interpreter
-    }
-  }
-  assert(Continuation::is_frame_in_continuation(_thread->last_continuation(), f), "");
-  return f;
+frame FreezeBase::freeze_start_frame_on_preempt() {
+  assert(_last_frame.sp() == _thread->last_frame().sp(), "_last_frame should be already initialized");
+  assert(Continuation::is_frame_in_continuation(_thread->last_continuation(), _last_frame), "");
+  return _last_frame;
 }
 
 // The parameter callee_argsize includes metadata that has to be part of caller/callee overlap.
 NOINLINE freeze_result FreezeBase::recurse_freeze(frame& f, frame& caller, int callee_argsize, bool callee_interpreted, bool top) {
   assert(f.unextended_sp() < _bottom_address, ""); // see recurse_freeze_java_frame
-  assert(f.is_interpreted_frame() || ((top && _preempt) == ContinuationHelper::Frame::is_stub(f.cb())), "");
+  assert(f.is_interpreted_frame() || ((top && _preempt) == ContinuationHelper::Frame::is_stub(f.cb()))
+         || ((top && _preempt) == f.is_native_frame()), "");
 
   if (stack_overflow()) {
     return freeze_exception;
   }
 
@@ -839,19 +1005,24 @@
       // special native frame
       return freeze_pinned_native;
     }
     return recurse_freeze_compiled_frame(f, caller, callee_argsize, callee_interpreted);
   } else if (f.is_interpreted_frame()) {
-    assert((_preempt && top) || !f.interpreter_frame_method()->is_native(), "");
+    assert(!f.interpreter_frame_method()->is_native() || (_preempt && top && _thread->is_on_monitorenter()), "");
     if (_preempt && top && f.interpreter_frame_method()->is_native()) {
-      // int native entry
+      // TODO: Allow preemption for this case too
       return freeze_pinned_native;
     }
-
     return recurse_freeze_interpreted_frame(f, caller, callee_argsize, callee_interpreted);
-  } else if (_preempt && top && ContinuationHelper::Frame::is_stub(f.cb())) {
-    return recurse_freeze_stub_frame(f, caller);
+  } else if (_preempt && top) {
+    assert(ContinuationHelper::Frame::is_stub(f.cb()) || (f.is_native_frame() && _thread->is_on_monitorenter()), "invariant");
+    if (f.is_native_frame()) {
+      // TODO: Allow preemption for this case too
+      return freeze_pinned_native;
+    } else {
+      return recurse_freeze_stub_frame(f, caller);
+    }
   } else {
     return freeze_pinned_native;
   }
 }
 
@@ -908,10 +1079,11 @@
 // The parameter argsize_md includes metadata that has to be part of caller/callee overlap.
 // See also StackChunkFrameStream<frame_kind>::frame_size()
 freeze_result FreezeBase::finalize_freeze(const frame& callee, frame& caller, int argsize_md) {
   int argsize = argsize_md - frame::metadata_words_at_top;
   assert(callee.is_interpreted_frame()
+    || ContinuationHelper::Frame::is_stub(callee.cb())
     || callee.cb()->as_nmethod()->is_osr_method()
     || argsize == _cont.argsize(), "argsize: %d cont.argsize: %d", argsize, _cont.argsize());
   log_develop_trace(continuations)("bottom: " INTPTR_FORMAT " count %d size: %d argsize: %d",
     p2i(_bottom_address), _frames, _freeze_size << LogBytesPerWord, argsize);
 
@@ -949,10 +1121,12 @@
 
   assert(chunk == nullptr || chunk->is_empty()
           || unextended_sp == chunk->to_offset(StackChunkFrameStream<ChunkFrames::Mixed>(chunk).unextended_sp()), "");
   assert(chunk != nullptr || unextended_sp < _freeze_size, "");
 
+  _freeze_size += _monitors_in_lockstack;
+
   // _barriers can be set to true by an allocation in freeze_fast, in which case the chunk is available
   bool allocated_old_in_freeze_fast = _barriers;
   assert(!allocated_old_in_freeze_fast || (unextended_sp >= _freeze_size && chunk->is_empty()),
     "Chunk allocated in freeze_fast is of insufficient size "
     "unextended_sp: %d size: %d is_empty: %d", unextended_sp, _freeze_size, chunk->is_empty());
@@ -1007,23 +1181,47 @@
   assert(!_barriers || is_empty(chunk), "");
 
   assert(!is_empty(chunk) || StackChunkFrameStream<ChunkFrames::Mixed>(chunk).is_done(), "");
   assert(!is_empty(chunk) || StackChunkFrameStream<ChunkFrames::Mixed>(chunk).to_frame().is_empty(), "");
 
+  if (_preempt) {
+    frame f = _thread->last_frame();
+    if (f.is_interpreted_frame()) {
+      // Do it now that we know freezing will be successful.
+      prepare_freeze_interpreted_top_frame(f);
+    }
+  }
+
   // We unwind frames after the last safepoint so that the GC will have found the oops in the frames, but before
   // writing into the chunk. This is so that an asynchronous stack walk (not at a safepoint) that suspends us here
   // will either see no continuation or a consistent chunk.
   unwind_frames();
 
-  chunk->set_max_thawing_size(chunk->max_thawing_size() + _freeze_size - frame::metadata_words);
+  chunk->set_max_thawing_size(chunk->max_thawing_size() + _freeze_size - _monitors_in_lockstack - frame::metadata_words);
 
   if (lt.develop_is_enabled()) {
     LogStream ls(lt);
     ls.print_cr("top chunk:");
     chunk->print_on(&ls);
   }
 
+  // Fix monitors after unwinding frames to make sure oops are valid. Also do it now to avoid unnecessary
+  // calls to fix_monitors_in_interpreted_frame/fix_monitors_in_compiled_frame() later.
+  if (_monitors_to_fix > 0) {
+    if (_monitors_in_lockstack > 0) {
+      assert(chunk->sp_address() - chunk->start_address() >= _monitors_in_lockstack, "no room for lockstack");
+      _thread->lock_stack().move_to_address((oop*)chunk->start_address());
+
+      chunk->set_lockStackSize((uint8_t)_monitors_in_lockstack);
+      chunk->set_has_lockStack(true);
+
+      _monitors_to_fix -= _monitors_in_lockstack;
+      assert(_monitors_to_fix == 0, "invariant");
+    }
+    _monitorenter_obj = _thread->is_on_monitorenter() ? _thread->current_pending_monitor()->object() : nullptr;
+  }
+
   // The topmost existing frame in the chunk; or an empty frame if the chunk is empty
   caller = StackChunkFrameStream<ChunkFrames::Mixed>(chunk).to_frame();
 
   DEBUG_ONLY(_last_write = caller.unextended_sp() + (empty_chunk ? argsize_md : overlap);)
 
@@ -1039,11 +1237,11 @@
 
   assert(!empty || Continuation::is_continuation_entry_frame(callee, nullptr), "");
 
   frame entry = sender(callee);
 
-  assert(Continuation::is_return_barrier_entry(entry.pc()) || Continuation::is_continuation_enterSpecial(entry), "");
+  assert((!empty && Continuation::is_return_barrier_entry(entry.pc())) || (empty && Continuation::is_continuation_enterSpecial(entry)), "");
   assert(callee.is_interpreted_frame() || entry.sp() == entry.unextended_sp(), "");
 #endif
 
   return freeze_ok_bottom;
 }
@@ -1106,12 +1304,13 @@
   // including metadata between f and its args
   const int argsize = ContinuationHelper::InterpretedFrame::stack_argsize(f) + frame::metadata_words_at_top;
 
   log_develop_trace(continuations)("recurse_freeze_interpreted_frame %s _size: %d fsize: %d argsize: %d",
     frame_method->name_and_sig_as_C_string(), _freeze_size, fsize, argsize);
-  // we'd rather not yield inside methods annotated with @JvmtiMountTransition
-  assert(!ContinuationHelper::Frame::frame_method(f)->jvmti_mount_transition(), "");
+  // we'd rather not yield inside methods annotated with @JvmtiMountTransition. In the preempt case
+  // we already checked it is safe to do so in Continuation::is_safe_vthread_to_preempt().
+  assert(!ContinuationHelper::Frame::frame_method(f)->jvmti_mount_transition() || _preempt, "");
 
   freeze_result result = recurse_freeze_java_frame<ContinuationHelper::InterpretedFrame>(f, caller, fsize, argsize);
   if (UNLIKELY(result > freeze_ok_bottom)) {
     return result;
   }
@@ -1142,10 +1341,15 @@
   caller = hf;
 
   // Mark frame_method's GC epoch for class redefinition on_stack calculation.
   frame_method->record_gc_epoch();
 
+  if (_monitors_to_fix > 0) {
+    // Check if we have monitors in this frame
+    fix_monitors_in_interpreted_frame(f);
+  }
+
   return freeze_ok;
 }
 
 // The parameter callee_argsize includes metadata that has to be part of caller/callee overlap.
 // See also StackChunkFrameStream<frame_kind>::frame_size()
@@ -1161,12 +1365,13 @@
 
   log_develop_trace(continuations)("recurse_freeze_compiled_frame %s _size: %d fsize: %d argsize: %d",
                              ContinuationHelper::Frame::frame_method(f) != nullptr ?
                              ContinuationHelper::Frame::frame_method(f)->name_and_sig_as_C_string() : "",
                              _freeze_size, fsize, argsize);
-  // we'd rather not yield inside methods annotated with @JvmtiMountTransition
-  assert(!ContinuationHelper::Frame::frame_method(f)->jvmti_mount_transition(), "");
+  // we'd rather not yield inside methods annotated with @JvmtiMountTransition. In the preempt case
+  // we already checked it is safe to do so in Continuation::is_safe_vthread_to_preempt().
+  assert(!ContinuationHelper::Frame::frame_method(f)->jvmti_mount_transition() || _preempt, "");
 
   freeze_result result = recurse_freeze_java_frame<ContinuationHelper::CompiledFrame>(f, caller, fsize, argsize);
   if (UNLIKELY(result > freeze_ok_bottom)) {
     return result;
   }
@@ -1191,52 +1396,51 @@
 
   assert(is_bottom_frame || Interpreter::contains(ContinuationHelper::CompiledFrame::real_pc(caller)) == caller.is_interpreted_frame(), "");
 
   DEBUG_ONLY(after_freeze_java_frame(hf, is_bottom_frame);)
   caller = hf;
+
+  if (_monitors_to_fix > 0) {
+    // Check if we have monitors in this frame
+    fix_monitors_in_compiled_frame(f, SmallRegisterMap::instance);
+  }
+
   return freeze_ok;
 }
 
 NOINLINE freeze_result FreezeBase::recurse_freeze_stub_frame(frame& f, frame& caller) {
+  DEBUG_ONLY(frame fsender = sender(f);)
+  assert(!fsender.is_native_frame() || (Continuation::is_continuation_enterSpecial(fsender) && !_cont.is_empty()), "sender should't be native except for enterSpecial case");
+
   intptr_t* const stack_frame_top = ContinuationHelper::StubFrame::frame_top(f, 0, 0);
   const int fsize = f.cb()->frame_size();
 
   log_develop_trace(continuations)("recurse_freeze_stub_frame %s _size: %d fsize: %d :: " INTPTR_FORMAT " - " INTPTR_FORMAT,
     f.cb()->name(), _freeze_size, fsize, p2i(stack_frame_top), p2i(stack_frame_top+fsize));
 
-  // recurse_freeze_java_frame and freeze inlined here because we need to use a full RegisterMap for lock ownership
-  NOT_PRODUCT(_frames++;)
-  _freeze_size += fsize;
-
-  RegisterMap map(_cont.thread(),
-                  RegisterMap::UpdateMap::include,
-                  RegisterMap::ProcessFrames::skip,
-                  RegisterMap::WalkContinuation::skip);
-  map.set_include_argument_oops(false);
-  ContinuationHelper::update_register_map<ContinuationHelper::StubFrame>(f, &map);
-  f.oop_map()->update_register_map(&f, &map); // we have callee-save registers in this case
-  frame senderf = sender<ContinuationHelper::StubFrame>(f);
-  assert(senderf.unextended_sp() < _bottom_address - 1, "");
-  assert(senderf.is_compiled_frame(), "");
-
-  if (UNLIKELY(senderf.oop_map() == nullptr)) {
-    // native frame
-    return freeze_pinned_native;
-  }
-
-  freeze_result result = recurse_freeze_compiled_frame(senderf, caller, 0, 0); // This might be deoptimized
+  freeze_result result = recurse_freeze_java_frame<ContinuationHelper::StubFrame>(f, caller, fsize, 0);
   if (UNLIKELY(result > freeze_ok_bottom)) {
     return result;
   }
-  assert(result != freeze_ok_bottom, "");
-  assert(!caller.is_interpreted_frame(), "");
 
-  DEBUG_ONLY(before_freeze_java_frame(f, caller, fsize, 0, false);)
+  bool is_bottom_frame = result == freeze_ok_bottom;
+  assert(!caller.is_empty() || (is_bottom_frame && !_cont.is_empty()), "");
+
+  DEBUG_ONLY(before_freeze_java_frame(f, caller, fsize, 0, is_bottom_frame);)
+
   frame hf = new_heap_frame<ContinuationHelper::StubFrame>(f, caller);
   intptr_t* heap_frame_top = ContinuationHelper::StubFrame::frame_top(hf, 0, 0);
+
   copy_to_chunk(stack_frame_top, heap_frame_top, fsize);
-  DEBUG_ONLY(after_freeze_java_frame(hf, false);)
+
+  if (caller.is_interpreted_frame()) {
+    _total_align_size += frame::align_wiggle;
+  }
+
+  patch(f, hf, caller, is_bottom_frame);
+
+  DEBUG_ONLY(after_freeze_java_frame(hf, is_bottom_frame);)
 
   caller = hf;
   return freeze_ok;
 }
 
@@ -1255,10 +1459,12 @@
   chunk->set_sp(chunk->to_offset(top.sp()));
   chunk->set_pc(top.pc());
 
   chunk->set_max_thawing_size(chunk->max_thawing_size() + _total_align_size);
 
+  assert(chunk->sp_address() - chunk->start_address() >= _monitors_in_lockstack, "clash with lockstack");
+
   // At this point the chunk is consistent
 
   if (UNLIKELY(_barriers)) {
     log_develop_trace(continuations)("do barriers on old chunk");
     // Serial and Parallel GC can allocate objects directly into the old generation.
@@ -1284,10 +1490,11 @@
   if (lt.develop_is_enabled()) {
     LogStream ls(lt);
     ls.print_cr("top hframe after (freeze):");
     assert(_cont.last_frame().is_heap_frame(), "should be");
     _cont.last_frame().print_on(&ls);
+    DEBUG_ONLY(print_frame_layout(top, false, &ls);)
   }
 
   assert(_cont.chunk_invariant(), "");
 }
 
@@ -1306,10 +1513,11 @@
 
 class StackChunkAllocator : public MemAllocator {
   const size_t                                 _stack_size;
   ContinuationWrapper&                         _continuation_wrapper;
   JvmtiSampledObjectAllocEventCollector* const _jvmti_event_collector;
+  JavaThread* const                            _target;
   mutable bool                                 _took_slow_path;
 
   // Does the minimal amount of initialization needed for a TLAB allocation.
   // We don't need to do a full initialization, as such an allocation need not be immediately walkable.
   virtual oop initialize(HeapWord* mem) const override {
@@ -1317,10 +1525,11 @@
     assert(_stack_size <= max_jint, "");
     assert(_word_size > _stack_size, "");
 
     // zero out fields (but not the stack)
     const size_t hs = oopDesc::header_size();
+    oopDesc::set_klass_gap(mem, 0);
     Copy::fill_to_aligned_words(mem + hs, vmClasses::StackChunk_klass()->size_helper() - hs);
 
     jdk_internal_vm_StackChunk::set_size(mem, (int)_stack_size);
     jdk_internal_vm_StackChunk::set_sp(mem, (int)_stack_size);
 
@@ -1339,21 +1548,25 @@
 
     oop obj = initialize(mem);
     return stackChunkOopDesc::cast(obj);
   }
 
+  bool is_preempt() const { return _thread != _target; }
+
 public:
   StackChunkAllocator(Klass* klass,
                       size_t word_size,
                       Thread* thread,
                       size_t stack_size,
                       ContinuationWrapper& continuation_wrapper,
-                      JvmtiSampledObjectAllocEventCollector* jvmti_event_collector)
+                      JvmtiSampledObjectAllocEventCollector* jvmti_event_collector,
+                      JavaThread* target)
     : MemAllocator(klass, word_size, thread),
       _stack_size(stack_size),
       _continuation_wrapper(continuation_wrapper),
       _jvmti_event_collector(jvmti_event_collector),
+      _target(target),
       _took_slow_path(false) {}
 
   // Provides it's own, specialized allocation which skips instrumentation
   // if the memory can be allocated without going to a slow-path.
   stackChunkOop allocate() const {
@@ -1403,11 +1616,11 @@
   //
   // This might safepoint while allocating, but all safepointing due to
   // instrumentation have been deferred. This property is important for
   // some GCs, as this ensures that the allocated object is in the young
   // generation / newly allocated memory.
-  StackChunkAllocator allocator(klass, size_in_words, current, stack_size, _cont, _jvmti_event_collector);
+  StackChunkAllocator allocator(klass, size_in_words, current, stack_size, _cont, _jvmti_event_collector, _thread);
   stackChunkOop chunk = allocator.allocate();
 
   if (chunk == nullptr) {
     return nullptr; // OOME
   }
@@ -1420,10 +1633,12 @@
   assert(chunk->max_thawing_size() == 0, "");
   assert(chunk->pc() == nullptr, "");
   assert(chunk->argsize() == 0, "");
   assert(chunk->flags() == 0, "");
   assert(chunk->is_gc_mode() == false, "");
+  assert(chunk->lockStackSize() == 0, "");
+  assert(chunk->objectMonitor() == nullptr, "");
 
   // fields are uninitialized
   chunk->set_parent_access<IS_DEST_UNINITIALIZED>(_cont.last_nonempty_chunk());
   chunk->set_cont_access<IS_DEST_UNINITIALIZED>(_cont.continuation());
 
@@ -1492,28 +1707,55 @@
     ContinuationWrapper::SafepointOp so(Thread::current(), cont);
     JvmtiExport::continuation_yield_cleanup(JavaThread::current(), num_frames);
   }
   invalidate_jvmti_stack(thread);
 }
-#endif // INCLUDE_JVMTI
 
-#ifdef ASSERT
-static bool monitors_on_stack(JavaThread* thread) {
-  ContinuationEntry* ce = thread->last_continuation();
-  RegisterMap map(thread,
-                  RegisterMap::UpdateMap::include,
-                  RegisterMap::ProcessFrames::include,
-                  RegisterMap::WalkContinuation::skip);
-  map.set_include_argument_oops(false);
-  for (frame f = thread->last_frame(); Continuation::is_frame_in_continuation(ce, f); f = f.sender(&map)) {
-    if ((f.is_interpreted_frame() && ContinuationHelper::InterpretedFrame::is_owning_locks(f)) ||
-        (f.is_compiled_frame() && ContinuationHelper::CompiledFrame::is_owning_locks(map.thread(), &map, f))) {
-      return true;
+static void jvmti_mount_end(JavaThread* current, ContinuationWrapper& cont, frame top, ObjectMonitor* mon, bool post_mount_event) {
+  assert(current->vthread() != nullptr, "must be");
+
+  HandleMarkCleaner hm(current);
+  Handle vth(current, current->vthread());
+
+  ContinuationWrapper::SafepointOp so(current, cont);
+
+  // Since we might safepoint set the anchor so that the stack can we walked.
+  set_anchor(current, top.sp());
+
+  JRT_BLOCK
+    current->rebind_to_jvmti_thread_state_of(vth());
+    {
+      MutexLocker mu(JvmtiThreadState_lock);
+      JvmtiThreadState* state = current->jvmti_thread_state();
+      if (state != NULL && state->is_pending_interp_only_mode()) {
+        JvmtiEventController::enter_interp_only_mode();
+      }
     }
-  }
-  return false;
+    assert(current->is_in_VTMS_transition(), "sanity check");
+    assert(!current->is_in_tmp_VTMS_transition(), "sanity check");
+    JvmtiVTMSTransitionDisabler::finish_VTMS_transition((jthread)vth.raw_value(), /* is_mount */ true);
+
+    if (post_mount_event && JvmtiExport::should_post_vthread_mount()) {
+      JvmtiExport::post_vthread_mount((jthread)vth.raw_value());
+    } else if (!post_mount_event) {
+      // Preemption cancelled case. Clear the unmount event pending flag. The
+      // flag might actually not be set if _VTMS_notify_jvmti_events was enabled
+      // after preemption happened (late binding agents). But since this would
+      // be a rare case we just do it unconditionally.
+      current->set_jvmti_unmount_event_pending(false);
+    }
+
+    if (mon != nullptr) {
+      JvmtiExport::post_monitor_contended_entered(current, mon);
+    }
+  JRT_BLOCK_END
+
+  clear_anchor(current);
 }
+#endif // INCLUDE_JVMTI
+
+#ifdef ASSERT
 
 // There are no interpreted frames if we're not called from the interpreter and we haven't ancountered an i2c
 // adapter or called Deoptimization::unpack_frames. As for native frames, upcalls from JNI also go through the
 // interpreter (see JavaCalls::call_helper), while the UpcallLinker explicitly sets cont_fastpath.
 bool FreezeBase::check_valid_fast_path() {
@@ -1521,27 +1763,25 @@
   RegisterMap map(_thread,
                   RegisterMap::UpdateMap::skip,
                   RegisterMap::ProcessFrames::skip,
                   RegisterMap::WalkContinuation::skip);
   map.set_include_argument_oops(false);
-  for (frame f = freeze_start_frame(); Continuation::is_frame_in_continuation(ce, f); f = f.sender(&map)) {
-    if (!f.is_compiled_frame() || f.is_deoptimized_frame()) {
+  int i = 0;
+  for (frame f = freeze_start_frame(); Continuation::is_frame_in_continuation(ce, f); f = f.sender(&map), i++) {
+    if (!((f.is_compiled_frame() && !f.is_deoptimized_frame()) || (i == 0 && f.is_runtime_frame()))) {
       return false;
     }
   }
   return true;
 }
 #endif // ASSERT
 
-static inline int freeze_epilog(JavaThread* thread, ContinuationWrapper& cont) {
+static inline int freeze_epilog(ContinuationWrapper& cont) {
   verify_continuation(cont.continuation());
   assert(!cont.is_empty(), "");
-  // This is done for the sake of the enterSpecial frame
-  StackWatermarkSet::after_unwind(thread);
 
   log_develop_debug(continuations)("=== End of freeze cont ### #" INTPTR_FORMAT, cont.hash());
-
   return 0;
 }
 
 static int freeze_epilog(JavaThread* thread, ContinuationWrapper& cont, freeze_result res) {
   if (UNLIKELY(res != freeze_ok)) {
@@ -1549,19 +1789,37 @@
     log_develop_trace(continuations)("=== end of freeze (fail %d)", res);
     return res;
   }
 
   JVMTI_ONLY(jvmti_yield_cleanup(thread, cont)); // can safepoint
-  return freeze_epilog(thread, cont);
+  return freeze_epilog(cont);
 }
 
-template<typename ConfigT>
+static int preempt_epilog(ContinuationWrapper& cont, freeze_result res, frame& old_last_frame) {
+  if (UNLIKELY(res != freeze_ok)) {
+    verify_continuation(cont.continuation());
+    log_develop_trace(continuations)("=== end of freeze (fail %d)", res);
+    return res;
+  }
+
+  patch_return_pc_with_preempt_stub(old_last_frame);
+  cont.set_preempted(true);
+  cont.tail()->set_is_preempted(true);
+
+  if (cont.thread()->is_on_monitorenter()) {
+    cont.tail()->set_objectMonitor(cont.thread()->current_pending_monitor());
+  }
+
+  return freeze_epilog(cont);
+}
+
+template<typename ConfigT, bool preempt>
 static inline int freeze_internal(JavaThread* current, intptr_t* const sp) {
   assert(!current->has_pending_exception(), "");
 
 #ifdef ASSERT
-  log_trace(continuations)("~~~~ freeze sp: " INTPTR_FORMAT, p2i(current->last_continuation()->entry_sp()));
+  log_trace(continuations)("~~~~ freeze sp: " INTPTR_FORMAT "JavaThread: " INTPTR_FORMAT, p2i(current->last_continuation()->entry_sp()), p2i(current));
   log_frames(current);
 #endif
 
   CONT_JFR_ONLY(EventContinuationFreeze event;)
 
@@ -1575,34 +1833,56 @@
   ContinuationWrapper cont(current, oopCont);
   log_develop_debug(continuations)("FREEZE #" INTPTR_FORMAT " " INTPTR_FORMAT, cont.hash(), p2i((oopDesc*)oopCont));
 
   assert(entry->is_virtual_thread() == (entry->scope(current) == java_lang_VirtualThread::vthread_scope()), "");
 
-  assert(monitors_on_stack(current) == ((current->held_monitor_count() - current->jni_monitor_count()) > 0),
-         "Held monitor count and locks on stack invariant: " INT64_FORMAT " JNI: " INT64_FORMAT, (int64_t)current->held_monitor_count(), (int64_t)current->jni_monitor_count());
-
-  if (entry->is_pinned() || current->held_monitor_count() > 0) {
+  const bool pinned_monitor = NOT_LOOM_MONITOR_SUPPORT(current->held_monitor_count() > 0) LOOM_MONITOR_SUPPORT_ONLY(current->jni_monitor_count() > 0);
+  if (entry->is_pinned() || pinned_monitor) {
     log_develop_debug(continuations)("PINNED due to critical section/hold monitor");
     verify_continuation(cont.continuation());
     freeze_result res = entry->is_pinned() ? freeze_pinned_cs : freeze_pinned_monitor;
     log_develop_trace(continuations)("=== end of freeze (fail %d)", res);
     return res;
   }
 
-  Freeze<ConfigT> freeze(current, cont, sp);
+  Freeze<ConfigT> freeze(current, cont, sp, preempt);
+
+  if (preempt) {
+    freeze.set_last_frame();  // remember last frame
+#ifdef AARCH64
+    JvmtiSampledObjectAllocEventCollector jsoaec(false);
+    freeze.set_jvmti_event_collector(&jsoaec);
+
+    // Force aarch64 to slow path always for now. It needs extra instructions to correctly set
+    // the last pc we copy into the stackChunk, since it will not necessarily be at sp[-1]).
+    freeze_result res = freeze.freeze_slow();
+    CONT_JFR_ONLY(cont.post_jfr_event(&event, oopCont, current);)
+    return preempt_epilog(cont, res, freeze.last_frame());
+#endif
+  }
 
   assert(!current->cont_fastpath() || freeze.check_valid_fast_path(), "");
   bool fast = UseContinuationFastPath && current->cont_fastpath();
   if (fast && freeze.size_if_fast_freeze_available() > 0) {
     freeze.freeze_fast_existing_chunk();
     CONT_JFR_ONLY(freeze.jfr_info().post_jfr_event(&event, oopCont, current);)
-    freeze_epilog(current, cont);
-    return 0;
+    return !preempt ? freeze_epilog(cont) : preempt_epilog(cont, freeze_ok, freeze.last_frame());
+  }
+
+  if (preempt) {
+    JvmtiSampledObjectAllocEventCollector jsoaec(false);
+    freeze.set_jvmti_event_collector(&jsoaec);
+
+    freeze_result res = fast ? freeze.try_freeze_fast() : freeze.freeze_slow();
+
+    CONT_JFR_ONLY(freeze.jfr_info().post_jfr_event(&event, oopCont, current);)
+    preempt_epilog(cont, res, freeze.last_frame());
+    return res;
   }
 
   log_develop_trace(continuations)("chunk unavailable; transitioning to VM");
-  assert(current == JavaThread::current(), "must be current thread except for preempt");
+  assert(current == JavaThread::current(), "must be current thread");
   JRT_BLOCK
     // delays a possible JvmtiSampledObjectAllocEventCollector in alloc_chunk
     JvmtiSampledObjectAllocEventCollector jsoaec(false);
     freeze.set_jvmti_event_collector(&jsoaec);
 
@@ -1678,10 +1958,11 @@
 
 static int thaw_size(stackChunkOop chunk) {
   int size = chunk->max_thawing_size();
   size += frame::metadata_words; // For the top pc+fp in push_return_frame or top = stack_sp - frame::metadata_words in thaw_fast
   size += 2*frame::align_wiggle; // in case of alignments at the top and bottom
+  size += frame::metadata_words; // for preemption case (see push_preempt_rerun_adapter)
   return size;
 }
 
 // make room on the stack for thaw
 // returns the size in bytes, or 0 on failure
@@ -1758,15 +2039,18 @@
 
   // fast path
   inline void prefetch_chunk_pd(void* start, int size_words);
   void patch_return(intptr_t* sp, bool is_last);
 
-  // slow path
-  NOINLINE intptr_t* thaw_slow(stackChunkOop chunk, bool return_barrier);
+  intptr_t* handle_preempted_continuation(stackChunkOop original_chunk, intptr_t* sp, bool fast_case);
+  inline intptr_t* push_preempt_rerun_adapter(frame top, bool is_interpreted_frame);
+  inline intptr_t* push_preempt_monitorenter_redo(stackChunkOop chunk);
 
-private:
   void recurse_thaw(const frame& heap_frame, frame& caller, int num_frames, bool top);
+  void finish_thaw(frame& f);
+
+private:
   template<typename FKind> bool recurse_thaw_java_frame(frame& caller, int num_frames);
   void finalize_thaw(frame& entry, int argsize);
 
   inline bool seen_by_gc();
 
@@ -1776,16 +2060,16 @@
   void clear_bitmap_bits(address start, address end);
 
   NOINLINE void recurse_thaw_interpreted_frame(const frame& hf, frame& caller, int num_frames);
   void recurse_thaw_compiled_frame(const frame& hf, frame& caller, int num_frames, bool stub_caller);
   void recurse_thaw_stub_frame(const frame& hf, frame& caller, int num_frames);
-  void finish_thaw(frame& f);
 
   void push_return_frame(frame& f);
   inline frame new_entry_frame();
   template<typename FKind> frame new_stack_frame(const frame& hf, frame& caller, bool bottom);
   inline void patch_pd(frame& f, const frame& sender);
+  inline void patch_pd(frame& f, intptr_t* caller_sp);
   inline intptr_t* align(const frame& hf, intptr_t* frame_sp, frame& caller, bool bottom);
 
   void maybe_set_fastpath(intptr_t* sp) { if (sp > _fastpath) _fastpath = sp; }
 
   static inline void derelativize_interpreted_frame_metadata(const frame& hf, const frame& f);
@@ -1806,10 +2090,11 @@
            && !PreserveFramePointer;
   }
 
   inline intptr_t* thaw(Continuation::thaw_kind kind);
   NOINLINE intptr_t* thaw_fast(stackChunkOop chunk);
+  NOINLINE intptr_t* thaw_slow(stackChunkOop chunk, Continuation::thaw_kind kind);
   inline void patch_caller_links(intptr_t* sp, intptr_t* bottom);
 };
 
 template <typename ConfigT>
 inline intptr_t* Thaw<ConfigT>::thaw(Continuation::thaw_kind kind) {
@@ -1821,11 +2106,11 @@
   assert(chunk != nullptr, "guaranteed by prepare_thaw");
   assert(!chunk->is_empty(), "guaranteed by prepare_thaw");
 
   _barriers = chunk->requires_barriers();
   return (LIKELY(can_thaw_fast(chunk))) ? thaw_fast(chunk)
-                                        : thaw_slow(chunk, kind != Continuation::thaw_top);
+                                        : thaw_slow(chunk, kind);
 }
 
 class ReconstructedStack : public StackObj {
   intptr_t* _base;  // _cont.entrySP(); // top of the entry frame
   int _thaw_size;
@@ -1862,17 +2147,31 @@
   StackChunkFrameStream<ChunkFrames::CompiledOnly> f(chunk);
   DEBUG_ONLY(intptr_t* const chunk_sp = chunk->start_address() + chunk->sp();)
   assert(chunk_sp == f.sp(), "");
   assert(chunk_sp == f.unextended_sp(), "");
 
-  const int frame_size = f.cb()->frame_size();
+  int frame_size = f.cb()->frame_size();
   argsize = f.stack_argsize();
+  bool is_stub = f.is_stub();
 
   f.next(SmallRegisterMap::instance, true /* stop */);
   empty = f.is_done();
   assert(!empty || argsize == chunk->argsize(), "");
 
+  assert(!is_stub || !empty, "runtime stub should have caller frame");
+  if (is_stub) {
+    // If we don't thaw the top compiled frame too, after restoring the saved
+    // registers back in Java, we would hit the return barrier to thaw one more
+    // frame effectively overwritting the restored registers during that call.
+    f.get_cb();
+    frame_size += f.cb()->frame_size();
+    argsize = f.stack_argsize();
+    f.next(SmallRegisterMap::instance, true /* stop */);
+    empty = f.is_done();
+    assert(!empty || argsize == chunk->argsize(), "");
+  }
+
   if (empty) {
     clear_chunk(chunk);
   } else {
     chunk->set_sp(chunk->sp() + frame_size);
     chunk->set_max_thawing_size(chunk->max_thawing_size() - frame_size);
@@ -1999,15 +2298,63 @@
 
 inline bool ThawBase::seen_by_gc() {
   return _barriers || _cont.tail()->is_gc_mode();
 }
 
-NOINLINE intptr_t* ThawBase::thaw_slow(stackChunkOop chunk, bool return_barrier) {
+template <typename ConfigT>
+NOINLINE intptr_t* Thaw<ConfigT>::thaw_slow(stackChunkOop chunk, Continuation::thaw_kind kind) {
+  bool retry_fast_path = false;
+
+  bool preempted_case = chunk->is_preempted();
+  if (preempted_case) {
+    assert(_cont.is_preempted(), "must be");
+    assert(chunk->objectMonitor() != nullptr, "must be");
+
+    ObjectMonitor* mon = chunk->objectMonitor();
+    if (!mon->is_owner(_thread)) {
+      return push_preempt_monitorenter_redo(chunk);
+    }
+    chunk->set_is_preempted(false);
+    retry_fast_path = true;
+  }
+
+#if INCLUDE_ZGC || INCLUDE_SHENANDOAHGC
+  if (UseZGC || UseShenandoahGC) {
+    _cont.tail()->relativize_derived_pointers_concurrently();
+  }
+#endif
+
+  // First thaw after freeze. If there were oops in the stacklock
+  // during freeze, restore them now.
+  if (chunk->lockStackSize() > 0) {
+    int lockStackSize = chunk->lockStackSize();
+    assert(lockStackSize > 0, "should be");
+
+    oop tmp_lockstack[8];
+    chunk->copy_lockstack(tmp_lockstack);
+    _thread->lock_stack().move_from_address(tmp_lockstack, lockStackSize);
+
+    chunk->set_lockStackSize(0);
+    chunk->set_has_lockStack(false);
+    retry_fast_path = true;
+  }
+
+  // Retry the fast path now that we possibly cleared the FLAG_HAS_LOCKSTACK
+  // and FLAG_PREEMPTED flags from the stackChunk.
+  if (retry_fast_path && can_thaw_fast(chunk)) {
+    intptr_t* sp = thaw_fast(chunk);
+    if (preempted_case) {
+      assert(_cont.is_preempted(), "must be");
+      return handle_preempted_continuation(chunk, sp, true /* fast_case */);
+    }
+    return sp;
+  }
+
   LogTarget(Trace, continuations) lt;
   if (lt.develop_is_enabled()) {
     LogStream ls(lt);
-    ls.print_cr("thaw slow return_barrier: %d " INTPTR_FORMAT, return_barrier, p2i(chunk));
+    ls.print_cr("thaw slow return_barrier: %d " INTPTR_FORMAT, kind, p2i(chunk));
     chunk->print_on(true, &ls);
   }
 
 #if CONT_JFR
   EventContinuationThawSlow e;
@@ -2017,52 +2364,53 @@
   }
 #endif
 
   DEBUG_ONLY(_frames = 0;)
   _align_size = 0;
-  int num_frames = (return_barrier ? 1 : 2);
+  bool is_return_barrier = kind != Continuation::thaw_top;
+  int num_frames = (is_return_barrier ? 1 : 2);
 
   _stream = StackChunkFrameStream<ChunkFrames::Mixed>(chunk);
   _top_unextended_sp_before_thaw = _stream.unextended_sp();
 
+  stackChunkOop original_chunk = chunk;
+
   frame heap_frame = _stream.to_frame();
   if (lt.develop_is_enabled()) {
     LogStream ls(lt);
     ls.print_cr("top hframe before (thaw):");
     assert(heap_frame.is_heap_frame(), "should have created a relative frame");
     heap_frame.print_value_on(&ls, nullptr);
   }
 
-#if INCLUDE_ZGC || INCLUDE_SHENANDOAHGC
-  if (UseZGC || UseShenandoahGC) {
-    _cont.tail()->relativize_derived_pointers_concurrently();
-  }
-#endif
-
   frame caller; // the thawed caller on the stack
   recurse_thaw(heap_frame, caller, num_frames, true);
   finish_thaw(caller); // caller is now the topmost thawed frame
   _cont.write();
 
   assert(_cont.chunk_invariant(), "");
 
-  JVMTI_ONLY(if (!return_barrier) invalidate_jvmti_stack(_thread));
+  JVMTI_ONLY(if (!is_return_barrier) invalidate_jvmti_stack(_thread));
 
   _thread->set_cont_fastpath(_fastpath);
 
   intptr_t* sp = caller.sp();
+
+  if (preempted_case) {
+    assert(_cont.is_preempted(), "must be");
+    return handle_preempted_continuation(original_chunk, sp, false /* fast_case */);
+  }
   return sp;
 }
 
 void ThawBase::recurse_thaw(const frame& heap_frame, frame& caller, int num_frames, bool top) {
   log_develop_debug(continuations)("thaw num_frames: %d", num_frames);
   assert(!_cont.is_empty(), "no more frames");
   assert(num_frames > 0, "");
   assert(!heap_frame.is_empty(), "");
 
-  if (top && heap_frame.is_safepoint_blob_frame()) {
-    assert(ContinuationHelper::Frame::is_stub(heap_frame.cb()), "cb: %s", heap_frame.cb()->name());
+  if (top && ContinuationHelper::Frame::is_stub(heap_frame.cb())) {
     recurse_thaw_stub_frame(heap_frame, caller, num_frames);
   } else if (!heap_frame.is_interpreted_frame()) {
     recurse_thaw_compiled_frame(heap_frame, caller, num_frames, false);
   } else {
     recurse_thaw_interpreted_frame(heap_frame, caller, num_frames);
@@ -2180,10 +2528,59 @@
   stackChunkOop chunk = _cont.tail();
   chunk->bitmap().clear_range(chunk->bit_index_for(start), chunk->bit_index_for(effective_end));
   assert(effective_end == end || !chunk->bitmap().at(chunk->bit_index_for(effective_end)), "bit should not be set");
 }
 
+intptr_t* ThawBase::handle_preempted_continuation(stackChunkOop original_chunk, intptr_t* sp, bool fast_case) {
+  frame top(sp);
+  assert(top.pc() == *(address*)(sp - frame::sender_sp_ret_address_offset()), "");
+  bool top_is_interpreted = Interpreter::contains(top.pc());
+
+  if (fast_case) {
+    assert(ContinuationHelper::Frame::is_stub(top.cb()), "invariant");
+    int fsize = ContinuationHelper::StubFrame::size(top);
+    patch_pd(top, sp + fsize);
+  }
+
+  _cont.set_preempted(false);
+  bool same_chunk = original_chunk == _cont.tail();
+
+  ObjectMonitor* mon = original_chunk->objectMonitor();
+  if (same_chunk && mon != nullptr) {
+    original_chunk->set_objectMonitor(nullptr);
+  }
+
+  bool post_mount_event = true;
+  if (_thread->preemption_cancelled()) {
+    // Since we never actually unmounted don't post the mount event.
+    post_mount_event = false;
+    _thread->set_preemption_cancelled(false);
+  }
+
+#if INCLUDE_JVMTI
+  bool is_vthread = Continuation::continuation_scope(_cont.continuation()) == java_lang_VirtualThread::vthread_scope();
+  if (is_vthread) {
+    if (JvmtiVTMSTransitionDisabler::VTMS_notify_jvmti_events()) {
+      jvmti_mount_end(_thread, _cont, top, mon, post_mount_event);
+    } else {
+      _thread->set_is_in_VTMS_transition(false);
+      java_lang_Thread::set_is_in_VTMS_transition(_thread->vthread(), false);
+    }
+  }
+#endif
+
+  if (!top_is_interpreted) {
+    assert(ContinuationHelper::Frame::is_stub(top.cb()), "invariant");
+    // The continuation might now run on a different platform thread than the previous time so
+    // we need to adjust the current thread saved in the stub frame before restoring registers.
+    JavaThread** thread_addr = frame::saved_thread_address(top);
+    if (thread_addr != nullptr) *thread_addr = _thread;
+  }
+  sp = push_preempt_rerun_adapter(top, top_is_interpreted /* is_interpreted_frame */);
+  return sp;
+}
+
 NOINLINE void ThawBase::recurse_thaw_interpreted_frame(const frame& hf, frame& caller, int num_frames) {
   assert(hf.is_interpreted_frame(), "");
 
   if (UNLIKELY(seen_by_gc())) {
     _cont.tail()->do_barriers<stackChunkOopDesc::BarrierType::Store>(_stream, SmallRegisterMap::instance);
@@ -2240,11 +2637,11 @@
   DEBUG_ONLY(after_thaw_java_frame(f, is_bottom_frame);)
   caller = f;
 }
 
 void ThawBase::recurse_thaw_compiled_frame(const frame& hf, frame& caller, int num_frames, bool stub_caller) {
-  assert(!hf.is_interpreted_frame(), "");
+  assert(hf.is_compiled_frame(), "");
   assert(_cont.is_preempted() || !stub_caller, "stub caller not at preemption");
 
   if (!stub_caller && UNLIKELY(seen_by_gc())) { // recurse_thaw_stub_frame already invoked our barriers with a full regmap
     _cont.tail()->do_barriers<stackChunkOopDesc::BarrierType::Store>(_stream, SmallRegisterMap::instance);
   }
@@ -2254,11 +2651,11 @@
   DEBUG_ONLY(before_thaw_java_frame(hf, caller, is_bottom_frame, num_frames);)
 
   assert(caller.sp() == caller.unextended_sp(), "");
 
   if ((!is_bottom_frame && caller.is_interpreted_frame()) || (is_bottom_frame && Interpreter::contains(_cont.tail()->pc()))) {
-    _align_size += frame::align_wiggle; // we add one whether or not we've aligned because we add it in freeze_interpreted_frame
+    _align_size += frame::align_wiggle; // we add one whether or not we've aligned because we add it in recurse_freeze_compiled_frame
   }
 
   // new_stack_frame must construct the resulting frame using hf.pc() rather than hf.raw_pc() because the frame is not
   // yet laid out in the stack, and so the original_pc is not stored in it.
   // As a result, f.is_deoptimized_frame() is always false and we must test hf to know if the frame is deoptimized.
@@ -2290,11 +2687,10 @@
     maybe_set_fastpath(f.sp());
   } else if (_thread->is_interp_only_mode()
               || (_cont.is_preempted() && f.cb()->as_nmethod()->is_marked_for_deoptimization())) {
     // The caller of the safepoint stub when the continuation is preempted is not at a call instruction, and so
     // cannot rely on nmethod patching for deopt.
-    assert(_thread->is_interp_only_mode() || stub_caller, "expected a stub-caller");
 
     log_develop_trace(continuations)("Deoptimizing thawed frame");
     DEBUG_ONLY(ContinuationHelper::Frame::patch_pc(f, nullptr));
 
     f.deoptimize(nullptr); // the null thread simply avoids the assertion in deoptimize which we're not set up for
@@ -2317,54 +2713,73 @@
   caller = f;
 }
 
 void ThawBase::recurse_thaw_stub_frame(const frame& hf, frame& caller, int num_frames) {
   DEBUG_ONLY(_frames++;)
+  bool is_bottom_frame = false;
 
-  {
+  if (UNLIKELY(seen_by_gc())) {
+    // Process the stub's caller here since we might need the full map (if the stub was
+    // generated on a poll on return we shouldn't need a full map).
     RegisterMap map(nullptr,
                     RegisterMap::UpdateMap::include,
                     RegisterMap::ProcessFrames::skip,
                     RegisterMap::WalkContinuation::skip);
     map.set_include_argument_oops(false);
     _stream.next(&map);
-    assert(!_stream.is_done(), "");
-    if (UNLIKELY(seen_by_gc())) { // we're now doing this on the stub's caller
+    if (!_stream.is_done()) {
       _cont.tail()->do_barriers<stackChunkOopDesc::BarrierType::Store>(_stream, &map);
     }
-    assert(!_stream.is_done(), "");
+  } else {
+    _stream.next(SmallRegisterMap::instance);
   }
 
-  recurse_thaw_compiled_frame(_stream.to_frame(), caller, num_frames, true); // this could be deoptimized
-
-  DEBUG_ONLY(before_thaw_java_frame(hf, caller, false, num_frames);)
+  if (_stream.is_done()) {
+    finalize_thaw(caller, 0);
+    is_bottom_frame = true;
+  } else {
+    frame f = _stream.to_frame();
+    if (f.is_interpreted_frame()) {
+      recurse_thaw_interpreted_frame(f, caller, num_frames);
+    } else {
+      recurse_thaw_compiled_frame(f, caller, num_frames, true);
+    }
+  }
 
-  assert(ContinuationHelper::Frame::is_stub(hf.cb()), "");
+  assert(!is_bottom_frame || !_cont.is_empty(), "");
   assert(caller.sp() == caller.unextended_sp(), "");
-  assert(!caller.is_interpreted_frame(), "");
+  assert(!caller.is_native_frame() || (is_bottom_frame && Continuation::is_continuation_enterSpecial(caller)), "caller should't be native except for enterSpecial case");
 
-  int fsize = ContinuationHelper::StubFrame::size(hf);
+  DEBUG_ONLY(before_thaw_java_frame(hf, caller, is_bottom_frame, num_frames);)
+
+  if ((!is_bottom_frame && caller.is_interpreted_frame()) || (is_bottom_frame && Interpreter::contains(_cont.tail()->pc()))) {
+    _align_size += frame::align_wiggle; // we add one whether or not we've aligned because we add it in recurse_freeze_stub_frame
+  }
 
   frame f = new_stack_frame<ContinuationHelper::StubFrame>(hf, caller, false);
   intptr_t* stack_frame_top = f.sp();
   intptr_t* heap_frame_top = hf.sp();
+  int fsize = ContinuationHelper::StubFrame::size(hf);
 
   copy_from_chunk(heap_frame_top - frame::metadata_words, stack_frame_top - frame::metadata_words,
                   fsize + frame::metadata_words);
 
-  { // can only fix caller once this frame is thawed (due to callee saved regs)
+  patch(f, caller, is_bottom_frame);
+
+  if (!is_bottom_frame) {
+    // can only fix caller once this frame is thawed (due to callee saved regs)
     RegisterMap map(nullptr,
                     RegisterMap::UpdateMap::include,
                     RegisterMap::ProcessFrames::skip,
-                    RegisterMap::WalkContinuation::skip); // map.clear();
+                    RegisterMap::WalkContinuation::skip);
     map.set_include_argument_oops(false);
     f.oop_map()->update_register_map(&f, &map);
     ContinuationHelper::update_register_map_with_callee(caller, &map);
     _cont.tail()->fix_thawed_frame(caller, &map);
   }
 
-  DEBUG_ONLY(after_thaw_java_frame(f, false);)
+  DEBUG_ONLY(after_thaw_java_frame(f, is_bottom_frame);)
   caller = f;
 }
 
 void ThawBase::finish_thaw(frame& f) {
   stackChunkOop chunk = _cont.tail();
@@ -2452,16 +2867,33 @@
 
   Thaw<ConfigT> thw(thread, cont);
   intptr_t* const sp = thw.thaw(kind);
   assert(is_aligned(sp, frame::frame_alignment), "");
 
-  // All the frames have been thawed so we know they don't hold any monitors
-  assert(thread->held_monitor_count() == 0, "Must be");
-
 #ifdef ASSERT
   intptr_t* sp0 = sp;
-  set_anchor(thread, sp0);
+  address pc0 = *(address*)(sp - frame::sender_sp_ret_address_offset());
+
+  bool sent_entry_pc = false;
+  if (pc0 == Interpreter::cont_preempt_rerun_interpreter_adapter() ||
+      pc0 == StubRoutines::cont_preempt_rerun_compiler_adapter()) {
+    sp0 += frame::metadata_words;    // see push_preempt_rerun_adapter
+#ifdef AARCH64
+    if (pc0 == StubRoutines::cont_preempt_rerun_compiler_adapter()) {
+      address pc1 = *(address*)(sp0 - frame::sender_sp_ret_address_offset());
+      CodeBlob* cb = CodeCache::find_blob(pc1);
+      assert(cb != nullptr, "should be either c1 or c2 runtime stub");
+      if (cb->frame_size() == 2) {
+        sp0 += frame::metadata_words;
+      }
+    }
+#endif
+  } else if (pc0 == StubRoutines::cont_preempt_monitorenter_redo()) {
+    sp0 += 2 * frame::metadata_words; // see push_preempt_monitorenter_redo
+    sent_entry_pc = true;
+  }
+  set_anchor(thread, sp0, sent_entry_pc ? cont.entryPC() : nullptr);
   log_frames(thread);
   if (LoomVerifyAfterThaw) {
     assert(do_verify_after_thaw(thread, cont.tail(), tty), "");
   }
   assert(ContinuationEntry::assert_entry_frame_laid_out(thread), "");
@@ -2569,18 +3001,18 @@
   }
   return true;
 }
 
 static void log_frames(JavaThread* thread) {
-  const static int show_entry_callers = 3;
+  const static int show_entry_callers = 100;
   LogTarget(Trace, continuations) lt;
   if (!lt.develop_is_enabled()) {
     return;
   }
   LogStream ls(lt);
 
-  ls.print_cr("------- frames ---------");
+  ls.print_cr("------- frames --------- for thread " INTPTR_FORMAT, p2i(thread));
   if (!thread->has_last_Java_frame()) {
     ls.print_cr("NO ANCHOR!");
   }
 
   RegisterMap map(thread,
@@ -2600,12 +3032,12 @@
     HandleMark hm(Thread::current());
     FrameValues values;
 
     int i = 0;
     int post_entry = -1;
-    for (frame f = thread->last_frame(); !f.is_entry_frame(); f = f.sender(&map)) {
-      f.describe(values, i++, &map);
+    for (frame f = thread->last_frame(); !f.is_first_frame(); f = f.sender(&map), i++) {
+      f.describe(values, i, &map, i == 0);
       if (post_entry >= 0 || Continuation::is_continuation_enterSpecial(f))
         post_entry++;
       if (post_entry >= show_entry_callers)
         break;
     }
@@ -2632,26 +3064,31 @@
   map.set_include_argument_oops(false);
   map.set_skip_missing(true);
   if (callee_complete) {
     frame::update_map_with_saved_link(&map, ContinuationHelper::Frame::callee_link_address(f));
   }
-  const_cast<frame&>(f).describe(values, 0, &map);
+  const_cast<frame&>(f).describe(values, 0, &map, true);
   values.print_on(static_cast<JavaThread*>(nullptr), st);
 }
 #endif
 
 static address thaw_entry   = nullptr;
 static address freeze_entry = nullptr;
+static address freeze_preempt_entry = nullptr;
 
 address Continuation::thaw_entry() {
   return ::thaw_entry;
 }
 
 address Continuation::freeze_entry() {
   return ::freeze_entry;
 }
 
+address Continuation::freeze_preempt_entry() {
+  return ::freeze_preempt_entry;
+}
+
 class ConfigResolve {
 public:
   static void resolve() { resolve_compressed(); }
 
   static void resolve_compressed() {
@@ -2681,10 +3118,11 @@
   template <bool use_compressed, typename BarrierSetT>
   static void resolve() {
     typedef Config<use_compressed ? oop_kind::NARROW : oop_kind::WIDE, BarrierSetT> SelectedConfigT;
 
     freeze_entry = (address)freeze<SelectedConfigT>;
+    freeze_preempt_entry = (address)SelectedConfigT::freeze_preempt;
 
     // If we wanted, we could templatize by kind and have three different thaw entries
     thaw_entry   = (address)thaw<SelectedConfigT>;
   }
 };
diff a/src/hotspot/share/runtime/continuationHelper.hpp b/src/hotspot/share/runtime/continuationHelper.hpp
--- a/src/hotspot/share/runtime/continuationHelper.hpp
+++ b/src/hotspot/share/runtime/continuationHelper.hpp
@@ -56,10 +56,11 @@
 
   // Concrete helpers for describing concrete types of frames
   class InterpretedFrame;
   class NonInterpretedUnknownFrame;
   class CompiledFrame;
+  class NativeFrame;
   class StubFrame;
 };
 
 class ContinuationHelper::Frame : public AllStatic {
 public:
@@ -97,13 +98,11 @@
   static void patch_sender_sp(frame& f, const frame& caller);
 
   static int size(const frame& f);
   static inline int expression_stack_size(const frame &f, InterpreterOopMap* mask);
 
-#ifdef ASSERT
-  static bool is_owning_locks(const frame& f);
-#endif
+  static int monitors_to_fix(JavaThread* thread, const frame& f, ResourceHashtable<oopDesc*, bool> &table, stackChunkOop chunk) NOT_DEBUG_RETURN0;
 
   static bool is_instance(const frame& f);
 
   typedef InterpreterOopMap* ExtraT;
 };
@@ -125,19 +124,25 @@
 
 class ContinuationHelper::CompiledFrame : public ContinuationHelper::NonInterpretedFrame {
 public:
   static bool is_instance(const frame& f);
 
-#ifdef ASSERT
   template <typename RegisterMapT>
-  static bool is_owning_locks(JavaThread* thread, RegisterMapT* map, const frame& f);
-#endif
+  static int monitors_to_fix(JavaThread* thread, RegisterMapT* map, const frame& f, ResourceHashtable<oopDesc*, bool> &table) NOT_DEBUG_RETURN0;
+};
+
+class ContinuationHelper::NativeFrame : public ContinuationHelper::NonInterpretedFrame {
+public:
+  static bool is_instance(const frame& f);
+
+  static int monitors_to_fix(JavaThread* thread, const frame& f, ResourceHashtable<oopDesc*, bool> &table) NOT_DEBUG_RETURN0;
 };
 
 class ContinuationHelper::StubFrame : public ContinuationHelper::NonInterpretedFrame {
 public:
   static const bool stub = true;
 
   static bool is_instance(const frame& f);
+  static int stack_argsize(const frame& f) { return 0; }
 };
 
 #endif // SHARE_VM_RUNTIME_CONTINUATIONHELPER_HPP
diff a/src/hotspot/share/runtime/continuationHelper.inline.hpp b/src/hotspot/share/runtime/continuationHelper.inline.hpp
--- a/src/hotspot/share/runtime/continuationHelper.inline.hpp
+++ b/src/hotspot/share/runtime/continuationHelper.inline.hpp
@@ -30,10 +30,11 @@
 #include "code/scopeDesc.hpp"
 #include "compiler/oopMap.hpp"
 #include "compiler/oopMap.inline.hpp"
 #include "interpreter/oopMapCache.hpp"
 #include "runtime/frame.inline.hpp"
+#include "runtime/objectMonitor.hpp"
 #include "runtime/stackValue.hpp"
 #include "utilities/macros.hpp"
 
 #include CPU_HEADER_INLINE(continuationHelper)
 
@@ -51,11 +52,11 @@
 inline bool ContinuationHelper::NonInterpretedUnknownFrame::is_instance(const frame& f) {
   return !f.is_interpreted_frame();
 }
 
 inline bool ContinuationHelper::Frame::is_stub(CodeBlob* cb) {
-  return cb != nullptr && (cb->is_safepoint_stub() || cb->is_runtime_stub());
+  return cb != nullptr && cb->is_runtime_stub();
 }
 
 inline Method* ContinuationHelper::Frame::frame_method(const frame& f) {
   return f.is_interpreted_frame() ? f.interpreter_frame_method() : f.cb()->as_nmethod()->method();
 }
@@ -106,26 +107,54 @@
   assert(size <= f.interpreter_frame_expression_stack_size(), "size1: %d size2: %d", size, f.interpreter_frame_expression_stack_size());
   return size;
 }
 
 #ifdef ASSERT
-inline bool ContinuationHelper::InterpretedFrame::is_owning_locks(const frame& f) {
-  assert(f.interpreter_frame_monitor_end() <= f.interpreter_frame_monitor_begin(), "must be");
-  if (f.interpreter_frame_monitor_end() == f.interpreter_frame_monitor_begin()) {
-    return false;
+inline int ContinuationHelper::InterpretedFrame::monitors_to_fix(JavaThread* thread, const frame& f, ResourceHashtable<oopDesc*, bool> &table, stackChunkOop chunk) {
+  BasicObjectLock* first_mon = f.interpreter_frame_monitor_begin();
+  BasicObjectLock* last_mon = f.interpreter_frame_monitor_end();
+  assert(last_mon <= first_mon, "must be");
+
+  if (first_mon == last_mon) {
+    // No monitors in this frame
+    return 0;
   }
 
-  for (BasicObjectLock* current = f.previous_monitor_in_interpreter_frame(f.interpreter_frame_monitor_begin());
-        current >= f.interpreter_frame_monitor_end();
-        current = f.previous_monitor_in_interpreter_frame(current)) {
+  int monitor_count = 0;
+  oop monitorenter_oop = thread->is_on_monitorenter() ? thread->current_pending_monitor()->object() : nullptr;
 
-      oop obj = current->obj();
-      if (obj != nullptr) {
-        return true;
+  for (BasicObjectLock* current = f.previous_monitor_in_interpreter_frame(first_mon);
+       current >= last_mon; current = f.previous_monitor_in_interpreter_frame(current)) {
+    oop* obj_adr = current->obj_adr();
+
+    oop obj;
+    if (f.is_heap_frame()) {
+      assert(chunk != nullptr, "null stackChunk");
+      obj = chunk->has_bitmap() && UseCompressedOops ? chunk->load_oop((narrowOop*)obj_adr) : chunk->load_oop(obj_adr);
+    } else {
+      // We have already processed oops when getting this frame.
+      obj = *obj_adr;
+    }
+    assert(obj == nullptr || dbg_is_good_oop(obj), "obj_adr: " PTR_FORMAT " obj: " PTR_FORMAT, p2i(obj_adr), p2i(obj));
+
+    if (obj != nullptr && obj != monitorenter_oop) {
+      markWord mark = obj->mark();
+      if (mark.has_monitor() && !mark.monitor()->is_owner_anonymous()) {
+        // Nothing to do
+        assert(mark.monitor()->is_owner(thread), "invariant");
+        continue;
+      }
+      assert(!f.is_heap_frame() || LockingMode == LM_LIGHTWEIGHT,
+             "monitors found on heap frame that need to be fixed should only be those saved in the LockStack");
+      bool created;
+      table.put_if_absent(obj, true, &created);
+      if (created) {
+        monitor_count++;
       }
+    }
   }
-  return false;
+  return monitor_count;
 }
 #endif
 
 inline intptr_t* ContinuationHelper::InterpretedFrame::frame_top(const frame& f) { // inclusive; this will be copied with the frame
   return f.unextended_sp();
@@ -154,46 +183,100 @@
 
 inline bool ContinuationHelper::CompiledFrame::is_instance(const frame& f) {
   return f.is_compiled_frame();
 }
 
+inline bool ContinuationHelper::NativeFrame::is_instance(const frame& f) {
+  return f.is_native_frame();
+}
+
 #ifdef ASSERT
 template<typename RegisterMapT>
-bool ContinuationHelper::CompiledFrame::is_owning_locks(JavaThread* thread, RegisterMapT* map, const frame& f) {
+int ContinuationHelper::CompiledFrame::monitors_to_fix(JavaThread* thread, RegisterMapT* map, const frame& f, ResourceHashtable<oopDesc*, bool> &table) {
   assert(!f.is_interpreted_frame(), "");
   assert(CompiledFrame::is_instance(f), "");
 
   nmethod* nm = f.cb()->as_nmethod();
-  assert(!nm->is_native_method(), ""); // See compiledVFrame::compiledVFrame(...) in vframe_hp.cpp
+  assert(!nm->is_nmethod() || !nm->as_nmethod()->is_native_method(), ""); // See compiledVFrame::compiledVFrame(...) in vframe_hp.cpp
 
   if (!nm->has_monitors()) {
-    return false;
+    // No monitors in this frame
+    return 0;
   }
 
-  frame::update_map_with_saved_link(map, Frame::callee_link_address(f)); // the monitor object could be stored in the link register
-  ResourceMark rm;
+  int monitor_count = 0;
+  oop monitorenter_oop = thread->is_on_monitorenter() ? thread->current_pending_monitor()->object() : nullptr;
+
   for (ScopeDesc* scope = nm->scope_desc_at(f.pc()); scope != nullptr; scope = scope->sender()) {
     GrowableArray<MonitorValue*>* mons = scope->monitors();
     if (mons == nullptr || mons->is_empty()) {
       continue;
     }
 
     for (int index = (mons->length()-1); index >= 0; index--) { // see compiledVFrame::monitors()
       MonitorValue* mon = mons->at(index);
       if (mon->eliminated()) {
-        continue; // we ignore scalar-replaced monitors
+        continue; // we ignore eliminated monitors
       }
+
       ScopeValue* ov = mon->owner();
       StackValue* owner_sv = StackValue::create_stack_value(&f, map, ov); // it is an oop
       oop owner = owner_sv->get_obj()();
-      if (owner != nullptr) {
-        //assert(nm->has_monitors(), "");
-        return true;
+      if (owner != nullptr && owner != monitorenter_oop) {
+        markWord mark = owner->mark();
+        if (mark.has_monitor() && !mark.monitor()->is_owner_anonymous()) {
+          // Nothing to do
+          assert(mark.monitor()->is_owner(thread), "invariant");
+          continue;
+        }
+        assert(!f.is_heap_frame() || LockingMode == LM_LIGHTWEIGHT,
+             "monitors found on heap frame that need to be fixed should only be those saved in the LockStack");
+        bool created;
+        table.put_if_absent(owner, true, &created);
+        if (created) {
+          monitor_count++;
+        }
       }
     }
   }
-  return false;
+  return monitor_count;
+}
+
+inline int ContinuationHelper::NativeFrame::monitors_to_fix(JavaThread* thread, const frame& f, ResourceHashtable<oopDesc*, bool> &table) {
+  assert(NativeFrame::is_instance(f), "");
+
+  Method* method = f.cb()->as_nmethod()->method();
+  if (!method->is_synchronized()) {
+    return 0;
+  }
+
+  oop synced_obj = f.get_native_receiver();
+  oop monitorenter_oop = thread->is_on_monitorenter() ? thread->current_pending_monitor()->object() : nullptr;
+
+  bool is_first_frame = f.sp() == thread->last_Java_sp();
+  if (!is_first_frame) {
+    assert(ObjectSynchronizer::current_thread_holds_lock(thread, Handle(thread, synced_obj)), "must be owner");
+    assert(monitorenter_oop == nullptr || monitorenter_oop != synced_obj, "owner already, should not be contended");
+
+    markWord mark = synced_obj->mark();
+    if (mark.has_monitor() && !mark.monitor()->is_owner_anonymous()) {
+      // Nothing to do
+      assert(mark.monitor()->is_owner(thread), "invariant");
+      return 0;
+    }
+    assert(!f.is_heap_frame(), "native frame on the heap???");
+    bool created;
+    table.put_if_absent(synced_obj, true, &created);
+    if (created) {
+      return 1;
+    }
+  } else {
+    assert(thread->is_on_monitorenter() && monitorenter_oop != nullptr && monitorenter_oop == synced_obj,
+           "should be freeze case due to preempt on monitorenter contention");
+    assert(!ObjectSynchronizer::current_thread_holds_lock(thread, Handle(thread, synced_obj)), "should not be owner");
+  }
+  return 0;
 }
 #endif
 
 inline bool ContinuationHelper::StubFrame::is_instance(const frame& f) {
   return !f.is_interpreted_frame() && is_stub(f.cb());
diff a/src/hotspot/share/runtime/continuationJavaClasses.cpp b/src/hotspot/share/runtime/continuationJavaClasses.cpp
--- a/src/hotspot/share/runtime/continuationJavaClasses.cpp
+++ b/src/hotspot/share/runtime/continuationJavaClasses.cpp
@@ -85,10 +85,12 @@
 int jdk_internal_vm_StackChunk::_sp_offset;
 int jdk_internal_vm_StackChunk::_pc_offset;
 int jdk_internal_vm_StackChunk::_argsize_offset;
 int jdk_internal_vm_StackChunk::_flags_offset;
 int jdk_internal_vm_StackChunk::_maxThawingSize_offset;
+int jdk_internal_vm_StackChunk::_lockStackSize_offset;
+int jdk_internal_vm_StackChunk::_objectMonitor_offset;
 int jdk_internal_vm_StackChunk::_cont_offset;
 
 #define STACKCHUNK_FIELDS_DO(macro) \
   macro(_parent_offset,  k, vmSymbols::parent_name(),  stackchunk_signature, false); \
   macro(_size_offset,    k, vmSymbols::size_name(),    int_signature,        false); \
diff a/src/hotspot/share/runtime/continuationJavaClasses.hpp b/src/hotspot/share/runtime/continuationJavaClasses.hpp
--- a/src/hotspot/share/runtime/continuationJavaClasses.hpp
+++ b/src/hotspot/share/runtime/continuationJavaClasses.hpp
@@ -69,14 +69,16 @@
   static inline void set_preempted(oop continuation, bool value);
 };
 
 // Interface to jdk.internal.vm.StackChunk objects
 #define STACKCHUNK_INJECTED_FIELDS(macro)                                          \
-  macro(jdk_internal_vm_StackChunk, cont,           continuation_signature, false) \
-  macro(jdk_internal_vm_StackChunk, flags,          byte_signature,         false) \
-  macro(jdk_internal_vm_StackChunk, pc,             intptr_signature,       false) \
-  macro(jdk_internal_vm_StackChunk, maxThawingSize, int_signature,          false) \
+  macro(jdk_internal_vm_StackChunk, cont,            continuation_signature, false) \
+  macro(jdk_internal_vm_StackChunk, flags,           byte_signature,         false) \
+  macro(jdk_internal_vm_StackChunk, pc,              intptr_signature,       false) \
+  macro(jdk_internal_vm_StackChunk, maxThawingSize,  int_signature,          false) \
+  macro(jdk_internal_vm_StackChunk, lockStackSize,   byte_signature,         false) \
+  macro(jdk_internal_vm_StackChunk, objectMonitor,   intptr_signature,       false) \
 
 class jdk_internal_vm_StackChunk: AllStatic {
   friend class JavaClasses;
  private:
   static int _parent_offset;
@@ -84,10 +86,12 @@
   static int _sp_offset;
   static int _pc_offset;
   static int _argsize_offset;
   static int _flags_offset;
   static int _maxThawingSize_offset;
+  static int _lockStackSize_offset;
+  static int _objectMonitor_offset;
   static int _cont_offset;
 
 
   static void compute_offsets();
  public:
@@ -121,10 +125,16 @@
   static inline bool try_set_flags(oop chunk, uint8_t expected_value, uint8_t new_value);
 
   static inline int maxThawingSize(oop chunk);
   static inline void set_maxThawingSize(oop chunk, int value);
 
+  static inline uint8_t lockStackSize(oop chunk);
+  static inline void set_lockStackSize(oop chunk, uint8_t value);
+
+  static inline address objectMonitor(oop chunk);
+  static inline void set_objectMonitor(oop chunk, address mon);
+
   // cont oop's processing is essential for the chunk's GC protocol
   static inline oop cont(oop chunk);
   template<typename P>
   static inline oop cont_raw(oop chunk);
   static inline void set_cont(oop chunk, oop value);
diff a/src/hotspot/share/runtime/continuationJavaClasses.inline.hpp b/src/hotspot/share/runtime/continuationJavaClasses.inline.hpp
--- a/src/hotspot/share/runtime/continuationJavaClasses.inline.hpp
+++ b/src/hotspot/share/runtime/continuationJavaClasses.inline.hpp
@@ -176,6 +176,22 @@
   log_develop_trace(continuations)("%s max_size: %d -> %d", value >= old ? "add" : "sub", old, value);
 #endif
   chunk->int_field_put(_maxThawingSize_offset, value);
 }
 
+inline uint8_t jdk_internal_vm_StackChunk::lockStackSize(oop chunk) {
+  return Atomic::load(chunk->field_addr<uint8_t>(_lockStackSize_offset));
+}
+
+inline void jdk_internal_vm_StackChunk::set_lockStackSize(oop chunk, uint8_t value) {
+  Atomic::store(chunk->field_addr<uint8_t>(_lockStackSize_offset), value);
+}
+
+inline address jdk_internal_vm_StackChunk::objectMonitor(oop chunk) {
+  return chunk->address_field(_objectMonitor_offset);
+}
+
+inline void jdk_internal_vm_StackChunk::set_objectMonitor(oop chunk, address value) {
+  chunk->address_field_put(_objectMonitor_offset, value);
+}
+
 #endif // SHARE_RUNTIME_CONTINUATIONJAVACLASSES_INLINE_HPP
diff a/src/hotspot/share/runtime/continuationWrapper.inline.hpp b/src/hotspot/share/runtime/continuationWrapper.inline.hpp
--- a/src/hotspot/share/runtime/continuationWrapper.inline.hpp
+++ b/src/hotspot/share/runtime/continuationWrapper.inline.hpp
@@ -113,10 +113,11 @@
   oop continuation()                 { return _continuation; }
   stackChunkOop tail() const         { return _tail; }
   void set_tail(stackChunkOop chunk) { _tail = chunk; }
 
   inline bool is_preempted();
+  inline void set_preempted(bool value);
   inline void read();
   inline void write();
 
   NOT_PRODUCT(intptr_t hash();)
 
@@ -161,10 +162,14 @@
 
 inline bool ContinuationWrapper::is_preempted() {
   return jdk_internal_vm_Continuation::is_preempted(_continuation);
 }
 
+inline void ContinuationWrapper::set_preempted(bool value) {
+  jdk_internal_vm_Continuation::set_preempted(_continuation, value);
+}
+
 inline void ContinuationWrapper::read() {
   _tail  = jdk_internal_vm_Continuation::tail(_continuation);
 }
 
 inline void ContinuationWrapper::write() {
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
@@ -1626,10 +1626,11 @@
 
 // relock objects for which synchronization was eliminated
 bool Deoptimization::relock_objects(JavaThread* thread, GrowableArray<MonitorInfo*>* monitors,
                                     JavaThread* deoptee_thread, frame& fr, int exec_mode, bool realloc_failures) {
   bool relocked_objects = false;
+  LOOM_MONITOR_SUPPORT_ONLY(int compensate_extra_increment = 0;)
   for (int i = 0; i < monitors->length(); i++) {
     MonitorInfo* mon_info = monitors->at(i);
     if (mon_info->eliminated()) {
       assert(!mon_info->owner_is_scalar_replaced() || realloc_failures, "reallocation was missed");
       relocked_objects = true;
@@ -1641,10 +1642,11 @@
             // With exec_mode == Unpack_none obj may be thread local and locked in
             // a callee frame. 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*) nullptr));
             obj->set_mark(dmw);
+            LOOM_MONITOR_SUPPORT_ONLY(compensate_extra_increment++;)
           }
           if (mark.has_monitor()) {
             // defer relocking if the deoptee thread is currently waiting for obj
             ObjectMonitor* waiting_monitor = deoptee_thread->current_waiting_monitor();
             if (waiting_monitor != nullptr && waiting_monitor->object() == obj()) {
@@ -1660,19 +1662,20 @@
           // 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");
+          assert(mon->is_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");
         }
       }
     }
   }
+  LOOM_MONITOR_SUPPORT_ONLY(deoptee_thread->dec_held_monitor_count(compensate_extra_increment);)
   return relocked_objects;
 }
 #endif // COMPILER2_OR_JVMCI
 
 vframeArray* Deoptimization::create_vframeArray(JavaThread* thread, frame fr, RegisterMap *reg_map, GrowableArray<compiledVFrame*>* chunk, bool realloc_failures) {
diff a/src/hotspot/share/runtime/frame.cpp b/src/hotspot/share/runtime/frame.cpp
--- a/src/hotspot/share/runtime/frame.cpp
+++ b/src/hotspot/share/runtime/frame.cpp
@@ -575,14 +575,25 @@
   // locks for synchronization
   for (BasicObjectLock* current = interpreter_frame_monitor_end();
        current < interpreter_frame_monitor_begin();
        current = next_monitor_in_interpreter_frame(current)) {
     st->print(" - obj    [%s", current->obj() == nullptr ? "null" : "");
-    if (current->obj() != nullptr) current->obj()->print_value_on(st);
+    oop obj = current->obj();
+    if (obj != nullptr) {
+      if (!is_heap_frame()) {
+        obj->print_value_on(st);
+      } else {
+        // Might be an invalid oop. We don't have the
+        // stackChunk to correct it so just print address.
+        st->print(INTPTR_FORMAT, p2i(obj));
+      }
+    }
     st->print_cr("]");
     st->print(" - lock   [");
-    current->lock()->print_on(st, current->obj());
+    if (!is_heap_frame()) {
+      current->lock()->print_on(st, obj);
+    }
     st->print_cr("]");
   }
   // monitor
   st->print_cr(" - monitor[" INTPTR_FORMAT "]", p2i(interpreter_frame_monitor_begin()));
   // bcp
@@ -1082,20 +1093,20 @@
   assert(Universe::heap()->is_in_or_null(r), "bad receiver: " INTPTR_FORMAT " (" INTX_FORMAT ")", p2i(r), p2i(r));
   return r;
 }
 
 
-BasicLock* frame::get_native_monitor() {
+BasicLock* frame::get_native_monitor() const {
   nmethod* nm = (nmethod*)_cb;
   assert(_cb != nullptr && _cb->is_nmethod() && nm->method()->is_native(),
          "Should not call this unless it's a native nmethod");
   int byte_offset = in_bytes(nm->native_basic_lock_sp_offset());
   assert(byte_offset >= 0, "should not see invalid offset");
   return (BasicLock*) &sp()[byte_offset / wordSize];
 }
 
-oop frame::get_native_receiver() {
+oop frame::get_native_receiver() const {
   nmethod* nm = (nmethod*)_cb;
   assert(_cb != nullptr && _cb->is_nmethod() && nm->method()->is_native(),
          "Should not call this unless it's a native nmethod");
   int byte_offset = in_bytes(nm->native_receiver_sp_offset());
   assert(byte_offset >= 0, "should not see invalid offset");
@@ -1271,11 +1282,11 @@
     _base->push(base_loc);
     _derived->push(derived_loc);
   }
 
   bool is_good(oop* p) {
-    return *p == nullptr || (dbg_is_safe(*p, -1) && dbg_is_safe((*p)->klass(), -1) && oopDesc::is_oop_or_null(*p));
+    return *p == nullptr || (dbg_is_safe(*p, -1) && oopDesc::is_oop_or_null(*p));
   }
   void describe(FrameValues& values, int frame_no) {
     for (int i = 0; i < _oops->length(); i++) {
       oop* p = _oops->at(i);
       values.describe(frame_no, (intptr_t*)p, err_msg("oop%s for #%d", is_good(p) ? "" : " (BAD)", frame_no));
@@ -1324,13 +1335,18 @@
   }
 };
 
 // callers need a ResourceMark because of name_and_sig_as_C_string() usage,
 // RA allocated string is returned to the caller
-void frame::describe(FrameValues& values, int frame_no, const RegisterMap* reg_map) {
+void frame::describe(FrameValues& values, int frame_no, const RegisterMap* reg_map, bool top) {
   // boundaries: sp and the 'real' frame pointer
   values.describe(-1, sp(), err_msg("sp for #%d", frame_no), 0);
+  if (top) {
+    values.describe(-1, sp() - 1, err_msg("sp[-1] for #%d", frame_no), 0);
+    values.describe(-1, sp() - 2, err_msg("sp[-2] for #%d", frame_no), 0);
+  }
+
   intptr_t* frame_pointer = real_fp(); // Note: may differ from fp()
 
   // print frame info at the highest boundary
   intptr_t* info_address = MAX2(sp(), frame_pointer);
 
@@ -1398,11 +1414,11 @@
       oopsFn.describe(values, frame_no);
     }
   } else if (is_entry_frame()) {
     // For now just label the frame
     values.describe(-1, info_address, err_msg("#%d entry frame", frame_no), 2);
-  } else if (cb()->is_nmethod()) {
+  } else if (cb() && cb()->is_nmethod()) {
     // For now just label the frame
     nmethod* nm = cb()->as_nmethod();
     values.describe(-1, info_address,
                     FormatBuffer<1024>("#%d nmethod " INTPTR_FORMAT " for method J %s%s", frame_no,
                                        p2i(nm),
diff a/src/hotspot/share/runtime/frame.hpp b/src/hotspot/share/runtime/frame.hpp
--- a/src/hotspot/share/runtime/frame.hpp
+++ b/src/hotspot/share/runtime/frame.hpp
@@ -335,12 +335,12 @@
   oop retrieve_receiver(RegisterMap *reg_map);
 
   // Return the monitor owner and BasicLock for compiled synchronized
   // native methods. Used by JVMTI's GetLocalInstance method
   // (via VM_GetReceiver) to retrieve the receiver from a native wrapper frame.
-  BasicLock* get_native_monitor();
-  oop        get_native_receiver();
+  BasicLock* get_native_monitor() const;
+  oop        get_native_receiver() const;
 
   // Find receiver for an invoke when arguments are just pushed on stack (i.e., callee stack-frame is
   // not setup)
   oop interpreter_callee_receiver(Symbol* signature);
 
@@ -422,10 +422,12 @@
 
  public:
   oop saved_oop_result(RegisterMap* map) const;
   void set_saved_oop_result(RegisterMap* map, oop obj);
 
+  static JavaThread** saved_thread_address(const frame& f);
+
   // For debugging
  private:
   const char* print_name() const;
 
   void describe_pd(FrameValues& values, int frame_no);
@@ -437,11 +439,11 @@
   void interpreter_frame_print_on(outputStream* st) const;
   void print_on_error(outputStream* st, char* buf, int buflen, bool verbose = false) const;
   static void print_C_frame(outputStream* st, char* buf, int buflen, address pc);
 
   // Add annotated descriptions of memory locations belonging to this frame to values
-  void describe(FrameValues& values, int frame_no, const RegisterMap* reg_map=nullptr);
+  void describe(FrameValues& values, int frame_no, const RegisterMap* reg_map=nullptr, bool top = false);
 
   // Conversion from a VMReg to physical stack location
   template <typename RegisterMapT>
   address oopmapreg_to_location(VMReg reg, const RegisterMapT* reg_map) const;
   template <typename RegisterMapT>
diff a/src/hotspot/share/runtime/frame.inline.hpp b/src/hotspot/share/runtime/frame.inline.hpp
--- a/src/hotspot/share/runtime/frame.inline.hpp
+++ b/src/hotspot/share/runtime/frame.inline.hpp
@@ -25,10 +25,11 @@
 #ifndef SHARE_RUNTIME_FRAME_INLINE_HPP
 #define SHARE_RUNTIME_FRAME_INLINE_HPP
 
 #include "runtime/frame.hpp"
 
+#include "c1/c1_Runtime1.hpp"
 #include "code/codeBlob.inline.hpp"
 #include "code/nmethod.inline.hpp"
 #include "interpreter/interpreter.hpp"
 #include "oops/stackChunkOop.inline.hpp"
 #include "oops/method.hpp"
@@ -79,10 +80,37 @@
     return nm->get_original_pc(this);
   }
   return nullptr;
 }
 
+#ifdef ASSERT
+static address get_register_address_in_stub(const frame& stub_fr, VMReg reg) {
+  RegisterMap map(nullptr,
+                  RegisterMap::UpdateMap::include,
+                  RegisterMap::ProcessFrames::skip,
+                  RegisterMap::WalkContinuation::skip);
+  stub_fr.oop_map()->update_register_map(&stub_fr, &map);
+  return map.location(reg, stub_fr.sp());
+}
+#endif
+
+inline JavaThread** frame::saved_thread_address(const frame& f) {
+  CodeBlob* cb = f.cb();
+  assert(cb != nullptr && cb->is_runtime_stub(), "invalid frame");
+
+  JavaThread** thread_addr;
+  if (cb == Runtime1::blob_for(Runtime1::monitorenter_id) ||
+      cb == Runtime1::blob_for(Runtime1::monitorenter_nofpu_id)) {
+    thread_addr = (JavaThread**)(f.sp() + Runtime1::runtime_blob_current_thread_offset(f));
+  } else {
+    // c2 only saves rbp in the stub frame so nothing to do.
+    thread_addr = nullptr;
+  }
+  assert(get_register_address_in_stub(f, SharedRuntime::thread_register()) == (address)thread_addr, "wrong thread address");
+  return thread_addr;
+}
+
 template <typename RegisterMapT>
 inline address frame::oopmapreg_to_location(VMReg reg, const RegisterMapT* reg_map) const {
   if (reg->is_reg()) {
     // If it is passed in a register, it got spilled in the stub frame.
     return reg_map->location(reg, sp());
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
@@ -1918,11 +1918,11 @@
           "Deopt stack after thaw")                                         \
                                                                             \
   develop(bool, LoomVerifyAfterThaw, false,                                 \
           "Verify stack after thaw")                                        \
                                                                             \
-  develop(bool, VerifyContinuations, false,                                 \
+  develop(bool, VerifyContinuations, true,                                  \
           "Verify continuation consistency")                                \
                                                                             \
   develop(bool, UseContinuationFastPath, true,                              \
           "Use fast-path frame walking in continuations")                   \
                                                                             \
diff a/src/hotspot/share/runtime/handshake.cpp b/src/hotspot/share/runtime/handshake.cpp
--- a/src/hotspot/share/runtime/handshake.cpp
+++ b/src/hotspot/share/runtime/handshake.cpp
@@ -435,11 +435,11 @@
 
   target->handshake_state()->add_operation(op);
 }
 
 // Filters
-static bool non_self_executable_filter(HandshakeOperation* op) {
+static bool handshaker_filter(HandshakeOperation* op) {
   return !op->is_async();
 }
 static bool no_async_exception_filter(HandshakeOperation* op) {
   return !op->is_async_exception();
 }
@@ -476,11 +476,11 @@
   _queue.push(op);
   SafepointMechanism::arm_local_poll_release(_handshakee);
 }
 
 bool HandshakeState::operation_pending(HandshakeOperation* op) {
-  MutexLocker ml(&_lock, Mutex::_no_safepoint_check_flag);
+  ConditionalMutexLocker ml(&_lock, !_lock.owned_by_self(), Mutex::_no_safepoint_check_flag);
   MatchOp mo(op);
   return _queue.contains(mo);
 }
 
 HandshakeOperation* HandshakeState::get_op_for_self(bool allow_suspend, bool check_async_exception) {
@@ -530,28 +530,28 @@
     remove_op(op);
     delete op;
   }
 }
 
-bool HandshakeState::have_non_self_executable_operation() {
+bool HandshakeState::has_handshaker_operation() {
   assert(_handshakee != Thread::current(), "Must not be called by self");
   assert(_lock.owned_by_self(), "Lock must be held");
-  return _queue.contains(non_self_executable_filter);
+  return _queue.contains(handshaker_filter);
 }
 
-HandshakeOperation* HandshakeState::get_op() {
+HandshakeOperation* HandshakeState::get_op_for_handshaker() {
   assert(_handshakee != Thread::current(), "Must not be called by self");
   assert(_lock.owned_by_self(), "Lock must be held");
-  return _queue.peek(non_self_executable_filter);
-};
+  return _queue.peek(handshaker_filter);
+}
 
 void HandshakeState::remove_op(HandshakeOperation* op) {
   assert(_lock.owned_by_self(), "Lock must be held");
   MatchOp mo(op);
   HandshakeOperation* ret = _queue.pop(mo);
   assert(ret == op, "Popped op must match requested op");
-};
+}
 
 bool HandshakeState::process_by_self(bool allow_suspend, bool check_async_exception) {
   assert(Thread::current() == _handshakee, "should call from _handshakee");
   assert(!_handshakee->is_terminated(), "should not be a terminated thread");
 
@@ -623,11 +623,11 @@
   }
   // Operations are added lock free and then the poll is armed.
   // If all handshake operations for the handshakee are finished and someone
   // just adds an operation we may see it here. But if the handshakee is not
   // armed yet it is not safe to proceed.
-  if (have_non_self_executable_operation()) {
+  if (has_handshaker_operation()) {
     OrderAccess::loadload(); // Matches the implicit storestore in add_operation()
     if (SafepointMechanism::local_poll_armed(_handshakee)) {
       return true;
     }
   }
@@ -659,11 +659,11 @@
     return HandshakeState::_not_safe;
   }
 
   Thread* current_thread = Thread::current();
 
-  HandshakeOperation* op = get_op();
+  HandshakeOperation* op = get_op_for_handshaker();
 
   assert(op != nullptr, "Must have an op");
   assert(SafepointMechanism::local_poll_armed(_handshakee), "Must be");
   assert(op->_target == nullptr || _handshakee == op->_target, "Wrong thread");
 
diff a/src/hotspot/share/runtime/handshake.hpp b/src/hotspot/share/runtime/handshake.hpp
--- a/src/hotspot/share/runtime/handshake.hpp
+++ b/src/hotspot/share/runtime/handshake.hpp
@@ -105,13 +105,13 @@
 
   bool claim_handshake();
   bool possibly_can_process_handshake();
   bool can_process_handshake();
 
-  bool have_non_self_executable_operation();
+  bool has_handshaker_operation();
   HandshakeOperation* get_op_for_self(bool allow_suspend, bool check_async_exception);
-  HandshakeOperation* get_op();
+  HandshakeOperation* get_op_for_handshaker();
   void remove_op(HandshakeOperation* op);
 
   void set_active_handshaker(Thread* thread) { Atomic::store(&_active_handshaker, thread); }
 
   class MatchOp {
diff a/src/hotspot/share/runtime/javaThread.cpp b/src/hotspot/share/runtime/javaThread.cpp
--- a/src/hotspot/share/runtime/javaThread.cpp
+++ b/src/hotspot/share/runtime/javaThread.cpp
@@ -81,10 +81,11 @@
 #include "runtime/serviceThread.hpp"
 #include "runtime/stackFrameStream.inline.hpp"
 #include "runtime/stackWatermarkSet.hpp"
 #include "runtime/synchronizer.hpp"
 #include "runtime/threadCritical.hpp"
+#include "runtime/threadIdentifier.hpp"
 #include "runtime/threadSMR.inline.hpp"
 #include "runtime/threadStatisticalInfo.hpp"
 #include "runtime/threadWXSetters.inline.hpp"
 #include "runtime/timer.hpp"
 #include "runtime/timerTrace.hpp"
@@ -233,10 +234,12 @@
   // We are called from jni_AttachCurrentThread/jni_AttachCurrentThreadAsDaemon.
   // We cannot use JavaCalls::construct_new_instance because the java.lang.Thread
   // constructor calls Thread.current(), which must be set here.
   java_lang_Thread::set_thread(thread_oop(), this);
   set_threadOopHandles(thread_oop());
+  // Set the lock_id to the next thread_id temporarily while initialization runs.
+  set_lock_id(ThreadIdentifier::next());
 
   JavaValue result(T_VOID);
   if (thread_name != nullptr) {
     Handle name = java_lang_String::create_from_str(thread_name, CHECK);
     // Thread gets assigned specified name and null target
@@ -258,10 +261,13 @@
                             vmSymbols::threadgroup_runnable_void_signature(),
                             thread_group,
                             Handle(),
                             CHECK);
   }
+  // Update the lock_id with the tid value.
+  set_lock_id(java_lang_Thread::thread_id(thread_oop()));
+
   os::set_priority(this, NormPriority);
 
   if (daemon) {
     java_lang_Thread::set_daemon(thread_oop());
   }
@@ -427,10 +433,12 @@
   _current_pending_monitor(nullptr),
   _current_pending_monitor_is_from_java(true),
   _current_waiting_monitor(nullptr),
   _active_handles(nullptr),
   _free_handle_block(nullptr),
+  _lock_id(0),
+  _on_monitorenter(false),
 
   _monitor_chunks(nullptr),
 
   _suspend_flags(0),
 
@@ -486,10 +494,14 @@
   _cont_entry(nullptr),
   _cont_fastpath(0),
   _cont_fastpath_thread_state(1),
   _held_monitor_count(0),
   _jni_monitor_count(0),
+  _preempting(false),
+  _preemption_cancelled(false),
+  _jvmti_unmount_event_pending(false),
+  DEBUG_ONLY(_obj_locker_count(0) COMMA)
 
   _handshake(this),
 
   _popframe_preserved_args(nullptr),
   _popframe_preserved_args_size(0),
@@ -931,10 +943,11 @@
     // We would like a fatal here, but due to we never checked this before there
     // is a lot of tests which breaks, even with an error log.
     log_debug(jni)("JavaThread %s (tid: " UINTX_FORMAT ") with Objects still locked by JNI MonitorEnter.",
                    exit_type == JavaThread::normal_exit ? "exiting" : "detaching", os::current_thread_id());
   }
+  assert(obj_locker_count() == 0, "expected 0 but found: " INTX_FORMAT, obj_locker_count());
 
   // These things needs to be done while we are still a Java Thread. Make sure that thread
   // is in a consistent state, in case GC happens
   JFR_ONLY(Jfr::on_thread_exit(this);)
 
@@ -1182,10 +1195,11 @@
   Handshake::execute(&iaeh, target);
 }
 
 #if INCLUDE_JVMTI
 void JavaThread::set_is_in_VTMS_transition(bool val) {
+  assert(is_in_VTMS_transition() != val, "already %s transition", val ? "inside" : "outside");
   _is_in_VTMS_transition = val;
 }
 
 #ifdef ASSERT
 void JavaThread::set_is_VTMS_transition_disabler(bool val) {
@@ -1738,10 +1752,11 @@
   Handle thread_oop(Thread::current(),
                     JNIHandles::resolve_non_null(jni_thread));
   assert(InstanceKlass::cast(thread_oop->klass())->is_linked(),
          "must be initialized");
   set_threadOopHandles(thread_oop());
+  set_lock_id(java_lang_Thread::thread_id(thread_oop()));
 
   if (prio == NoPriority) {
     prio = java_lang_Thread::priority(thread_oop());
     assert(prio != NoPriority, "A valid priority should be present");
   }
@@ -2030,14 +2045,14 @@
 // Slow-path decrement of the held monitor counts. JNI unlocking is always
 // this slow-path.
 void JavaThread::dec_held_monitor_count(intx i, bool jni) {
 #ifdef SUPPORT_MONITOR_COUNT
   _held_monitor_count -= i;
-  assert(_held_monitor_count >= 0, "Must always be greater than 0: " INTX_FORMAT, _held_monitor_count);
+  assert(_held_monitor_count >= 0, "Must always be non-negative: " INTX_FORMAT, _held_monitor_count);
   if (jni) {
     _jni_monitor_count -= i;
-    assert(_jni_monitor_count >= 0, "Must always be greater than 0: " INTX_FORMAT, _jni_monitor_count);
+    assert(_jni_monitor_count >= 0, "Must always be non-negative: " INTX_FORMAT, _jni_monitor_count);
   }
   // When a thread is detaching with still owned JNI monitors, the logic that releases
   // the monitors doesn't know to set the "jni" flag and so the counts can get out of sync.
   // So we skip this assert if the thread is exiting. Once all monitors are unlocked the
   // JNI count is directly set to zero.
@@ -2223,10 +2238,11 @@
 
   java_lang_Thread::set_daemon(thread_oop());
 
   // Now bind the thread_oop to the target JavaThread.
   target->set_threadOopHandles(thread_oop());
+  target->set_lock_id(java_lang_Thread::thread_id(thread_oop()));
 
   Threads::add(target); // target is now visible for safepoint/handshake
   // Publish the JavaThread* in java.lang.Thread after the JavaThread* is
   // on a ThreadsList. We don't want to wait for the release when the
   // Theads_lock is dropped when the 'mu' destructor is run since the
diff a/src/hotspot/share/runtime/javaThread.hpp b/src/hotspot/share/runtime/javaThread.hpp
--- a/src/hotspot/share/runtime/javaThread.hpp
+++ b/src/hotspot/share/runtime/javaThread.hpp
@@ -39,10 +39,11 @@
 #include "runtime/safepointMechanism.hpp"
 #include "runtime/stackWatermarkSet.hpp"
 #include "runtime/stackOverflow.hpp"
 #include "runtime/thread.hpp"
 #include "runtime/threadHeapSampler.hpp"
+#include "runtime/threadIdentifier.hpp"
 #include "runtime/threadStatisticalInfo.hpp"
 #include "utilities/exceptions.hpp"
 #include "utilities/globalDefinitions.hpp"
 #include "utilities/macros.hpp"
 #if INCLUDE_JFR
@@ -155,11 +156,27 @@
   JNIHandleBlock* _active_handles;
 
   // One-element thread local free list
   JNIHandleBlock* _free_handle_block;
 
+  // ID used as owner for inflated monitors. Same as the tid field of the current
+  // _vthread object, except during creation of the primordial and JNI attached
+  // thread cases where this field can have a temporal value.
+  int64_t _lock_id;
+
  public:
+  bool _on_monitorenter;
+
+  bool is_on_monitorenter() { return _on_monitorenter; }
+  void set_on_monitorenter(bool val) { _on_monitorenter = val; }
+
+  void set_lock_id(int64_t tid) {
+    assert(tid >= ThreadIdentifier::initial() && tid < ThreadIdentifier::current(), "invalid tid");
+    _lock_id = tid;
+  }
+  int64_t lock_id() const { return _lock_id; }
+
   // For tracking the heavyweight monitor the thread is pending on.
   ObjectMonitor* current_pending_monitor() {
     // Use Atomic::load() to prevent data race between concurrent modification and
     // concurrent readers, e.g. ThreadService::get_current_contended_monitor().
     // Especially, reloading pointer from thread after null check must be prevented.
@@ -465,10 +482,30 @@
   int _cont_fastpath_thread_state; // whether global thread state allows continuation fastpath (JVMTI)
 
   // It's signed for error detection.
   intx _held_monitor_count;  // used by continuations for fast lock detection
   intx _jni_monitor_count;
+  bool _preempting;
+  bool _preemption_cancelled;
+  bool _jvmti_unmount_event_pending;
+  address _preempt_alternate_return; // used when preempting a thread
+  address _preempt_alternate_return_sp;
+
+#ifdef ASSERT
+  intx _obj_locker_count;
+
+ public:
+  intx obj_locker_count() { return _obj_locker_count; }
+  void inc_obj_locker_count() {
+    assert(_obj_locker_count >= 0, "Must always be greater than 0: " INTX_FORMAT, _obj_locker_count);
+    _obj_locker_count++;
+  }
+  void dec_obj_locker_count() {
+    _obj_locker_count--;
+    assert(_obj_locker_count >= 0, "Must always be greater than 0: " INTX_FORMAT, _obj_locker_count);
+  }
+#endif // ASSERT
 
 private:
 
   friend class VMThread;
   friend class ThreadWaitTransition;
@@ -609,18 +646,29 @@
   bool cont_fastpath() const                   { return _cont_fastpath == nullptr && _cont_fastpath_thread_state != 0; }
   bool cont_fastpath_thread_state() const      { return _cont_fastpath_thread_state != 0; }
 
   void inc_held_monitor_count(intx i = 1, bool jni = false);
   void dec_held_monitor_count(intx i = 1, bool jni = false);
-
   intx held_monitor_count() { return _held_monitor_count; }
+
   intx jni_monitor_count()  { return _jni_monitor_count;  }
-  void clear_jni_monitor_count() { _jni_monitor_count = 0;   }
+  void clear_jni_monitor_count() { _jni_monitor_count = 0; }
 
   inline bool is_vthread_mounted() const;
   inline const ContinuationEntry* vthread_continuation() const;
 
+  bool preempting()           { return _preempting; }
+  void set_preempting(bool b) { _preempting = b; }
+
+  bool preemption_cancelled() { return _preemption_cancelled; }
+  void set_preemption_cancelled(bool val) { _preemption_cancelled = val; }
+
+  bool jvmti_unmount_event_pending() { return _jvmti_unmount_event_pending; }
+  void set_jvmti_unmount_event_pending(bool val) { _jvmti_unmount_event_pending = val; }
+
+  void set_preempt_alternate_return(address val) { _preempt_alternate_return = val; }
+  void set_preempt_alternate_return_sp(address val) { _preempt_alternate_return_sp = val; }
  private:
   DEBUG_ONLY(void verify_frame_info();)
 
   // Support for thread handshake operations
   HandshakeState _handshake;
@@ -819,14 +867,19 @@
     return byte_offset_of(JavaThread, _should_post_on_exceptions_flag);
   }
   static ByteSize doing_unsafe_access_offset() { return byte_offset_of(JavaThread, _doing_unsafe_access); }
   NOT_PRODUCT(static ByteSize requires_cross_modify_fence_offset()  { return byte_offset_of(JavaThread, _requires_cross_modify_fence); })
 
+  static ByteSize lock_id_offset()            { return byte_offset_of(JavaThread, _lock_id); }
+
   static ByteSize cont_entry_offset()         { return byte_offset_of(JavaThread, _cont_entry); }
   static ByteSize cont_fastpath_offset()      { return byte_offset_of(JavaThread, _cont_fastpath); }
   static ByteSize held_monitor_count_offset() { return byte_offset_of(JavaThread, _held_monitor_count); }
   static ByteSize jni_monitor_count_offset()  { return byte_offset_of(JavaThread, _jni_monitor_count); }
+  static ByteSize preempting_offset()         { return byte_offset_of(JavaThread, _preempting); }
+  static ByteSize preemption_cancelled_offset()  { return byte_offset_of(JavaThread, _preemption_cancelled); }
+  static ByteSize preempt_alternate_return_offset() { return byte_offset_of(JavaThread, _preempt_alternate_return); }
 
 #if INCLUDE_JVMTI
   static ByteSize is_in_VTMS_transition_offset()     { return byte_offset_of(JavaThread, _is_in_VTMS_transition); }
   static ByteSize is_in_tmp_VTMS_transition_offset() { return byte_offset_of(JavaThread, _is_in_tmp_VTMS_transition); }
   static ByteSize is_disable_suspend_offset()        { return byte_offset_of(JavaThread, _is_disable_suspend); }
@@ -1240,6 +1293,15 @@
     thread->push_jni_handle_block();
   }
   ~JNIHandleMark() { _thread->pop_jni_handle_block(); }
 };
 
+class ThreadOnMonitorEnter {
+  JavaThread* _thread;
+ public:
+  ThreadOnMonitorEnter(JavaThread* thread) : _thread(thread) {
+    _thread->set_on_monitorenter(true);
+  }
+  ~ThreadOnMonitorEnter() { _thread->set_on_monitorenter(false); }
+};
+
 #endif // SHARE_RUNTIME_JAVATHREAD_HPP
diff a/src/hotspot/share/runtime/javaThread.inline.hpp b/src/hotspot/share/runtime/javaThread.inline.hpp
--- a/src/hotspot/share/runtime/javaThread.inline.hpp
+++ b/src/hotspot/share/runtime/javaThread.inline.hpp
@@ -31,10 +31,11 @@
 #include "classfile/javaClasses.hpp"
 #include "gc/shared/tlab_globals.hpp"
 #include "memory/universe.hpp"
 #include "oops/instanceKlass.hpp"
 #include "oops/oopHandle.inline.hpp"
+#include "prims/jvmtiThreadState.hpp"
 #include "runtime/atomic.hpp"
 #include "runtime/continuation.hpp"
 #include "runtime/continuationEntry.inline.hpp"
 #include "runtime/nonJavaThread.hpp"
 #include "runtime/orderAccess.hpp"
diff a/src/hotspot/share/runtime/lockStack.cpp b/src/hotspot/share/runtime/lockStack.cpp
--- a/src/hotspot/share/runtime/lockStack.cpp
+++ b/src/hotspot/share/runtime/lockStack.cpp
@@ -146,11 +146,11 @@
       assert(mark.is_fast_locked() || mark.monitor()->is_owner_anonymous(),
              "must be fast_locked or inflated by other thread");
       top_index--;
     } else {
       assert(!mark.is_fast_locked(), "must be inflated");
-      assert(mark.monitor()->owner_raw() == get_thread() ||
+      assert(mark.monitor()->is_owner(get_thread()) ||
              (!leaf_frame && get_thread()->current_waiting_monitor() == mark.monitor()),
              "must be owned by (or waited on by) thread");
       assert(!contains(obj), "must not be on lock_stack");
     }
   }
diff a/src/hotspot/share/runtime/lockStack.hpp b/src/hotspot/share/runtime/lockStack.hpp
--- a/src/hotspot/share/runtime/lockStack.hpp
+++ b/src/hotspot/share/runtime/lockStack.hpp
@@ -114,10 +114,15 @@
   inline size_t remove(oop o);
 
   // Tests whether the oop is on this lock-stack.
   inline bool contains(oop o) const;
 
+  inline int monitor_count() const;
+  inline void move_to_address(oop* start);
+  inline void move_from_address(oop* start, int count);
+  inline int unique_count() const NOT_DEBUG_RETURN0;
+
   // GC support
   inline void oops_do(OopClosure* cl);
 
   // Printing
   void print_on(outputStream* st);
diff a/src/hotspot/share/runtime/lockStack.inline.hpp b/src/hotspot/share/runtime/lockStack.inline.hpp
--- a/src/hotspot/share/runtime/lockStack.inline.hpp
+++ b/src/hotspot/share/runtime/lockStack.inline.hpp
@@ -211,10 +211,49 @@
   }
   verify("post-contains");
   return false;
 }
 
+inline int LockStack::monitor_count() const {
+  int end = to_index(_top);
+  assert(end <= CAPACITY, "invariant");
+  return end;
+}
+
+inline void LockStack::move_to_address(oop* start) {
+  int end = to_index(_top);
+  for (int i = 0; i < end; i++) {
+    start[i] = _base[i];
+    DEBUG_ONLY(_base[i] = nullptr;)
+  }
+  _top = lock_stack_base_offset;
+}
+
+inline void LockStack::move_from_address(oop* start, int count) {
+  assert(to_index(_top) == 0, "lockstack should be empty");
+  for (int i = 0; i < count; i++) {
+    _base[i] = start[i];
+    _top += oopSize;
+  }
+}
+
+#ifdef ASSERT
+inline int LockStack::unique_count() const {
+  int end = to_index(_top);
+  oop previous = nullptr;
+  int count = 0;
+  for (int i = 0; i < end; i++) {
+    assert(_base[i] != nullptr, "invariant");
+    if (_base[i] != previous) {
+      previous = _base[i];
+      count++;
+    }
+  }
+  return count;
+}
+#endif
+
 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]);
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
@@ -50,10 +50,11 @@
 #include "runtime/osThread.hpp"
 #include "runtime/perfData.hpp"
 #include "runtime/safefetch.hpp"
 #include "runtime/safepointMechanism.inline.hpp"
 #include "runtime/sharedRuntime.hpp"
+#include "runtime/threads.hpp"
 #include "services/threadService.hpp"
 #include "utilities/dtrace.hpp"
 #include "utilities/globalDefinitions.hpp"
 #include "utilities/macros.hpp"
 #include "utilities/preserveException.hpp"
@@ -110,10 +111,22 @@
 
 DEBUG_ONLY(static volatile bool InitDone = false;)
 
 OopStorage* ObjectMonitor::_oop_storage = nullptr;
 
+OopHandle ObjectMonitor::_vthread_cxq_head;
+ParkEvent* ObjectMonitor::_vthread_unparker_ParkEvent = nullptr;
+
+static void post_virtual_thread_pinned_event(JavaThread* current, const char* reason) {
+  EventVirtualThreadPinned e;
+  if (e.should_commit()) {
+    e.set_pinnedReason(reason);
+    e.set_carrierThread(JFR_JVM_THREAD_ID(current));
+    e.commit();
+  }
+}
+
 // -----------------------------------------------------------------------------
 // Theory of operations -- Monitors lists, thread residency, etc:
 //
 // * A thread acquires ownership of a monitor by successfully
 //   CAS()ing the _owner field from null to non-null.
@@ -247,10 +260,11 @@
 
 ObjectMonitor::ObjectMonitor(oop object) :
   _header(markWord::zero()),
   _object(_oop_storage, object),
   _owner(nullptr),
+  _stack_locker(nullptr),
   _previous_owner_tid(0),
   _next_om(nullptr),
   _recursions(0),
   _EntryList(nullptr),
   _cxq(nullptr),
@@ -314,11 +328,11 @@
     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) {
+    } else if (prev_owner == owner_for(locking_thread)) {
       _recursions++;
       success = true;
     } else if (prev_owner == DEFLATER_MARKER) {
       // Racing with deflation.
       prev_owner = try_set_owner_from(DEFLATER_MARKER, locking_thread);
@@ -332,15 +346,10 @@
         // 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 {
@@ -355,11 +364,11 @@
     }
   }
 
   add_to_contentions(-1);
 
-  assert(!success || owner_raw() == locking_thread, "must be");
+  assert(!success || is_owner(locking_thread), "must be");
 
   return success;
 }
 
 bool ObjectMonitor::enter(JavaThread* current) {
@@ -371,41 +380,34 @@
   if (cur == nullptr) {
     assert(_recursions == 0, "invariant");
     return true;
   }
 
-  if (cur == current) {
+  if (cur == owner_for(current)) {
     // TODO-FIXME: check for integer overflow!  BUGID 6557169.
     _recursions++;
     return true;
   }
 
-  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;
-  }
-
   // We've encountered genuine contention.
 
   // Try one round of spinning *before* enqueueing current
   // and before going through the awkward and expensive state
   // transitions.  The following spin is strictly optional ...
   // Note that if we acquire the monitor from an initial spin
   // we forgo posting JVMTI events and firing DTRACE probes.
   if (TrySpin(current)) {
-    assert(owner_raw() == current, "must be current: owner=" INTPTR_FORMAT, p2i(owner_raw()));
+    assert(owner_raw() == owner_for(current), "must be current: owner=" INTPTR_FORMAT, p2i(owner_raw()));
     assert(_recursions == 0, "must be 0: recursions=" INTX_FORMAT, _recursions);
     assert(object()->mark() == markWord::encode(this),
            "object mark must match encoded this: mark=" INTPTR_FORMAT
            ", encoded this=" INTPTR_FORMAT, object()->mark().value(),
            markWord::encode(this).value());
     return true;
   }
 
-  assert(owner_raw() != current, "invariant");
+  assert(owner_raw() != owner_for(current), "invariant");
   assert(_succ != current, "invariant");
   assert(!SafepointSynchronize::is_at_safepoint(), "invariant");
   assert(current->thread_state() != _thread_blocked, "invariant");
 
   // Keep track of contention for JVM/TI and M&M queries.
@@ -449,10 +451,27 @@
       // This means that the JVMTI_EVENT_MONITOR_CONTENDED_ENTER event
       // handler cannot accidentally consume an unpark() meant for the
       // ParkEvent associated with this ObjectMonitor.
     }
 
+#ifdef LOOM_MONITOR_SUPPORT
+    ContinuationEntry* ce = current->last_continuation();
+    if (ce != nullptr && ce->is_virtual_thread() && current->is_on_monitorenter()) {
+      int result = Continuation::try_preempt(current, ce->cont_oop(current));
+      if (result == freeze_ok) {
+        bool acquired = HandlePreemptedVThread(current);
+        DEBUG_ONLY(int state = java_lang_VirtualThread::state(current->vthread()));
+        assert((acquired && current->preemption_cancelled() && state == java_lang_VirtualThread::RUNNING) ||
+               (!acquired && !current->preemption_cancelled() && state == java_lang_VirtualThread::BLOCKING), "invariant");
+        return true;
+      }
+      if (result == freeze_pinned_native) {
+        post_virtual_thread_pinned_event(current, "Native frame or <clinit> on stack");
+      }
+    }
+#endif
+
     OSThreadContendState osts(current->osthread());
 
     assert(current->thread_state() == _thread_in_vm, "invariant");
 
     for (;;) {
@@ -469,11 +488,11 @@
         // If there is a suspend request, ExitOnSuspend will exit the OM
         // and set the OM as pending.
       }
       if (!eos.exited()) {
         // ExitOnSuspend did not exit the OM
-        assert(owner_raw() == current, "invariant");
+        assert(owner_raw() == owner_for(current), "invariant");
         break;
       }
     }
 
     // We've just gotten past the enter-check-for-suspend dance and we now own
@@ -483,11 +502,11 @@
   add_to_contentions(-1);
   assert(contentions() >= 0, "must not be negative: contentions=%d", contentions());
 
   // Must either set _recursions = 0 or ASSERT _recursions == 0.
   assert(_recursions == 0, "invariant");
-  assert(owner_raw() == current, "invariant");
+  assert(owner_raw() == owner_for(current), "invariant");
   assert(_succ != current, "invariant");
   assert(object()->mark() == markWord::encode(this), "invariant");
 
   // The thread -- now the owner -- is back in vm mode.
   // Report the glorious news via TI,DTrace and jvmstat.
@@ -558,31 +577,31 @@
   const oop obj = object_peek();
 
   if (obj == nullptr) {
     // If the object died, we can recycle the monitor without racing with
     // Java threads. The GC already broke the association with the object.
-    set_owner_from(nullptr, DEFLATER_MARKER);
+    set_owner_from_raw(nullptr, DEFLATER_MARKER);
     assert(contentions() >= 0, "must be non-negative: contentions=%d", contentions());
     _contentions = INT_MIN; // minimum negative int
   } else {
     // Attempt async deflation protocol.
 
     // Set a null owner to DEFLATER_MARKER to force any contending thread
     // through the slow path. This is just the first part of the async
     // deflation dance.
-    if (try_set_owner_from(nullptr, DEFLATER_MARKER) != nullptr) {
+    if (try_set_owner_from_raw(nullptr, DEFLATER_MARKER) != nullptr) {
       // The owner field is no longer null so we lost the race since the
       // ObjectMonitor is now busy.
       return false;
     }
 
     if (contentions() > 0 || _waiters != 0) {
       // Another thread has raced to enter the ObjectMonitor after
       // is_busy() above or has already entered and waited on
       // it which makes it busy so no deflation. Restore owner to
       // null if it is still DEFLATER_MARKER.
-      if (try_set_owner_from(DEFLATER_MARKER, nullptr) != DEFLATER_MARKER) {
+      if (try_set_owner_from_raw(DEFLATER_MARKER, nullptr) != DEFLATER_MARKER) {
         // Deferred decrement for the JT EnterI() that cancelled the async deflation.
         add_to_contentions(-1);
       }
       return false;
     }
@@ -591,11 +610,11 @@
     // to retry. This is the second part of the async deflation dance.
     if (Atomic::cmpxchg(&_contentions, 0, INT_MIN) != 0) {
       // Contentions was no longer 0 so we lost the race since the
       // ObjectMonitor is now busy. Restore owner to null if it is
       // still DEFLATER_MARKER:
-      if (try_set_owner_from(DEFLATER_MARKER, nullptr) != DEFLATER_MARKER) {
+      if (try_set_owner_from_raw(DEFLATER_MARKER, nullptr) != DEFLATER_MARKER) {
         // Deferred decrement for the JT EnterI() that cancelled the async deflation.
         add_to_contentions(-1);
       }
       return false;
     }
@@ -687,11 +706,11 @@
 // Convert the fields used by is_busy() to a string that can be
 // used for diagnostic output.
 const char* ObjectMonitor::is_busy_to_string(stringStream* ss) {
   ss->print("is_busy: waiters=%d"
             ", contentions=%d"
-            ", owner=" PTR_FORMAT
+            ", owner=" INTPTR_FORMAT
             ", cxq=" PTR_FORMAT
             ", EntryList=" PTR_FORMAT,
             _waiters,
             (contentions() > 0 ? contentions() : 0),
             owner_is_DEFLATER_MARKER()
@@ -710,11 +729,11 @@
   assert(current->thread_state() == _thread_blocked, "invariant");
 
   // Try the lock - TATAS
   if (TryLock(current) == TryLockResult::Success) {
     assert(_succ != current, "invariant");
-    assert(owner_raw() == current, "invariant");
+    assert(owner_raw() == owner_for(current), "invariant");
     assert(_Responsible != current, "invariant");
     return;
   }
 
   if (try_set_owner_from(DEFLATER_MARKER, current) == DEFLATER_MARKER) {
@@ -743,19 +762,19 @@
   // operation to donate the remainder of this thread's quantum
   // to the owner.  This has subtle but beneficial affinity
   // effects.
 
   if (TrySpin(current)) {
-    assert(owner_raw() == current, "invariant");
+    assert(owner_raw() == owner_for(current), "invariant");
     assert(_succ != current, "invariant");
     assert(_Responsible != current, "invariant");
     return;
   }
 
   // The Spin failed -- Enqueue and park the thread ...
   assert(_succ != current, "invariant");
-  assert(owner_raw() != current, "invariant");
+  assert(owner_raw() != owner_for(current), "invariant");
   assert(_Responsible != current, "invariant");
 
   // Enqueue "current" on ObjectMonitor's _cxq.
   //
   // Node acts as a proxy for current.
@@ -781,11 +800,11 @@
 
     // Interference - the CAS failed because _cxq changed.  Just retry.
     // As an optional optimization we retry the lock.
     if (TryLock(current) == TryLockResult::Success) {
       assert(_succ != current, "invariant");
-      assert(owner_raw() == current, "invariant");
+      assert(owner_raw() == owner_for(current), "invariant");
       assert(_Responsible != current, "invariant");
       return;
     }
   }
 
@@ -829,20 +848,26 @@
   // to defer the state transitions until absolutely necessary,
   // and in doing so avoid some transitions ...
 
   int nWakeups = 0;
   int recheckInterval = 1;
+  bool do_timed_parked = false;
+
+  ContinuationEntry* ce = current->last_continuation();
+  if (ce != nullptr && ce->is_virtual_thread()) {
+    do_timed_parked = true;
+  }
 
   for (;;) {
 
     if (TryLock(current) == TryLockResult::Success) {
       break;
     }
-    assert(owner_raw() != current, "invariant");
+    assert(owner_raw() != owner_for(current), "invariant");
 
     // park self
-    if (_Responsible == current) {
+    if (_Responsible == current || do_timed_parked) {
       current->_ParkEvent->park((jlong) recheckInterval);
       // Increase the recheckInterval, but clamp the value.
       recheckInterval *= 8;
       if (recheckInterval > MAX_RECHECK_INTERVAL) {
         recheckInterval = MAX_RECHECK_INTERVAL;
@@ -908,11 +933,11 @@
   // From the perspective of the lock owner (this thread), the
   // EntryList is stable and cxq is prepend-only.
   // The head of cxq is volatile but the interior is stable.
   // In addition, current.TState is stable.
 
-  assert(owner_raw() == current, "invariant");
+  assert(owner_raw() == owner_for(current), "invariant");
 
   UnlinkAfterAcquire(current, &node);
   if (_succ == current) _succ = nullptr;
 
   assert(_succ != current, "invariant");
@@ -964,10 +989,95 @@
   // execute a serializing instruction.
 
   return;
 }
 
+bool ObjectMonitor::HandlePreemptedVThread(JavaThread* current) {
+  // Either because we acquire the lock below or because we will preempt the
+  // vthread clear the _current_pending_monitor field from the current JavaThread.
+  current->set_current_pending_monitor(nullptr);
+
+  // Try once more after freezing the continuation.
+  if (TryLock(current) == TryLockResult::Success) {
+    assert(owner_raw() == owner_for(current), "invariant");
+    assert(_succ != current, "invariant");
+    assert(_Responsible != current, "invariant");
+    current->set_preemption_cancelled(true);
+    add_to_contentions(-1);
+    return true;
+  }
+
+  if (try_set_owner_from(DEFLATER_MARKER, current) == DEFLATER_MARKER) {
+    // Cancelled the in-progress async deflation by changing owner from
+    // DEFLATER_MARKER to current. As part of the contended enter protocol,
+    // contentions was incremented to a positive value before this call to
+    // HandlePreemptedVThread(). We avoid decrementing contentions to
+    // prevent the deflater thread from winning the last part of the
+    // 2-part async deflation protocol. The deflater thread will decrement
+    // contentions after it recognizes that the async deflation was cancelled.
+    assert(_succ != current, "invariant");
+    assert(_Responsible != current, "invariant");
+    current->set_preemption_cancelled(true);
+    return true;
+  }
+
+  oop vthread = current->vthread();
+  assert(java_lang_VirtualThread::state(vthread) == java_lang_VirtualThread::RUNNING, "wrong state for vthread");
+  java_lang_VirtualThread::set_state(vthread, java_lang_VirtualThread::BLOCKING);
+
+  ObjectWaiter* node = new ObjectWaiter(vthread);
+  node->_prev   = (ObjectWaiter*) 0xBAD;
+  node->TState  = ObjectWaiter::TS_CXQ;
+
+  // Push node associated with vthread onto the front of the _cxq.
+  ObjectWaiter* nxt;
+  for (;;) {
+    node->_next = nxt = _cxq;
+    if (Atomic::cmpxchg(&_cxq, nxt, node) == nxt) break;
+
+    // Interference - the CAS failed because _cxq changed.  Just retry.
+    // As an optional optimization we retry the lock.
+    if (TryLock(current) == TryLockResult::Success) {
+      assert(owner_raw() == owner_for(current), "invariant");
+      assert(_succ != current, "invariant");
+      assert(_Responsible != current, "invariant");
+      current->set_preemption_cancelled(true);
+      java_lang_VirtualThread::set_state(vthread, java_lang_VirtualThread::RUNNING);
+      add_to_contentions(-1);
+      delete node;
+      return true;
+    }
+  }
+
+  // We have to try once more since owner could have exited monitor and checked
+  // _cxq before we added the node to the queue.
+  if (TryLock(current) == TryLockResult::Success) {
+    assert(owner_raw() == owner_for(current), "invariant");
+    assert(_Responsible != current, "invariant");
+    current->set_preemption_cancelled(true);
+    java_lang_VirtualThread::set_state(vthread, java_lang_VirtualThread::RUNNING);
+    UnlinkAfterAcquire(current, node, vthread);
+    delete node;
+    if (_succ == (JavaThread*)java_lang_Thread::thread_id(vthread)) _succ = nullptr;
+    add_to_contentions(-1);
+    return true;
+  }
+
+  if (nxt == nullptr && _EntryList == nullptr) {
+    // The C2 unlock() fast path first checks if _cxq and _EntryList are empty and
+    // if they are it just clears the _owner field. Since we always run the risk of
+    // having that check happening before we added the node to _cxq and the release
+    // of the monitor happening after the last TryLock attempt we need to do something
+    // to avoid stranding. We set the _Responsible field which results in a timed-wait.
+    if (Atomic::replace_if_null(&_Responsible, (JavaThread*)java_lang_Thread::thread_id(vthread))) {
+      java_lang_VirtualThread::set_recheckInterval(vthread, 1);
+    }
+  }
+
+  return false;
+}
+
 // ReenterI() is a specialized inline form of the latter half of the
 // contended slow-path from EnterI().  We use ReenterI() only for
 // monitor reentry in wait().
 //
 // In the future we should reconcile EnterI() and ReenterI().
@@ -983,11 +1093,11 @@
 
   int nWakeups = 0;
   for (;;) {
     ObjectWaiter::TStates v = currentNode->TState;
     guarantee(v == ObjectWaiter::TS_ENTER || v == ObjectWaiter::TS_CXQ, "invariant");
-    assert(owner_raw() != current, "invariant");
+    assert(owner_raw() != owner_for(current), "invariant");
 
     if (TrySpin(current)) {
         break;
     }
 
@@ -1036,26 +1146,85 @@
   // From the perspective of the lock owner (this thread), the
   // EntryList is stable and cxq is prepend-only.
   // The head of cxq is volatile but the interior is stable.
   // In addition, current.TState is stable.
 
-  assert(owner_raw() == current, "invariant");
+  assert(owner_raw() == owner_for(current), "invariant");
   assert(object()->mark() == markWord::encode(this), "invariant");
   UnlinkAfterAcquire(current, currentNode);
   if (_succ == current) _succ = nullptr;
   assert(_succ != current, "invariant");
   currentNode->TState = ObjectWaiter::TS_RUN;
   OrderAccess::fence();      // see comments at the end of EnterI()
 }
 
+void ObjectMonitor::redo_enter(JavaThread* current) {
+  assert(java_lang_VirtualThread::state(current->vthread()) == java_lang_VirtualThread::RUNNING, "wrong state for vthread");
+  assert(current->is_in_VTMS_transition(), "must be");
+
+  if (TryLock(current) == TryLockResult::Success) {
+    VThreadEpilog(current);
+    return;
+  }
+
+  oop vthread = current->vthread();
+  if (_succ == (JavaThread*)java_lang_Thread::thread_id(vthread)) _succ = nullptr;
+
+  // Invariant: after clearing _succ a thread *must* retry _owner before parking.
+  OrderAccess::fence();
+
+  if (TryLock(current) == TryLockResult::Success) {
+    assert(owner_raw() == owner_for(current), "invariant");
+    VThreadEpilog(current);
+    return;
+  }
+
+  // Fast preemption. The JT will read this variable on return to the
+  // monitorenter_redo stub and will just remove enterSpecial frame
+  // from the stack and return to Continuation.run()
+  current->set_preempting(true);
+
+  java_lang_VirtualThread::set_state(vthread, java_lang_VirtualThread::BLOCKING);
+  if (_Responsible == (JavaThread*)java_lang_Thread::thread_id(vthread)) {
+    int recheckInterval = java_lang_VirtualThread::recheckInterval(vthread);
+    assert(recheckInterval >= 1 && recheckInterval <= 6, "invariant");
+    if (recheckInterval < 6) {
+      recheckInterval++;
+      java_lang_VirtualThread::set_recheckInterval(vthread, recheckInterval);
+    }
+  } else if (java_lang_VirtualThread::recheckInterval(vthread) > 0) {
+    // No need to do timed park anymore
+    java_lang_VirtualThread::set_recheckInterval(vthread, 0);
+  }
+}
+
+void ObjectMonitor::VThreadEpilog(JavaThread* current) {
+  assert(owner_raw() == owner_for(current), "invariant");
+  add_to_contentions(-1);
+
+  oop vthread = current->vthread();
+  if (java_lang_VirtualThread::recheckInterval(vthread) > 0) {
+    java_lang_VirtualThread::set_recheckInterval(vthread, 0);
+  }
+  int64_t threadid = java_lang_Thread::thread_id(vthread);
+  if (_succ == (JavaThread*)threadid) _succ = nullptr;
+  if (_Responsible == (JavaThread*)threadid) {
+    _Responsible = nullptr;
+    OrderAccess::fence(); // Dekker pivot-point
+  }
+  ObjectWaiter* node = LookupWaiter(threadid);
+  UnlinkAfterAcquire(current, node, vthread);
+  delete node;
+}
+
 // By convention we unlink a contending thread from EntryList|cxq immediately
 // after the thread acquires the lock in ::enter().  Equally, we could defer
 // unlinking the thread until ::exit()-time.
 
-void ObjectMonitor::UnlinkAfterAcquire(JavaThread* current, ObjectWaiter* currentNode) {
-  assert(owner_raw() == current, "invariant");
-  assert(currentNode->_thread == current, "invariant");
+void ObjectMonitor::UnlinkAfterAcquire(JavaThread* current, ObjectWaiter* currentNode, oop vthread) {
+  assert(owner_raw() == owner_for(current), "invariant");
+  assert((currentNode->_thread == current) || (currentNode->_thread == nullptr && currentNode->vthread() == vthread), "invariant");
 
   if (currentNode->TState == ObjectWaiter::TS_ENTER) {
     // Normal case: remove current from the DLL EntryList .
     // This is a constant-time operation.
     ObjectWaiter* nxt = currentNode->_next;
@@ -1111,10 +1280,20 @@
   currentNode->_next  = (ObjectWaiter*) 0xBAD;
   currentNode->TState = ObjectWaiter::TS_RUN;
 #endif
 }
 
+// Fix this. Save ObjectWaiter* when freezing. Or use hashtable.
+ObjectWaiter* ObjectMonitor::LookupWaiter(int64_t threadid) {
+  ObjectWaiter* p;
+  for (p = _EntryList; p != nullptr && (!p->is_vthread() || java_lang_Thread::thread_id(p->vthread()) != threadid); p = p->_next) {}
+  if (p != nullptr) return p;
+  for (p = _cxq; p != nullptr && (!p->is_vthread() || java_lang_Thread::thread_id(p->vthread()) != threadid); p = p->_next) {}
+  assert(p != nullptr, "should be on either _cxq or _EntryList");
+  return p;
+}
+
 // -----------------------------------------------------------------------------
 // Exit support
 //
 // exit()
 // ~~~~~~
@@ -1170,35 +1349,29 @@
 // structured the code so the windows are short and the frequency
 // of such futile wakups is low.
 
 void ObjectMonitor::exit(JavaThread* current, bool not_suspended) {
   void* cur = owner_raw();
-  if (current != 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 ...
-      // Naively we'd like to throw IllegalMonitorStateException.
-      // As a practical matter we can neither allocate nor throw an
-      // exception as ::exit() can be called from leaf routines.
-      // see x86_32.ad Fast_Unlock() and the I1 and I2 properties.
-      // Upon deeper reflection, however, in a properly run JVM the only
-      // way we should encounter this situation is in the presence of
-      // unbalanced JNI locking. TODO: CheckJNICalls.
-      // See also: CR4414101
+  if (owner_for(current) != cur) {
+    // Apparent unbalanced locking ...
+    // Naively we'd like to throw IllegalMonitorStateException.
+    // As a practical matter we can neither allocate nor throw an
+    // exception as ::exit() can be called from leaf routines.
+    // see x86_32.ad Fast_Unlock() and the I1 and I2 properties.
+    // Upon deeper reflection, however, in a properly run JVM the only
+    // way we should encounter this situation is in the presence of
+    // unbalanced JNI locking. TODO: CheckJNICalls.
+    // See also: CR4414101
 #ifdef ASSERT
-      LogStreamHandle(Error, monitorinflation) lsh;
-      lsh.print_cr("ERROR: ObjectMonitor::exit(): thread=" INTPTR_FORMAT
-                    " is exiting an ObjectMonitor it does not own.", p2i(current));
-      lsh.print_cr("The imbalance is possibly caused by JNI locking.");
-      print_debug_style_on(&lsh);
-      assert(false, "Non-balanced monitor enter/exit!");
+    LogStreamHandle(Error, monitorinflation) lsh;
+    lsh.print_cr("ERROR: ObjectMonitor::exit(): thread=" INTPTR_FORMAT
+                  " is exiting an ObjectMonitor it does not own.", p2i(current));
+    lsh.print_cr("The imbalance is possibly caused by JNI locking.");
+    print_debug_style_on(&lsh);
+    assert(false, "Non-balanced monitor enter/exit!");
 #endif
-      return;
-    }
+    return;
   }
 
   if (_recursions != 0) {
     _recursions--;        // this is simple recursive enter
     return;
@@ -1215,17 +1388,17 @@
     _previous_owner_tid = JFR_THREAD_ID(current);
   }
 #endif
 
   for (;;) {
-    assert(current == owner_raw(), "invariant");
+    assert(owner_for(current) == owner_raw(), "invariant");
 
     // Drop the lock.
     // release semantics: prior loads and stores from within the critical section
     // must not float (reorder) past the following store that drops the lock.
     // Uses a storeload to separate release_store(owner) from the
-    // successor check. The try_set_owner() below uses cmpxchg() so
+    // successor check. The try_set_owner_from() below uses cmpxchg() so
     // we get the fence down there.
     release_clear_owner(current);
     OrderAccess::storeload();
 
     if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != nullptr) {
@@ -1271,11 +1444,11 @@
     //
     if (try_set_owner_from(nullptr, current) != nullptr) {
       return;
     }
 
-    guarantee(owner_raw() == current, "invariant");
+    guarantee(owner_raw() == owner_for(current), "invariant");
 
     ObjectWaiter* w = nullptr;
 
     w = _EntryList;
     if (w != nullptr) {
@@ -1348,19 +1521,27 @@
     }
   }
 }
 
 void ObjectMonitor::ExitEpilog(JavaThread* current, ObjectWaiter* Wakee) {
-  assert(owner_raw() == current, "invariant");
+  assert(owner_raw() == owner_for(current), "invariant");
 
   // Exit protocol:
   // 1. ST _succ = wakee
   // 2. membar #loadstore|#storestore;
   // 2. ST _owner = nullptr
   // 3. unpark(wakee)
 
-  _succ = Wakee->_thread;
+  oop vthread = nullptr;
+  if (Wakee->_thread != nullptr) {
+    // Platform thread case
+    _succ = Wakee->_thread;
+  } else {
+    assert(Wakee->vthread() != nullptr, "invariant");
+    vthread = Wakee->vthread();
+    _succ = (JavaThread*)java_lang_Thread::thread_id(vthread);
+  }
   ParkEvent * Trigger = Wakee->_event;
 
   // Hygiene -- once we've set _owner = nullptr we can't safely dereference Wakee again.
   // The thread associated with Wakee may have grabbed the lock and "Wakee" may be
   // out-of-scope (non-extant).
@@ -1370,11 +1551,17 @@
   // Uses a fence to separate release_store(owner) from the LD in unpark().
   release_clear_owner(current);
   OrderAccess::fence();
 
   DTRACE_MONITOR_PROBE(contended__exit, this, object(), current);
-  Trigger->unpark();
+
+  if (vthread == nullptr) {
+    // Platform thread case
+    Trigger->unpark();
+  } else if (java_lang_VirtualThread::set_onWaitingList(vthread, _vthread_cxq_head)) {
+    Trigger->unpark();
+  }
 
   // Maintain stats and report events to JVMTI
   OM_PERFDATA_OP(Parks, inc());
 }
 
@@ -1385,23 +1572,23 @@
 // thread due to contention.
 intx ObjectMonitor::complete_exit(JavaThread* current) {
   assert(InitDone, "Unexpectedly not initialized");
 
   void* cur = owner_raw();
-  if (current != cur) {
-    if (LockingMode != LM_LIGHTWEIGHT && current->is_lock_owned((address)cur)) {
+  if (owner_for(current) != cur) {
+    if (LockingMode == LM_LEGACY && is_stack_locker(current)) {
       assert(_recursions == 0, "internal state error");
-      set_owner_from_BasicLock(cur, current);  // Convert from BasicLock* to Thread*.
+      set_owner_from_BasicLock(current);  // Convert from BasicLock* to Thread*.
       _recursions = 0;
     }
   }
 
-  guarantee(current == owner_raw(), "complete_exit not owner");
+  guarantee(owner_for(current) == owner_raw(), "complete_exit not owner");
   intx save = _recursions; // record the old recursion count
   _recursions = 0;         // set the recursion level to be 0
   exit(current);           // exit the monitor
-  guarantee(owner_raw() != current, "invariant");
+  guarantee(owner_raw() != owner_for(current), "invariant");
   return save;
 }
 
 // Checks that the current THREAD owns this monitor and causes an
 // immediate return if it doesn't. We don't use the CHECK macro
@@ -1421,17 +1608,11 @@
 // (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 (LockingMode != LM_LIGHTWEIGHT && current->is_lock_owned((address)cur)) {
-    set_owner_from_BasicLock(cur, current);  // Convert from BasicLock* to Thread*.
-    _recursions = 0;
+  if (cur == owner_for(current)) {
     return true;
   }
   THROW_MSG_(vmSymbols::java_lang_IllegalMonitorStateException(),
              "current thread is not owner", false);
 }
@@ -1499,10 +1680,21 @@
     }
     THROW(vmSymbols::java_lang_InterruptedException());
     return;
   }
 
+  ContinuationEntry* ce = current->last_continuation();
+  if (ce != nullptr && ce->is_virtual_thread()) {
+    const Klass* monitor_klass = object()->klass();
+    if (!is_excluded(monitor_klass)) {
+      ResourceMark rm;
+      char reason[256];
+      jio_snprintf(reason, sizeof reason, "Object.wait on object of klass %s", monitor_klass->external_name());
+      post_virtual_thread_pinned_event(current, reason);
+    }
+  }
+
   current->set_current_waiting_monitor(this);
 
   // create a node to be put into the queue
   // Critically, after we reset() the event but prior to park(), we must check
   // for a pending interrupt.
@@ -1526,11 +1718,11 @@
 
   intx save = _recursions;     // record the old recursion count
   _waiters++;                  // increment the number of waiters
   _recursions = 0;             // set the recursion level to be 1
   exit(current);               // exit the monitor
-  guarantee(owner_raw() != current, "invariant");
+  guarantee(owner_raw() != owner_for(current), "invariant");
 
   // The thread is on the WaitSet list - now park() it.
   // On MP systems it's conceivable that a brief spin before we park
   // could be profitable.
   //
@@ -1632,11 +1824,11 @@
       post_monitor_wait_event(&event, this, node._notifier_tid, millis, ret == OS_TIMEOUT);
     }
 
     OrderAccess::fence();
 
-    assert(owner_raw() != current, "invariant");
+    assert(owner_raw() != owner_for(current), "invariant");
     ObjectWaiter::TStates v = node.TState;
     if (v == ObjectWaiter::TS_RUN) {
       enter(current);
     } else {
       guarantee(v == ObjectWaiter::TS_ENTER || v == ObjectWaiter::TS_CXQ, "invariant");
@@ -1647,25 +1839,25 @@
     // current has reacquired the lock.
     // Lifecycle - the node representing current must not appear on any queues.
     // Node is about to go out-of-scope, but even if it were immortal we wouldn't
     // want residual elements associated with this thread left on any lists.
     guarantee(node.TState == ObjectWaiter::TS_RUN, "invariant");
-    assert(owner_raw() == current, "invariant");
+    assert(owner_raw() == owner_for(current), "invariant");
     assert(_succ != current, "invariant");
   } // OSThreadWaitState()
 
   current->set_current_waiting_monitor(nullptr);
 
   guarantee(_recursions == 0, "invariant");
   int relock_count = JvmtiDeferredUpdates::get_and_reset_relock_count_after_wait(current);
   _recursions =   save          // restore the old recursion count
                 + relock_count; //  increased by the deferred relock count
-  current->inc_held_monitor_count(relock_count); // Deopt never entered these counts.
+  NOT_LOOM_MONITOR_SUPPORT(current->inc_held_monitor_count(relock_count);) // Deopt never entered these counts.
   _waiters--;             // decrement the number of waiters
 
   // Verify a few postconditions
-  assert(owner_raw() == current, "invariant");
+  assert(owner_raw() == owner_for(current), "invariant");
   assert(_succ != current, "invariant");
   assert(object()->mark() == markWord::encode(this), "invariant");
 
   // check if the notification happened
   if (!WasNotified) {
@@ -1938,11 +2130,11 @@
   // when preparing to LD...CAS _owner, etc and the CAS is likely
   // to succeed.
   if (_succ == nullptr) {
     _succ = current;
   }
-  Thread* prv = nullptr;
+  void* prv = nullptr;
 
   // There are three ways to exit the following loop:
   // 1.  A successful spin where this thread has acquired the lock.
   // 2.  Spin failure with prejudice
   // 3.  Spin failure without prejudice
@@ -1976,13 +2168,13 @@
     // or if we observe _owner change from one non-null value to
     // another non-null value.   In such cases we might abort
     // the spin without prejudice or apply a "penalty" to the
     // spin count-down variable "ctr", reducing it by 100, say.
 
-    JavaThread* ox = static_cast<JavaThread*>(owner_raw());
+    void* ox = owner_raw();
     if (ox == nullptr) {
-      ox = static_cast<JavaThread*>(try_set_owner_from(nullptr, current));
+      ox = try_set_owner_from(nullptr, current);
       if (ox == nullptr) {
         // The CAS succeeded -- this thread acquired ownership
         // Take care of some bookkeeping to exit spin state.
         if (_succ == current) {
           _succ = nullptr;
@@ -2047,15 +2239,19 @@
   _prev     = nullptr;
   _notified = 0;
   _notifier_tid = 0;
   TState    = TS_RUN;
   _thread   = current;
-  _event    = _thread->_ParkEvent;
+  _event    = _thread != nullptr ? _thread->_ParkEvent : ObjectMonitor::vthread_unparker_ParkEvent();
   _active   = false;
   assert(_event != nullptr, "invariant");
 }
 
+ObjectWaiter::ObjectWaiter(oop vthread) : ObjectWaiter((JavaThread*)nullptr) {
+  _vthread = OopHandle(JavaThread::thread_oop_storage(), vthread);
+}
+
 void ObjectWaiter::wait_reenter_begin(ObjectMonitor * const mon) {
   _active = JavaThreadBlockedOnMonitorEnterState::wait_reenter_begin(_thread, mon);
 }
 
 void ObjectWaiter::wait_reenter_end(ObjectMonitor * const mon) {
@@ -2167,10 +2363,15 @@
   _oop_storage = OopStorageSet::create_weak("ObjectSynchronizer Weak", mtSynchronizer);
 
   DEBUG_ONLY(InitDone = true;)
 }
 
+void ObjectMonitor::Initialize2() {
+  _vthread_cxq_head = OopHandle(JavaThread::thread_oop_storage(), nullptr);
+  _vthread_unparker_ParkEvent = ParkEvent::Allocate(nullptr);
+}
+
 void ObjectMonitor::print_on(outputStream* st) const {
   // The minimal things to print for markWord printing, more can be added for debugging and logging.
   st->print("{contentions=0x%08x,waiters=0x%08x"
             ",recursions=" INTX_FORMAT ",owner=" INTPTR_FORMAT "}",
             contentions(), waiters(), recursions(),
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
@@ -26,37 +26,49 @@
 #define SHARE_RUNTIME_OBJECTMONITOR_HPP
 
 #include "memory/allocation.hpp"
 #include "memory/padded.hpp"
 #include "oops/markWord.hpp"
+#include "oops/oopHandle.inline.hpp"
 #include "oops/weakHandle.hpp"
+#include "runtime/javaThread.hpp"
 #include "runtime/perfDataTypes.hpp"
 #include "utilities/checkedCast.hpp"
 
 class ObjectMonitor;
 class ParkEvent;
+class BasicLock;
 
 // ObjectWaiter serves as a "proxy" or surrogate thread.
 // TODO-FIXME: Eliminate ObjectWaiter and use the thread-specific
 // ParkEvent instead.  Beware, however, that the JVMTI code
 // knows about ObjectWaiters, so we'll have to reconcile that code.
 // See next_waiter(), first_waiter(), etc.
 
-class ObjectWaiter : public StackObj {
+class ObjectWaiter : public CHeapObj<mtThread> {
  public:
   enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ };
   ObjectWaiter* volatile _next;
   ObjectWaiter* volatile _prev;
   JavaThread*   _thread;
+  OopHandle _vthread;
   uint64_t      _notifier_tid;
   ParkEvent *   _event;
   volatile int  _notified;
   volatile TStates TState;
   bool          _active;           // Contention monitoring is enabled
  public:
   ObjectWaiter(JavaThread* current);
-
+  ObjectWaiter(oop vthread);
+  ~ObjectWaiter() {
+    if (is_vthread()) {
+      assert(_vthread.resolve() != nullptr, "invariant");
+      _vthread.release(JavaThread::thread_oop_storage());
+    }
+  }
+  oop vthread() { return _vthread.resolve(); }
+  bool is_vthread() { return _thread == nullptr; }
   void wait_reenter_begin(ObjectMonitor *mon);
   void wait_reenter_end(ObjectMonitor *mon);
 };
 
 // The ObjectMonitor class implements the heavyweight version of a
@@ -125,14 +137,18 @@
 
 class ObjectMonitor : public CHeapObj<mtObjectMonitor> {
   friend class ObjectSynchronizer;
   friend class ObjectWaiter;
   friend class VMStructs;
+  friend class MonitorList;
   JVMCI_ONLY(friend class JVMCIVMStructs;)
 
   static OopStorage* _oop_storage;
 
+  static OopHandle _vthread_cxq_head;
+  static ParkEvent* _vthread_unparker_ParkEvent;
+
   // The sync code expects the header field to be at offset zero (0).
   // Enforced by the assert() in header_addr().
   volatile markWord _header;        // displaced object header word - mark
   WeakHandle _object;               // backward object pointer
   // Separate _header and _owner on different cache lines since both can
@@ -156,16 +172,17 @@
 
 private:
   static void* anon_owner_ptr() { return reinterpret_cast<void*>(ANONYMOUS_OWNER); }
 
   void* volatile _owner;            // pointer to owning thread OR BasicLock
+  BasicLock* volatile _stack_locker;      // can this share a cache line with owner? they're used together
   volatile uint64_t _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
   // cache line with _owner.
-  DEFINE_PAD_MINUS_SIZE(1, OM_CACHE_LINE_SIZE, sizeof(void* volatile) +
+  DEFINE_PAD_MINUS_SIZE(1, OM_CACHE_LINE_SIZE, 2 * sizeof(void* volatile) +
                         sizeof(volatile uint64_t));
   ObjectMonitor* _next_om;          // Next ObjectMonitor* linkage
   volatile intx _recursions;        // recursion count, 0 for first entry
   ObjectWaiter* volatile _EntryList;  // Threads blocked on entry or reentry.
                                       // The list is actually composed of WaitNodes,
@@ -188,10 +205,14 @@
   volatile int _WaitSetLock;        // protects Wait Queue - simple spinlock
 
  public:
 
   static void Initialize();
+  static void Initialize2();
+
+  static OopHandle& vthread_cxq_head() { return _vthread_cxq_head; }
+  static ParkEvent* vthread_unparker_ParkEvent() { return _vthread_unparker_ParkEvent; }
 
   // Only perform a PerfData operation if the PerfData object has been
   // allocated and if the PerfDataManager has not freed the PerfData
   // objects which can happen at normal VM shutdown.
   //
@@ -216,10 +237,11 @@
   static ByteSize owner_offset()       { return byte_offset_of(ObjectMonitor, _owner); }
   static ByteSize recursions_offset()  { return byte_offset_of(ObjectMonitor, _recursions); }
   static ByteSize cxq_offset()         { return byte_offset_of(ObjectMonitor, _cxq); }
   static ByteSize succ_offset()        { return byte_offset_of(ObjectMonitor, _succ); }
   static ByteSize EntryList_offset()   { return byte_offset_of(ObjectMonitor, _EntryList); }
+  static ByteSize stack_locker_offset(){ return byte_offset_of(ObjectMonitor, _stack_locker); }
 
   // ObjectMonitor references can be ORed with markWord::monitor_value
   // as part of the ObjectMonitor tagging mechanism. When we combine an
   // ObjectMonitor reference with an offset, we need to remove the tag
   // value in order to generate the proper address.
@@ -250,55 +272,62 @@
     return ret_code != 0;
   }
   const char* is_busy_to_string(stringStream* ss);
 
   bool is_entered(JavaThread* current) const;
+  int contentions() const;
 
   // Returns true if this OM has an owner, false otherwise.
-  bool      has_owner() const;
-  void*     owner() const;  // Returns null if DEFLATER_MARKER is observed.
+  bool   has_owner() const;
+  void*  owner() const;  // Returns null if DEFLATER_MARKER is observed.
+  bool   is_owner(JavaThread* thread) const { return owner() == owner_for(thread); }
+  bool   is_owner_anonymous() const { return owner_raw() == anon_owner_ptr(); }
+  bool   is_stack_locker(JavaThread* current);
+  BasicLock* stack_locker() const;
+
+ private:
   void*     owner_raw() const;
+  void*     owner_for(JavaThread* thread) 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.
   bool      is_being_async_deflated();
   // Clear _owner field; current value must match old_value.
-  void      release_clear_owner(void* old_value);
+  void      release_clear_owner(JavaThread* old_value);
   // Simply set _owner field to new_value; current value must match old_value.
-  void      set_owner_from(void* old_value, void* new_value);
+  void      set_owner_from_raw(void* old_value, void* new_value);
+  void      set_owner_from(void* old_value, JavaThread* current);
   // Simply set _owner field to current; current value must match basic_lock_p.
-  void      set_owner_from_BasicLock(void* basic_lock_p, JavaThread* current);
+  void      set_owner_from_BasicLock(JavaThread* current);
   // 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*     try_set_owner_from_raw(void* old_value, void* new_value);
+  void*     try_set_owner_from(void* old_value, JavaThread* current);
 
-  void set_owner_anonymous() {
-    set_owner_from(nullptr, anon_owner_ptr());
-  }
+  void set_stack_locker(BasicLock* locker);
 
-  bool is_owner_anonymous() const {
-    return owner_raw() == anon_owner_ptr();
+  void set_owner_anonymous() {
+    set_owner_from_raw(nullptr, anon_owner_ptr());
   }
 
-  void set_owner_from_anonymous(Thread* owner) {
+  void set_owner_from_anonymous(JavaThread* owner) {
     set_owner_from(anon_owner_ptr(), owner);
   }
 
   // Simply get _next_om field.
   ObjectMonitor* next_om() const;
   // Simply set _next_om field to new_value.
   void set_next_om(ObjectMonitor* new_value);
 
-  int       waiters() const;
-
-  int       contentions() const;
   void      add_to_contentions(int value);
   intx      recursions() const                                         { return _recursions; }
   void      set_recursions(size_t recursions);
 
+ public:
   // JVM/TI GetObjectMonitorUsage() needs this:
+  int waiters() const;
   ObjectWaiter* first_waiter()                                         { return _WaitSet; }
   ObjectWaiter* next_waiter(ObjectWaiter* o)                           { return o->_next; }
   JavaThread* thread_of_waiter(ObjectWaiter* o)                        { return o->_thread; }
 
   ObjectMonitor(oop object);
@@ -329,10 +358,11 @@
     void operator()(JavaThread* current);
   };
  public:
   bool      enter_for(JavaThread* locking_thread);
   bool      enter(JavaThread* current);
+  void      redo_enter(JavaThread* current);
   void      exit(JavaThread* current, bool not_suspended = true);
   void      wait(jlong millis, bool interruptible, TRAPS);
   void      notify(TRAPS);
   void      notifyAll(TRAPS);
 
@@ -350,11 +380,14 @@
   void      INotify(JavaThread* current);
   ObjectWaiter* DequeueWaiter();
   void      DequeueSpecificWaiter(ObjectWaiter* waiter);
   void      EnterI(JavaThread* current);
   void      ReenterI(JavaThread* current, ObjectWaiter* current_node);
-  void      UnlinkAfterAcquire(JavaThread* current, ObjectWaiter* current_node);
+  bool      HandlePreemptedVThread(JavaThread* current);
+  void      VThreadEpilog(JavaThread* current);
+  void      UnlinkAfterAcquire(JavaThread* current, ObjectWaiter* current_node, oop vthread = nullptr);
+  ObjectWaiter* LookupWaiter(int64_t threadid);
 
 
   enum class TryLockResult { Interference = -1, HasOwner = 0, Success = 1 };
 
   TryLockResult  TryLock(JavaThread* current);
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
@@ -28,25 +28,30 @@
 #include "runtime/objectMonitor.hpp"
 
 #include "logging/log.hpp"
 #include "oops/access.inline.hpp"
 #include "runtime/atomic.hpp"
+#include "runtime/javaThread.inline.hpp"
 #include "runtime/lockStack.inline.hpp"
 #include "runtime/synchronizer.hpp"
+#include "runtime/threadIdentifier.hpp"
+
+inline void* ObjectMonitor::owner_for(JavaThread* thread) const {
+  int64_t tid = thread->lock_id();
+  assert(tid >= 3 && tid < ThreadIdentifier::current(), "must be reasonable");
+  return (void*)tid;
+}
 
 inline bool ObjectMonitor::is_entered(JavaThread* current) const {
-  if (LockingMode == LM_LIGHTWEIGHT) {
-    if (is_owner_anonymous()) {
+  if (is_owner_anonymous()) {
+    if (LockingMode == LM_LIGHTWEIGHT) {
       return current->lock_stack().contains(object());
     } else {
-      return current == owner_raw();
+      return current->is_lock_owned((address)stack_locker());
     }
   } else {
-    void* owner = owner_raw();
-    if (current == owner || current->is_lock_owned((address)owner)) {
-      return true;
-    }
+    return is_owner(current);
   }
   return false;
 }
 
 inline markWord ObjectMonitor::header() const {
@@ -78,10 +83,22 @@
 
 inline void* ObjectMonitor::owner_raw() const {
   return Atomic::load(&_owner);
 }
 
+inline BasicLock* ObjectMonitor::stack_locker() const {
+  return Atomic::load(&_stack_locker);
+}
+
+inline void ObjectMonitor::set_stack_locker(BasicLock* locker) {
+  Atomic::store(&_stack_locker, locker);
+}
+
+inline bool ObjectMonitor::is_stack_locker(JavaThread* current) {
+  return is_owner_anonymous() && current->is_lock_owned((address)stack_locker());
+}
+
 // Returns true if owner field == DEFLATER_MARKER and false otherwise.
 // This accessor is called when we really need to know if the owner
 // field == DEFLATER_MARKER and any non-null value won't do the trick.
 inline bool ObjectMonitor::owner_is_DEFLATER_MARKER() const {
   return owner_raw() == DEFLATER_MARKER;
@@ -107,11 +124,12 @@
   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) {
+inline void ObjectMonitor::release_clear_owner(JavaThread* old_owner) {
+  void* old_value = owner_for(old_owner);
 #ifdef ASSERT
   void* prev = Atomic::load(&_owner);
   assert(prev == old_value, "unexpected prev owner=" INTPTR_FORMAT
          ", expected=" INTPTR_FORMAT, p2i(prev), p2i(old_value));
 #endif
@@ -121,53 +139,63 @@
                                      p2i(this), p2i(old_value));
 }
 
 // Simply set _owner field to new_value; current value must match old_value.
 // (Simple means no memory sync needed.)
-inline void ObjectMonitor::set_owner_from(void* old_value, void* new_value) {
+inline void ObjectMonitor::set_owner_from_raw(void* old_value, void* new_value) {
 #ifdef ASSERT
   void* prev = Atomic::load(&_owner);
+  assert((int64_t)prev < ThreadIdentifier::current(), "must be reasonable");
   assert(prev == old_value, "unexpected prev owner=" INTPTR_FORMAT
          ", expected=" INTPTR_FORMAT, p2i(prev), p2i(old_value));
 #endif
   Atomic::store(&_owner, new_value);
   log_trace(monitorinflation, owner)("set_owner_from(): mid="
                                      INTPTR_FORMAT ", old_value=" INTPTR_FORMAT
                                      ", new_value=" INTPTR_FORMAT, p2i(this),
                                      p2i(old_value), p2i(new_value));
 }
 
+inline void ObjectMonitor::set_owner_from(void* old_value, JavaThread* current) {
+  set_owner_from_raw(old_value, owner_for(current));
+}
+
 // Simply set _owner field to self; current value must match basic_lock_p.
-inline void ObjectMonitor::set_owner_from_BasicLock(void* basic_lock_p, JavaThread* current) {
-#ifdef ASSERT
-  void* prev = Atomic::load(&_owner);
-  assert(prev == basic_lock_p, "unexpected prev owner=" INTPTR_FORMAT
-         ", expected=" INTPTR_FORMAT, p2i(prev), p2i(basic_lock_p));
-#endif
+inline void ObjectMonitor::set_owner_from_BasicLock(JavaThread* current) {
+  BasicLock* basic_lock_p = stack_locker();
+
+  set_stack_locker(nullptr); // first
+  assert(is_owner_anonymous(), "should be anon for now");
+
   // Non-null owner field to non-null owner field is safe without
   // cmpxchg() as long as all readers can tolerate either flavor.
-  Atomic::store(&_owner, current);
+  Atomic::store(&_owner, owner_for(current));
   log_trace(monitorinflation, owner)("set_owner_from_BasicLock(): mid="
                                      INTPTR_FORMAT ", basic_lock_p="
                                      INTPTR_FORMAT ", new_value=" INTPTR_FORMAT,
                                      p2i(this), p2i(basic_lock_p), p2i(current));
 }
 
 // Try to set _owner field to new_value if the current value matches
 // old_value. Otherwise, does not change the _owner field. Returns
 // the prior value of the _owner field.
-inline void* ObjectMonitor::try_set_owner_from(void* old_value, void* new_value) {
+inline void* ObjectMonitor::try_set_owner_from_raw(void* old_value, void* new_value) {
+  assert((int64_t)new_value < ThreadIdentifier::current(), "must be reasonable");
   void* prev = Atomic::cmpxchg(&_owner, old_value, new_value);
   if (prev == old_value) {
     log_trace(monitorinflation, owner)("try_set_owner_from(): mid="
                                        INTPTR_FORMAT ", prev=" INTPTR_FORMAT
                                        ", new=" INTPTR_FORMAT, p2i(this),
                                        p2i(prev), p2i(new_value));
   }
   return prev;
 }
 
+inline void* ObjectMonitor::try_set_owner_from(void* old_value, JavaThread* current) {
+  return try_set_owner_from_raw(old_value, owner_for(current));
+}
+
 // The _next_om field can be concurrently read and modified so we
 // use Atomic operations to disable compiler optimizations that
 // might try to elide loading and/or storing this field.
 
 // Simply get _next_om field.
diff a/src/hotspot/share/runtime/osThread.hpp b/src/hotspot/share/runtime/osThread.hpp
--- a/src/hotspot/share/runtime/osThread.hpp
+++ b/src/hotspot/share/runtime/osThread.hpp
@@ -26,11 +26,10 @@
 #define SHARE_RUNTIME_OSTHREAD_HPP
 
 #include "runtime/frame.hpp"
 #include "runtime/handles.hpp"
 #include "runtime/javaFrameAnchor.hpp"
-#include "runtime/objectMonitor.hpp"
 #include "runtime/suspendedThreadTask.hpp"
 #include "utilities/macros.hpp"
 
 #if defined(LINUX) || defined(AIX) || defined(BSD)
 #include "suspendResume_posix.hpp"
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
@@ -61,10 +61,11 @@
 #include "runtime/init.hpp"
 #include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/java.hpp"
 #include "runtime/javaCalls.hpp"
 #include "runtime/jniHandles.inline.hpp"
+#include "runtime/objectMonitor.inline.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "runtime/stackWatermarkSet.hpp"
 #include "runtime/stubRoutines.hpp"
 #include "runtime/synchronizer.hpp"
 #include "runtime/vframe.inline.hpp"
@@ -1896,20 +1897,29 @@
   // would leave you with the lock held and it would never be released.
   // The normal monitorenter NullPointerException is thrown without acquiring a lock
   // and the model is that an exception implies the method failed.
   JRT_BLOCK_NO_ASYNC
   Handle h_obj(THREAD, obj);
+  ThreadOnMonitorEnter tme(current);
   ObjectSynchronizer::enter(h_obj, lock, current);
   assert(!HAS_PENDING_EXCEPTION, "Should have no exception here");
   JRT_BLOCK_END
 }
 
 // Handles the uncommon case in locking, i.e., contention or an inflated lock.
 JRT_BLOCK_ENTRY(void, SharedRuntime::complete_monitor_locking_C(oopDesc* obj, BasicLock* lock, JavaThread* current))
   SharedRuntime::monitor_enter_helper(obj, lock, current);
 JRT_END
 
+JRT_LEAF(void, SharedRuntime::redo_monitorenter(JavaThread* current, ObjectMonitor* monitor))
+  assert(current == JavaThread::current(), "invariant");
+  assert(!monitor->is_owner(current), "invariant");
+
+  monitor->redo_enter(current);
+  assert(monitor->is_owner(current) || current->preempting(), "invariant");
+JRT_END
+
 void SharedRuntime::monitor_exit_helper(oopDesc* obj, BasicLock* lock, JavaThread* current) {
   assert(JavaThread::current() == current, "invariant");
   // Exit must be non-blocking, and therefore no exceptions can be thrown.
   ExceptionMark em(current);
   // The object could become unlocked through a JNI call, which we have no other checks for.
diff a/src/hotspot/share/runtime/sharedRuntime.hpp b/src/hotspot/share/runtime/sharedRuntime.hpp
--- a/src/hotspot/share/runtime/sharedRuntime.hpp
+++ b/src/hotspot/share/runtime/sharedRuntime.hpp
@@ -464,10 +464,14 @@
   // Stack slots that may be unused by the calling convention but must
   // otherwise be preserved.  On Intel this includes the return address.
   // On PowerPC it includes the 4 words holding the old TOC & LR glue.
   static uint in_preserve_stack_slots();
 
+  static VMReg thread_register();
+
+  static void continuation_enter_cleanup(MacroAssembler* masm);
+
   // Is vector's size (in bytes) bigger than a size saved by default?
   // For example, on x86 16 bytes XMM registers are saved by default.
   static bool is_wide_vector(int size);
 
   // Save and restore a native result
@@ -495,10 +499,11 @@
   static bool should_fixup_call_destination(address destination, address entry_point, address caller_pc, Method* moop, CodeBlob* cb);
 
   // Slow-path Locking and Unlocking
   static void complete_monitor_locking_C(oopDesc* obj, BasicLock* lock, JavaThread* current);
   static void complete_monitor_unlocking_C(oopDesc* obj, BasicLock* lock, JavaThread* current);
+  static void redo_monitorenter(JavaThread* current, ObjectMonitor* monitor);
 
   // Resolving of calls
   static address get_resolved_entry        (JavaThread* current, methodHandle callee_method);
   static address resolve_static_call_C     (JavaThread* current);
   static address resolve_virtual_call_C    (JavaThread* current);
diff a/src/hotspot/share/runtime/stackChunkFrameStream.hpp b/src/hotspot/share/runtime/stackChunkFrameStream.hpp
--- a/src/hotspot/share/runtime/stackChunkFrameStream.hpp
+++ b/src/hotspot/share/runtime/stackChunkFrameStream.hpp
@@ -73,10 +73,11 @@
 
   inline bool is_interpreted() const;
   inline bool is_stub() const;
   inline bool is_compiled() const;
   CodeBlob* cb() const { return _cb; }
+  inline void get_cb();
   const ImmutableOopMap* oopmap() const { if (_oopmap == nullptr) get_oopmap(); return _oopmap; }
   inline int frame_size() const;
   inline int stack_argsize() const;
   inline int num_oops() const;
 
@@ -96,11 +97,10 @@
 
   void print_on(outputStream* st) const PRODUCT_RETURN;
 
  private:
   inline address get_pc() const;
-  inline void get_cb();
 
   inline int interpreter_frame_size() const;
   inline int interpreter_frame_num_oops() const;
   inline int interpreter_frame_stack_argsize() const;
   inline void next_for_interpreter_frame();
diff a/src/hotspot/share/runtime/stackChunkFrameStream.inline.hpp b/src/hotspot/share/runtime/stackChunkFrameStream.inline.hpp
--- a/src/hotspot/share/runtime/stackChunkFrameStream.inline.hpp
+++ b/src/hotspot/share/runtime/stackChunkFrameStream.inline.hpp
@@ -104,11 +104,11 @@
   } DEBUG_ONLY(else _has_stub = false;)
 }
 
 template <ChunkFrames frame_kind>
 inline bool StackChunkFrameStream<frame_kind>::is_stub() const {
-  return cb() != nullptr && (_cb->is_safepoint_stub() || _cb->is_runtime_stub());
+  return cb() != nullptr && _cb->is_runtime_stub();
 }
 
 template <ChunkFrames frame_kind>
 inline bool StackChunkFrameStream<frame_kind>::is_compiled() const {
   return cb() != nullptr && _cb->is_nmethod();
@@ -194,11 +194,18 @@
   return (cb()->as_nmethod()->num_stack_arg_slots() * VMRegImpl::stack_slot_size) >> LogBytesPerWord;
 }
 
 template <ChunkFrames frame_kind>
 inline int StackChunkFrameStream<frame_kind>::num_oops() const {
-  return is_interpreted() ? interpreter_frame_num_oops() : oopmap()->num_oops();
+  if (is_interpreted()) {
+    return interpreter_frame_num_oops();
+  } else if (is_compiled()) {
+    return oopmap()->num_oops();
+  } else {
+    assert(is_stub(), "invariant");
+    return 0;
+  }
 }
 
 template <ChunkFrames frame_kind>
 inline void StackChunkFrameStream<frame_kind>::initialize_register_map(RegisterMap* map) {
   update_reg_map_pd(map);
@@ -231,11 +238,11 @@
   }
 
   get_cb();
   update_reg_map_pd(map);
   if (safepoint && cb() != nullptr) { // there's no post-call nop and no fast oopmap lookup
-    _oopmap = cb()->oop_map_for_return_address(pc());
+    _oopmap = cb()->oop_map_for_return_address(orig_pc());
   }
 }
 
 template <ChunkFrames frame_kind>
 inline void StackChunkFrameStream<frame_kind>::get_cb() {
@@ -298,13 +305,12 @@
 }
 
 template<>
 template<>
 inline void StackChunkFrameStream<ChunkFrames::CompiledOnly>::update_reg_map(RegisterMap* map) {
-  assert(map->in_cont(), "");
-  assert(map->stack_chunk()() == _chunk, "");
-  if (map->update_map()) {
+  assert(!map->in_cont() || map->stack_chunk() == _chunk, "");
+  if (map->update_map() && is_stub()) {
     frame f = to_frame();
     oopmap()->update_register_map(&f, map); // we have callee-save registers in this case
   }
 }
 
diff a/src/hotspot/share/runtime/stackValue.cpp b/src/hotspot/share/runtime/stackValue.cpp
--- a/src/hotspot/share/runtime/stackValue.cpp
+++ b/src/hotspot/share/runtime/stackValue.cpp
@@ -291,22 +291,22 @@
 
   assert(value_addr == nullptr || Continuation::is_in_usable_stack(value_addr, reg_map->as_RegisterMap()) || (reg_map->thread() != nullptr && reg_map->thread()->is_in_usable_stack(value_addr)), INTPTR_FORMAT, p2i(value_addr));
   return value_addr;
 }
 
-BasicLock* StackValue::resolve_monitor_lock(const frame* fr, Location location) {
+BasicLock* StackValue::resolve_monitor_lock(const frame& fr, Location location) {
   assert(location.is_stack(), "for now we only look at the stack");
   int word_offset = location.stack_offset() / wordSize;
   // (stack picture)
   // high: [     ]  word_offset + 1
   // low   [     ]  word_offset
   //
   // sp->  [     ]  0
   // the word_offset is the distance from the stack pointer to the lowest address
   // The frame's original stack pointer, before any extension by its callee
   // (due to Compiler1 linkage on SPARC), must be used.
-  return (BasicLock*) (fr->unextended_sp() + word_offset);
+  return (BasicLock*) (fr.unextended_sp() + word_offset);
 }
 
 
 #ifndef PRODUCT
 
diff a/src/hotspot/share/runtime/stackValue.hpp b/src/hotspot/share/runtime/stackValue.hpp
--- a/src/hotspot/share/runtime/stackValue.hpp
+++ b/src/hotspot/share/runtime/stackValue.hpp
@@ -121,11 +121,11 @@
   }
 
   static StackValue* create_stack_value_from_oop_location(stackChunkOop chunk, void* addr);
   static StackValue* create_stack_value_from_narrowOop_location(stackChunkOop chunk, void* addr, bool is_register);
 
-  static BasicLock*  resolve_monitor_lock(const frame* fr, Location location);
+  static BasicLock*  resolve_monitor_lock(const frame& fr, Location location);
 
   template<typename RegisterMapT>
   static StackValue* create_stack_value(const frame* fr, const RegisterMapT* reg_map, ScopeValue* sv);
 
   template<typename RegisterMapT>
diff a/src/hotspot/share/runtime/stubRoutines.cpp b/src/hotspot/share/runtime/stubRoutines.cpp
--- a/src/hotspot/share/runtime/stubRoutines.cpp
+++ b/src/hotspot/share/runtime/stubRoutines.cpp
@@ -184,10 +184,13 @@
 address StubRoutines::_array_partition  = nullptr;
 
 address StubRoutines::_cont_thaw          = nullptr;
 address StubRoutines::_cont_returnBarrier = nullptr;
 address StubRoutines::_cont_returnBarrierExc = nullptr;
+address StubRoutines::_cont_preempt_stub = nullptr;
+address StubRoutines::_cont_preempt_monitorenter_redo = nullptr;
+address StubRoutines::_cont_preempt_rerun_compiler_adapter = nullptr;
 
 JFR_ONLY(RuntimeStub* StubRoutines::_jfr_write_checkpoint_stub = nullptr;)
 JFR_ONLY(address StubRoutines::_jfr_write_checkpoint = nullptr;)
 JFR_ONLY(RuntimeStub* StubRoutines::_jfr_return_lease_stub = nullptr;)
 JFR_ONLY(address StubRoutines::_jfr_return_lease = nullptr;)
diff a/src/hotspot/share/runtime/stubRoutines.hpp b/src/hotspot/share/runtime/stubRoutines.hpp
--- a/src/hotspot/share/runtime/stubRoutines.hpp
+++ b/src/hotspot/share/runtime/stubRoutines.hpp
@@ -262,10 +262,13 @@
   static address _method_entry_barrier;
 
   static address _cont_thaw;
   static address _cont_returnBarrier;
   static address _cont_returnBarrierExc;
+  static address _cont_preempt_stub;
+  static address _cont_preempt_monitorenter_redo;
+  static address _cont_preempt_rerun_compiler_adapter;
 
   JFR_ONLY(static RuntimeStub* _jfr_write_checkpoint_stub;)
   JFR_ONLY(static address _jfr_write_checkpoint;)
   JFR_ONLY(static RuntimeStub* _jfr_return_lease_stub;)
   JFR_ONLY(static address _jfr_return_lease;)
@@ -478,10 +481,13 @@
   static address method_entry_barrier() { return _method_entry_barrier; }
 
   static address cont_thaw()           { return _cont_thaw; }
   static address cont_returnBarrier()  { return _cont_returnBarrier; }
   static address cont_returnBarrierExc(){return _cont_returnBarrierExc; }
+  static address cont_preempt_stub()   { return _cont_preempt_stub; }
+  static address cont_preempt_monitorenter_redo()   { return _cont_preempt_monitorenter_redo; }
+  static address cont_preempt_rerun_compiler_adapter() { return _cont_preempt_rerun_compiler_adapter; }
 
   JFR_ONLY(static address jfr_write_checkpoint() { return _jfr_write_checkpoint; })
   JFR_ONLY(static address jfr_return_lease() { return _jfr_return_lease; })
 
   static address upcall_stub_exception_handler() {
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
@@ -349,11 +349,11 @@
   }
 
   if (mark.has_monitor()) {
     ObjectMonitor* const mon = mark.monitor();
     assert(mon->object() == oop(obj), "invariant");
-    if (mon->owner() != current) return false;  // slow-path for IMS exception
+    if (!mon->is_owner(current)) return false;  // slow-path for IMS exception
 
     if (mon->first_waiter() != nullptr) {
       // We have one or more waiters. Since this is an inflated monitor
       // that we own, we can transfer one or more threads from the waitset
       // to the entrylist here and now, avoiding the slow-path.
@@ -399,11 +399,11 @@
       // Always go into runtime if the lock stack is full.
       return false;
     }
     if (lock_stack.try_recursive_enter(obj)) {
       // Recursive lock successful.
-      current->inc_held_monitor_count();
+      NOT_LOOM_MONITOR_SUPPORT(current->inc_held_monitor_count();)
       return true;
     }
   }
 
   const markWord mark = obj->mark();
@@ -414,20 +414,19 @@
     // the ObjectMonitor busy by setting the owner below. If we detect
     // that race we just bail out to the slow-path here.
     if (m->object_peek() == nullptr) {
       return false;
     }
-    JavaThread* const owner = static_cast<JavaThread*>(m->owner_raw());
 
     // Lock contention and Transactional Lock Elision (TLE) diagnostics
     // and observability
     // Case: light contention possibly amenable to TLE
     // Case: TLE inimical operations such as nested/recursive synchronization
 
-    if (owner == current) {
+    if (m->is_owner(current)) {
       m->_recursions++;
-      current->inc_held_monitor_count();
+      NOT_LOOM_MONITOR_SUPPORT(current->inc_held_monitor_count();)
       return true;
     }
 
     if (LockingMode != LM_LIGHTWEIGHT) {
       // This Java Monitor is inflated so obj's header will never be
@@ -440,13 +439,13 @@
       // 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 == nullptr && m->try_set_owner_from(nullptr, current) == nullptr) {
+    if (!m->has_owner() && m->try_set_owner_from(nullptr, current) == nullptr) {
       assert(m->_recursions == 0, "invariant");
-      current->inc_held_monitor_count();
+      NOT_LOOM_MONITOR_SUPPORT(current->inc_held_monitor_count();)
       return true;
     }
   }
 
   // Note that we could inflate in quick_enter.
@@ -522,10 +521,13 @@
 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");
+
+  NOT_LOOM_MONITOR_SUPPORT(locking_thread->inc_held_monitor_count();)
+
   if (!enter_fast_impl(obj, lock, locking_thread)) {
     // Inflated ObjectMonitor::enter_for is required
 
     // An async deflation can race after the inflate_for() call and before
     // enter_for() can make the ObjectMonitor busy. enter_for() returns false
@@ -540,10 +542,13 @@
   }
 }
 
 void ObjectSynchronizer::enter(Handle obj, BasicLock* lock, JavaThread* current) {
   assert(current == Thread::current(), "must be");
+
+  NOT_LOOM_MONITOR_SUPPORT(current->inc_held_monitor_count();)
+
   if (!enter_fast_impl(obj, lock, current)) {
     // Inflated ObjectMonitor::enter is required
 
     // An async deflation can race after the inflate() call and before
     // enter() can make the ObjectMonitor busy. enter() returns false if
@@ -559,17 +564,14 @@
 
 // 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) {
-
   if (obj->klass()->is_value_based()) {
     handle_sync_on_value_based_class(obj, locking_thread);
   }
 
-  locking_thread->inc_held_monitor_count();
-
   if (!useHeavyMonitors()) {
     if (LockingMode == LM_LIGHTWEIGHT) {
       // Fast-locking does not use the 'lock' argument.
       LockStack& lock_stack = locking_thread->lock_stack();
       if (lock_stack.is_full()) {
@@ -594,11 +596,11 @@
         // 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,
+        assert(monitor->is_owner(JavaThread::current()), "must be owner=" PTR_FORMAT " current=" PTR_FORMAT " mark=" PTR_FORMAT,
                p2i(monitor->owner()), p2i(Thread::current()), monitor->object()->mark_acquire().value());
         assert(!lock_stack.is_full(), "must have made room here");
       }
 
       markWord mark = obj()->mark_acquire();
@@ -628,10 +630,11 @@
       if (mark.is_unlocked()) {
         // Anticipate successful CAS -- the ST of the displaced mark must
         // be visible <= the ST performed by the CAS.
         lock->set_displaced_header(mark);
         if (mark == obj()->cas_set_mark(markWord::from_pointer(lock), mark)) {
+          LOOM_MONITOR_SUPPORT_ONLY(locking_thread->inc_held_monitor_count();)
           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");
@@ -655,11 +658,11 @@
 
   return false;
 }
 
 void ObjectSynchronizer::exit(oop object, BasicLock* lock, JavaThread* current) {
-  current->dec_held_monitor_count();
+  NOT_LOOM_MONITOR_SUPPORT(current->dec_held_monitor_count();)
 
   if (!useHeavyMonitors()) {
     markWord mark = object->mark();
     if (LockingMode == LM_LIGHTWEIGHT) {
       // Fast-locking does not use the 'lock' argument.
@@ -720,10 +723,11 @@
       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) {
+          LOOM_MONITOR_SUPPORT_ONLY(current->dec_held_monitor_count();)
           return;
         }
       }
     }
   } else if (VerifyHeavyMonitors) {
@@ -781,18 +785,20 @@
 // Internal VM locks on java objects
 // standard constructor, allows locking failures
 ObjectLocker::ObjectLocker(Handle obj, JavaThread* thread) {
   _thread = thread;
   _thread->check_for_valid_safepoint_state();
+  DEBUG_ONLY(_thread->inc_obj_locker_count();)
   _obj = obj;
 
   if (_obj() != nullptr) {
     ObjectSynchronizer::enter(_obj, &_lock, _thread);
   }
 }
 
 ObjectLocker::~ObjectLocker() {
+  DEBUG_ONLY(_thread->dec_obj_locker_count();)
   if (_obj() != nullptr) {
     ObjectSynchronizer::exit(_obj(), &_lock, _thread);
   }
 }
 
@@ -1148,11 +1154,11 @@
   markWord mark = read_stable_mark(obj);
 
   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());
+    return Threads::owning_thread_from_stacklock(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:
@@ -1198,28 +1204,28 @@
     // target thread is suspended or when the target thread is
     // operating on itself. The current closures in use today are
     // only interested in an owned ObjectMonitor and ownership
     // cannot be dropped under the calling contexts so the
     // ObjectMonitor cannot be async deflated.
-    if (monitor->has_owner() && filter(monitor->owner_raw())) {
+    if (monitor->has_owner() && filter(monitor)) {
       assert(!monitor->is_being_async_deflated(), "Owned monitors should not be deflating");
 
       closure->do_monitor(monitor);
     }
   });
 }
 
 // Iterate ObjectMonitors where the owner == thread; this does NOT include
 // ObjectMonitors where owner is set to a stack-lock address in thread.
 void ObjectSynchronizer::owned_monitors_iterate(MonitorClosure* closure, JavaThread* thread) {
-  auto thread_filter = [&](void* owner) { return owner == thread; };
+  auto thread_filter = [&](ObjectMonitor* monitor) { return monitor->is_owner(thread); };
   return owned_monitors_iterate_filtered(closure, thread_filter);
 }
 
 // Iterate ObjectMonitors owned by any thread.
 void ObjectSynchronizer::owned_monitors_iterate(MonitorClosure* closure) {
-  auto all_filter = [&](void* owner) { return true; };
+  auto all_filter = [&](ObjectMonitor* monitor) { return true; };
   return owned_monitors_iterate_filtered(closure, all_filter);
 }
 
 static bool monitors_used_above_threshold(MonitorList* list) {
   if (MonitorUsedDeflationThreshold == 0) {  // disabled case is easy
@@ -1385,41 +1391,35 @@
   (void)inflate(Thread::current(), obj, inflate_cause_vm_internal);
 }
 
 ObjectMonitor* ObjectSynchronizer::inflate(Thread* current, oop obj, const InflateCause cause) {
   assert(current == Thread::current(), "must be");
-  if (LockingMode == LM_LIGHTWEIGHT && current->is_Java_thread()) {
-    return inflate_impl(JavaThread::cast(current), obj, cause);
-  }
-  return inflate_impl(nullptr, obj, cause);
+  return inflate_impl(current->is_Java_thread() ? JavaThread::cast(current) : 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,
+  // The JavaThread* inflating_thread requires that the inflating_thread == Thread::current() or
+  // is suspended throughout the call by some other mechanism.
+  // 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 correctness that the
+  // thread is set when called from ObjectSynchronizer::enter from the owning thread,
   // ObjectSynchronizer::enter_for from any thread, or ObjectSynchronizer::exit.
   EventJavaMonitorInflate event;
 
   for (;;) {
     const markWord mark = object->mark_acquire();
 
     // The mark can be in one of the following states:
-    // *  inflated     - Just return if using stack-locking.
-    //                   If using fast-locking and the ObjectMonitor owner
-    //                   is anonymous and the inflating_thread owns the
-    //                   object lock, then we make the inflating_thread
-    //                   the ObjectMonitor owner and remove the lock from
+    // *  inflated     - If the ObjectMonitor owner is anonymous and the
+    //                   inflating_thread owns the object lock, then we
+    //                   make the inflating_thread the ObjectMonitor owner.
+    //                   For LM_LIGHTWEIGHT we also remove the lock from
     //                   the inflating_thread's lock stack.
     // *  fast-locked  - Coerce it to inflated from fast-locked.
     // *  stack-locked - Coerce it to inflated from stack-locked.
     // *  INFLATING    - Busy wait for conversion from stack-locked to
     //                   inflated.
@@ -1428,15 +1428,25 @@
     // 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);
+      if (inf->is_owner_anonymous() && inflating_thread != nullptr) {
+        if (LockingMode == LM_LIGHTWEIGHT) {
+          if (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);
+          }
+        } else {
+          assert(LockingMode == LM_LEGACY, "invariant");
+          if (inflating_thread->is_lock_owned((address)inf->stack_locker())) {
+            inf->set_owner_from_BasicLock(inflating_thread);
+            // Decrement monitor count now since this monitor is okay for freezing
+            LOOM_MONITOR_SUPPORT_ONLY(inflating_thread->dec_held_monitor_count();)
+          }
+        }
       }
       return inf;
     }
 
     if (LockingMode != LM_LIGHTWEIGHT) {
@@ -1566,16 +1576,23 @@
       assert(dmw.is_neutral(), "invariant: header=" INTPTR_FORMAT, dmw.value());
 
       // Setup monitor fields to proper values -- prepare the monitor
       m->set_header(dmw);
 
-      // Optimization: if the mark.locker stack address is associated
-      // with this thread we could simply set m->_owner = current.
       // Note that a thread can inflate an object
       // that it has stack-locked -- as might happen in wait() -- directly
       // with CAS.  That is, we can avoid the xchg-nullptr .... ST idiom.
-      m->set_owner_from(nullptr, mark.locker());
+      if (inflating_thread != nullptr && inflating_thread->is_lock_owned((address)mark.locker())) {
+        m->set_owner_from(nullptr, inflating_thread);
+        // Decrement monitor count now since this monitor is okay for freezing
+        LOOM_MONITOR_SUPPORT_ONLY(inflating_thread->dec_held_monitor_count();)
+      } else {
+        // Use ANONYMOUS_OWNER to indicate that the owner is the BasicLock on the stack,
+        // and set the stack locker field in the monitor.
+        m->set_stack_locker(mark.locker());
+        m->set_owner_anonymous();  // second
+      }
       // TODO-FIXME: assert BasicLock->dhw != 0.
 
       // Must preserve store ordering. The monitor state must
       // be stable at the time of publishing the monitor address.
       guarantee(object->mark() == markWord::INFLATING(), "invariant");
@@ -1869,11 +1886,11 @@
 
  public:
   ReleaseJavaMonitorsClosure(JavaThread* thread) : _thread(thread) {}
   void do_monitor(ObjectMonitor* mid) {
     intx rec = mid->complete_exit(_thread);
-    _thread->dec_held_monitor_count(rec + 1);
+    _thread->dec_held_monitor_count(NOT_LOOM_MONITOR_SUPPORT((rec + 1)));
   }
 };
 
 // Release all inflated monitors owned by current thread.  Lightweight monitors are
 // ignored.  This is meant to be called during JNI thread detach which assumes
@@ -1909,10 +1926,11 @@
     case inflate_cause_wait:           return "Monitor Wait";
     case inflate_cause_notify:         return "Monitor Notify";
     case inflate_cause_hash_code:      return "Monitor Hash Code";
     case inflate_cause_jni_enter:      return "JNI Monitor Enter";
     case inflate_cause_jni_exit:       return "JNI Monitor Exit";
+    case inflate_cause_cont_freeze:    return "Continuation Freeze";
     default:
       ShouldNotReachHere();
   }
   return "Unknown";
 }
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
@@ -77,11 +77,12 @@
     inflate_cause_wait = 2,
     inflate_cause_notify = 3,
     inflate_cause_hash_code = 4,
     inflate_cause_jni_enter = 5,
     inflate_cause_jni_exit = 6,
-    inflate_cause_nof = 7 // Number of causes
+    inflate_cause_cont_freeze = 7,
+    inflate_cause_nof = 8 // Number of causes
   } InflateCause;
 
   typedef enum {
     NOT_ENABLED    = 0,
     FATAL_EXIT     = 1,
diff a/src/hotspot/share/runtime/threadIdentifier.cpp b/src/hotspot/share/runtime/threadIdentifier.cpp
--- a/src/hotspot/share/runtime/threadIdentifier.cpp
+++ b/src/hotspot/share/runtime/threadIdentifier.cpp
@@ -24,16 +24,28 @@
 
 #include "precompiled.hpp"
 #include "runtime/atomic.hpp"
 #include "runtime/threadIdentifier.hpp"
 
-static volatile int64_t next_thread_id = 2; // starting at 2, excluding the primordial thread id
+// starting at 3, excluding reserved values defined in ObjectMonitor.hpp
+static const int64_t INITIAL_TID = 3;
+static volatile int64_t next_thread_id = INITIAL_TID;
+
+#ifdef ASSERT
+int64_t ThreadIdentifier::initial() {
+  return INITIAL_TID;
+}
+#endif
 
 int64_t ThreadIdentifier::unsafe_offset() {
   return reinterpret_cast<int64_t>(&next_thread_id);
 }
 
+int64_t ThreadIdentifier::current() {
+  return Atomic::load(&next_thread_id);
+}
+
 int64_t ThreadIdentifier::next() {
   int64_t next_tid;
   do {
     next_tid = Atomic::load(&next_thread_id);
   } while (Atomic::cmpxchg(&next_thread_id, next_tid, next_tid + 1) != next_tid);
diff a/src/hotspot/share/runtime/threadIdentifier.hpp b/src/hotspot/share/runtime/threadIdentifier.hpp
--- a/src/hotspot/share/runtime/threadIdentifier.hpp
+++ b/src/hotspot/share/runtime/threadIdentifier.hpp
@@ -34,9 +34,11 @@
  * JFR uses next() for a non-reusable id for non-java threads.
  */
 class ThreadIdentifier : AllStatic {
  public:
   static int64_t next();
+  static int64_t current();
   static int64_t unsafe_offset();
+  DEBUG_ONLY(static int64_t initial();)
 };
 
 #endif // SHARE_RUNTIME_THREADIDENTIFIER_HPP
diff a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp
--- a/src/hotspot/share/runtime/threads.cpp
+++ b/src/hotspot/share/runtime/threads.cpp
@@ -166,10 +166,12 @@
                           vmSymbols::threadgroup_string_void_signature(),
                           thread_group,
                           string,
                           CHECK);
 
+  assert(thread->lock_id() == ThreadIdentifier::initial(), "invariant");
+
   // Set thread status to running since main thread has
   // been started and running.
   java_lang_Thread::set_thread_status(thread_oop(),
                                       JavaThreadStatus::RUNNABLE);
 }
@@ -531,10 +533,15 @@
   main_thread->record_stack_base_and_size();
   main_thread->register_thread_stack_with_NMT();
   main_thread->set_active_handles(JNIHandleBlock::allocate_block());
   MACOS_AARCH64_ONLY(main_thread->init_wx());
 
+  // Set the lock_id now since we will run Java code before the Thread instance
+  // is even created. The same value will be assigned to the Thread instance on init.
+  main_thread->set_lock_id(ThreadIdentifier::next());
+  assert(main_thread->lock_id() == ThreadIdentifier::initial(), "invariant");
+
   if (!main_thread->set_as_starting_thread()) {
     vm_shutdown_during_initialization(
                                       "Failed necessary internal allocation. Out of swap space");
     main_thread->smr_delete();
     *canTryAgain = false; // don't let caller call JNI_CreateJavaVM again
@@ -580,10 +587,12 @@
     }
     *canTryAgain = false; // don't let caller call JNI_CreateJavaVM again
     return status;
   }
 
+  ObjectMonitor::Initialize2();
+
   JFR_ONLY(Jfr::on_create_vm_1();)
 
   // Should be done after the heap is fully created
   main_thread->cache_global_variables();
 
@@ -1212,39 +1221,20 @@
 
   return result;
 }
 #endif // INCLUDE_JVMTI
 
-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 == nullptr) return nullptr;
-
-  for (JavaThread* p : *t_list) {
-    // first, see if owner is the address of a Java thread
-    if (owner == (address)p) return p;
-  }
-
-  // Cannot assert on lack of success here since this function may be
-  // used by code that is trying to report useful problem information
-  // like deadlock detection.
-  if (LockingMode == LM_MONITOR) return nullptr;
+JavaThread *Threads::owning_thread_from_stacklock(ThreadsList * t_list, address basicLock) {
+  assert(LockingMode == LM_LEGACY, "Not with new lightweight locking");
 
-  // If we didn't find a matching Java thread and we didn't force use of
-  // heavyweight monitors, then the owner is the stack address of the
-  // Lock Word in the owning Java thread's stack.
-  //
   JavaThread* the_owner = nullptr;
   for (JavaThread* q : *t_list) {
-    if (q->is_lock_owned(owner)) {
+    if (q->is_lock_owned(basicLock)) {
       the_owner = q;
       break;
     }
   }
-
-  // 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");
@@ -1261,21 +1251,27 @@
   }
   return nullptr;
 }
 
 JavaThread* Threads::owning_thread_from_monitor(ThreadsList* t_list, ObjectMonitor* monitor) {
-  if (LockingMode == LM_LIGHTWEIGHT) {
-    if (monitor->is_owner_anonymous()) {
+  if (monitor->is_owner_anonymous()) {
+    if (LockingMode == LM_LIGHTWEIGHT) {
       return owning_thread_from_object(t_list, monitor->object());
     } else {
-      Thread* owner = reinterpret_cast<Thread*>(monitor->owner());
-      assert(owner == nullptr || owner->is_Java_thread(), "only JavaThreads own monitors");
-      return reinterpret_cast<JavaThread*>(owner);
+      assert(LockingMode == LM_LEGACY, "invariant");
+      return owning_thread_from_stacklock(t_list, (address)monitor->stack_locker());
     }
   } else {
-    address owner = (address)monitor->owner();
-    return owning_thread_from_monitor_owner(t_list, owner);
+    JavaThread* the_owner = nullptr;
+    int64_t owner_tid = (int64_t)monitor->owner();
+    for (JavaThread* q : *t_list) {
+      if (monitor->is_owner(q)) {
+        the_owner = q;
+        break;
+      }
+    }
+    return the_owner;
   }
 }
 
 class PrintOnClosure : public ThreadClosure {
 private:
diff a/src/hotspot/share/runtime/threads.hpp b/src/hotspot/share/runtime/threads.hpp
--- a/src/hotspot/share/runtime/threads.hpp
+++ b/src/hotspot/share/runtime/threads.hpp
@@ -131,13 +131,12 @@
 
   // Get count of Java threads that are waiting to enter or re-enter the specified monitor.
   static GrowableArray<JavaThread*>* get_pending_threads(ThreadsList * t_list,
                                                          int count, address monitor);
 
-  // Get owning Java thread from the monitor's owner field.
-  static JavaThread *owning_thread_from_monitor_owner(ThreadsList * t_list,
-                                                      address owner);
+  // Get owning Java thread from the basicLock address.
+  static JavaThread *owning_thread_from_stacklock(ThreadsList * t_list, address basicLock);
 
   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
diff a/src/hotspot/share/runtime/vframe.cpp b/src/hotspot/share/runtime/vframe.cpp
--- a/src/hotspot/share/runtime/vframe.cpp
+++ b/src/hotspot/share/runtime/vframe.cpp
@@ -274,17 +274,18 @@
   assert(fr().is_interpreted_frame(), "frame should be an interpreted frame");
   return fr().interpreter_frame_local_at(offset);
 }
 
 GrowableArray<MonitorInfo*>* interpretedVFrame::monitors() const {
+  bool heap_frame = stack_chunk() != nullptr;
+  frame f = !heap_frame ? _fr : stack_chunk()->derelativize(_fr);
   GrowableArray<MonitorInfo*>* result = new GrowableArray<MonitorInfo*>(5);
-  if (stack_chunk() == nullptr) { // no monitors in continuations
-    for (BasicObjectLock* current = (fr().previous_monitor_in_interpreter_frame(fr().interpreter_frame_monitor_begin()));
-        current >= fr().interpreter_frame_monitor_end();
-        current = fr().previous_monitor_in_interpreter_frame(current)) {
-      result->push(new MonitorInfo(current->obj(), current->lock(), false, false));
-    }
+  for (BasicObjectLock* current = (f.previous_monitor_in_interpreter_frame(f.interpreter_frame_monitor_begin()));
+      current >= f.interpreter_frame_monitor_end();
+      current = f.previous_monitor_in_interpreter_frame(current)) {
+      oop owner = !heap_frame ? current->obj() : StackValue::create_stack_value_from_oop_location(stack_chunk(), (void*)current->obj_adr())->get_obj()();
+    result->push(new MonitorInfo(owner, current->lock(), false, false));
   }
   return result;
 }
 
 int interpretedVFrame::bci() const {
diff a/src/hotspot/share/runtime/vframe_hp.cpp b/src/hotspot/share/runtime/vframe_hp.cpp
--- a/src/hotspot/share/runtime/vframe_hp.cpp
+++ b/src/hotspot/share/runtime/vframe_hp.cpp
@@ -233,11 +233,11 @@
   const_cast<RegisterMap*>(&_reg_map)->set_stack_chunk_index(index);
   return res;
 }
 
 BasicLock* compiledVFrame::resolve_monitor_lock(Location location) const {
-  return StackValue::resolve_monitor_lock(&_fr, location);
+  return StackValue::resolve_monitor_lock(stack_chunk() == nullptr ? _fr : stack_chunk()->derelativize(_fr), location);
 }
 
 
 GrowableArray<MonitorInfo*>* compiledVFrame::monitors() const {
   // Natives has no scope
@@ -282,10 +282,11 @@
     }
   }
 
   // Replace the original values with any stores that have been
   // performed through compiledVFrame::update_monitors.
+  if (thread() == nullptr) return result; // Unmounted continuations have no thread so nothing to do.
   GrowableArrayView<jvmtiDeferredLocalVariableSet*>* list = JvmtiDeferredUpdates::deferred_locals(thread());
   if (list != nullptr ) {
     // In real life this never happens or is typically a single element search
     for (int i = 0; i < list->length(); i++) {
       if (list->at(i)->matches(this)) {
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
@@ -98,10 +98,11 @@
 #include "runtime/javaCalls.hpp"
 #include "runtime/javaThread.hpp"
 #include "runtime/jniHandles.hpp"
 #include "runtime/monitorDeflationThread.hpp"
 #include "runtime/notificationThread.hpp"
+#include "runtime/objectMonitor.hpp"
 #include "runtime/os.hpp"
 #include "runtime/osThread.hpp"
 #include "runtime/perfMemory.hpp"
 #include "runtime/serviceThread.hpp"
 #include "runtime/sharedRuntime.hpp"
@@ -655,10 +656,11 @@
   nonstatic_field(JavaThread,                  _stack_base,                                   address)                               \
   nonstatic_field(JavaThread,                  _stack_size,                                   size_t)                                \
   nonstatic_field(JavaThread,                  _vframe_array_head,                            vframeArray*)                          \
   nonstatic_field(JavaThread,                  _vframe_array_last,                            vframeArray*)                          \
   nonstatic_field(JavaThread,                  _active_handles,                               JNIHandleBlock*)                       \
+  nonstatic_field(JavaThread,                  _lock_id,                                      int64_t)                               \
   volatile_nonstatic_field(JavaThread,         _terminated,                                   JavaThread::TerminatedTypes)           \
   nonstatic_field(Thread,                      _resource_area,                                ResourceArea*)                         \
   nonstatic_field(CompilerThread,              _env,                                          ciEnv*)                                \
                                                                                                                                      \
   /************/                                                                                                                     \
@@ -776,10 +778,11 @@
   /************/                                                                                                                     \
                                                                                                                                      \
   volatile_nonstatic_field(ObjectMonitor,      _header,                                       markWord)                              \
   unchecked_nonstatic_field(ObjectMonitor,     _object,                                       sizeof(void *)) /* NOTE: no type */    \
   unchecked_nonstatic_field(ObjectMonitor,     _owner,                                        sizeof(void *)) /* NOTE: no type */    \
+  unchecked_nonstatic_field(ObjectMonitor,     _stack_locker,                                 BasicLock*)                            \
   volatile_nonstatic_field(ObjectMonitor,      _next_om,                                      ObjectMonitor*)                        \
   volatile_nonstatic_field(BasicLock,          _displaced_header,                             markWord)                              \
   nonstatic_field(ObjectMonitor,               _contentions,                                  int)                                   \
   volatile_nonstatic_field(ObjectMonitor,      _waiters,                                      int)                                   \
   volatile_nonstatic_field(ObjectMonitor,      _recursions,                                   intx)                                  \
diff a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp
--- a/src/hotspot/share/services/diagnosticCommand.cpp
+++ b/src/hotspot/share/services/diagnosticCommand.cpp
@@ -126,10 +126,11 @@
 #if INCLUDE_JVMTI
   DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JVMTIDataDumpDCmd>(full_export, true, false));
 #endif // INCLUDE_JVMTI
   DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ThreadDumpDCmd>(full_export, true, false));
   DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ThreadDumpToFileDCmd>(full_export, true, false));
+  DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<VThreadSchedulerDCmd>(full_export, true, false));
   DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ClassLoaderStatsDCmd>(full_export, true, false));
   DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ClassLoaderHierarchyDCmd>(full_export, true, false));
   DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<CompileQueueDCmd>(full_export, true, false));
   DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<CodeListDCmd>(full_export, true, false));
   DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<CodeCacheDCmd>(full_export, true, false));
@@ -1185,11 +1186,10 @@
 
   Handle h_path = java_lang_String::create_from_str(path, CHECK);
 
   Symbol* sym = vmSymbols::jdk_internal_vm_ThreadDumper();
   Klass* k = SystemDictionary::resolve_or_fail(sym, true, CHECK);
-  InstanceKlass* ik = InstanceKlass::cast(k);
   if (HAS_PENDING_EXCEPTION) {
     java_lang_Throwable::print(PENDING_EXCEPTION, output());
     output()->cr();
     CLEAR_PENDING_EXCEPTION;
     return;
@@ -1222,10 +1222,50 @@
   typeArrayOop ba = typeArrayOop(res);
   jbyte* addr = typeArrayOop(res)->byte_at_addr(0);
   output()->print_raw((const char*)addr, ba->length());
 }
 
+void VThreadSchedulerDCmd::execute(DCmdSource source, TRAPS) {
+  ResourceMark rm(THREAD);
+  HandleMark hm(THREAD);
+
+  Symbol* sym = vmSymbols::java_lang_VirtualThread();
+  Klass* k = SystemDictionary::resolve_or_fail(sym, true, CHECK);
+  if (HAS_PENDING_EXCEPTION) {
+    java_lang_Throwable::print(PENDING_EXCEPTION, output());
+    output()->cr();
+    CLEAR_PENDING_EXCEPTION;
+    return;
+  }
+
+  // invoke printDefaultScheduler method
+  JavaValue result(T_OBJECT);
+  JavaCallArguments args;
+  JavaCalls::call_static(&result,
+                         k,
+                         vmSymbols::printDefaultScheduler_name(),
+                         vmSymbols::void_byte_array_signature(),
+                         &args,
+                         THREAD);
+  if (HAS_PENDING_EXCEPTION) {
+    java_lang_Throwable::print(PENDING_EXCEPTION, output());
+    output()->cr();
+    CLEAR_PENDING_EXCEPTION;
+    return;
+  }
+
+  // check that result is byte array
+  oop res = cast_to_oop(result.get_jobject());
+  assert(res->is_typeArray(), "just checking");
+  assert(TypeArrayKlass::cast(res->klass())->element_type() == T_BYTE, "just checking");
+
+  // copy the bytes to the output stream
+  typeArrayOop ba = typeArrayOop(res);
+  jbyte* addr = typeArrayOop(res)->byte_at_addr(0);
+  output()->print_raw((const char*)addr, ba->length());
+}
+
 CompilationMemoryStatisticDCmd::CompilationMemoryStatisticDCmd(outputStream* output, bool heap) :
     DCmdWithParser(output, heap),
   _human_readable("-H", "Human readable format", "BOOLEAN", false, "false"),
   _minsize("-s", "Minimum memory size", "MEMORY SIZE", false, "0") {
   _dcmdparser.add_dcmd_option(&_human_readable);
diff a/src/hotspot/share/services/diagnosticCommand.hpp b/src/hotspot/share/services/diagnosticCommand.hpp
--- a/src/hotspot/share/services/diagnosticCommand.hpp
+++ b/src/hotspot/share/services/diagnosticCommand.hpp
@@ -986,10 +986,23 @@
     return p;
   }
   virtual void execute(DCmdSource source, TRAPS);
 };
 
+class VThreadSchedulerDCmd : public DCmd {
+public:
+  VThreadSchedulerDCmd(outputStream* output, bool heap) : DCmd(output, heap) { }
+  static const char* name() {
+    return "Thread.vthread_scheduler";
+  }
+  static const char* description() {
+    return "Print virtual thread scheduler info.";
+  }
+  static const char* impact() { return "Low"; }
+  virtual void execute(DCmdSource source, TRAPS);
+};
+
 class CompilationMemoryStatisticDCmd: public DCmdWithParser {
 protected:
   DCmdArgument<bool> _human_readable;
   DCmdArgument<MemorySizeArgument> _minsize;
 public:
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
@@ -462,27 +462,10 @@
           currentThread = JavaThread::cast(owner);
         }
       } else if (waitingToLockMonitor != nullptr) {
         if (waitingToLockMonitor->has_owner()) {
           currentThread = Threads::owning_thread_from_monitor(t_list, waitingToLockMonitor);
-          if (currentThread == nullptr) {
-            // 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.
-            num_deadlocks++;
-
-            // add this cycle to the deadlocks list
-            if (deadlocks == nullptr) {
-              deadlocks = cycle;
-            } else {
-              last->set_next(cycle);
-            }
-            last = cycle;
-            cycle = new DeadlockCycle();
-            break;
-          }
         }
       } else {
         if (concurrent_locks) {
           if (waitingToLockBlocker->is_a(vmClasses::java_util_concurrent_locks_AbstractOwnableSynchronizer_klass())) {
             oop threadObj = java_util_concurrent_locks_AbstractOwnableSynchronizer::get_owner_threadObj(waitingToLockBlocker);
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
@@ -1344,6 +1344,16 @@
 
 // Quickly test to make sure IEEE-754 subnormal numbers are correctly
 // handled.
 bool IEEE_subnormal_handling_OK();
 
+#if defined(AMD64) || defined (AARCH64)
+#define LOOM_MONITOR_SUPPORT true
+#define LOOM_MONITOR_SUPPORT_ONLY(code) code
+#define NOT_LOOM_MONITOR_SUPPORT(code)
+#else
+#undef LOOM_MONITOR_SUPPORT
+#define LOOM_MONITOR_SUPPORT_ONLY(code)
+#define NOT_LOOM_MONITOR_SUPPORT(code) code
+#endif
+
 #endif // SHARE_UTILITIES_GLOBALDEFINITIONS_HPP
diff a/src/hotspot/share/utilities/macros.hpp b/src/hotspot/share/utilities/macros.hpp
--- a/src/hotspot/share/utilities/macros.hpp
+++ b/src/hotspot/share/utilities/macros.hpp
@@ -358,16 +358,18 @@
 
 #ifdef ASSERT
 #define DEBUG_ONLY(code) code
 #define NOT_DEBUG(code)
 #define NOT_DEBUG_RETURN  /*next token must be ;*/
+#define NOT_DEBUG_RETURN0
 // Historical.
 #define debug_only(code) code
 #else // ASSERT
 #define DEBUG_ONLY(code)
 #define NOT_DEBUG(code) code
 #define NOT_DEBUG_RETURN {}
+#define NOT_DEBUG_RETURN0 { return 0; }
 #define debug_only(code)
 #endif // ASSERT
 
 #ifdef  _LP64
 #define LP64_ONLY(code) code
diff a/src/java.base/linux/classes/sun/nio/ch/EPollSelectorImpl.java b/src/java.base/linux/classes/sun/nio/ch/EPollSelectorImpl.java
--- a/src/java.base/linux/classes/sun/nio/ch/EPollSelectorImpl.java
+++ b/src/java.base/linux/classes/sun/nio/ch/EPollSelectorImpl.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 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.  Oracle designates this
@@ -34,11 +34,10 @@
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
-import jdk.internal.misc.Blocker;
 
 import static sun.nio.ch.EPoll.EPOLLIN;
 import static sun.nio.ch.EPoll.EPOLL_CTL_ADD;
 import static sun.nio.ch.EPoll.EPOLL_CTL_DEL;
 import static sun.nio.ch.EPoll.EPOLL_CTL_MOD;
@@ -109,40 +108,75 @@
         boolean timedPoll = (to > 0);
 
         int numEntries;
         processUpdateQueue();
         processDeregisterQueue();
-        try {
-            begin(blocking);
 
-            do {
-                long startTime = timedPoll ? System.nanoTime() : 0;
-                boolean attempted = Blocker.begin(blocking);
-                try {
+        if (Thread.currentThread().isVirtual()) {
+            numEntries = (timedPoll)
+                    ? timedPoll(TimeUnit.MILLISECONDS.toNanos(to))
+                    : untimedPoll(blocking);
+        } else {
+            try {
+                begin(blocking);
+                do {
+                    long startTime = timedPoll ? System.nanoTime() : 0;
                     numEntries = EPoll.wait(epfd, pollArrayAddress, NUM_EPOLLEVENTS, to);
-                } finally {
-                    Blocker.end(attempted);
-                }
-                if (numEntries == IOStatus.INTERRUPTED && timedPoll) {
-                    // timed poll interrupted so need to adjust timeout
-                    long adjust = System.nanoTime() - startTime;
-                    to -= (int) TimeUnit.NANOSECONDS.toMillis(adjust);
-                    if (to <= 0) {
-                        // timeout expired so no retry
-                        numEntries = 0;
+                    if (numEntries == IOStatus.INTERRUPTED && timedPoll) {
+                        // timed poll interrupted so need to adjust timeout
+                        long adjust = System.nanoTime() - startTime;
+                        to -= (int) TimeUnit.NANOSECONDS.toMillis(adjust);
+                        if (to <= 0) {
+                            // timeout expired so no retry
+                            numEntries = 0;
+                        }
                     }
-                }
-            } while (numEntries == IOStatus.INTERRUPTED);
-            assert IOStatus.check(numEntries);
-
-        } finally {
-            end(blocking);
+                } while (numEntries == IOStatus.INTERRUPTED);
+            } finally {
+                end(blocking);
+            }
         }
+        assert IOStatus.check(numEntries);
+
         processDeregisterQueue();
         return processEvents(numEntries, action);
     }
 
+    /**
+     * If blocking, parks the current virtual thread until a file descriptor is polled
+     * or the thread is interrupted.
+     */
+    private int untimedPoll(boolean block) throws IOException {
+        int numEntries = EPoll.wait(epfd, pollArrayAddress, NUM_EPOLLEVENTS, 0);
+        if (block) {
+            while (numEntries == 0 && !Thread.currentThread().isInterrupted()) {
+                Poller.pollSelector(epfd, 0);
+                numEntries = EPoll.wait(epfd, pollArrayAddress, NUM_EPOLLEVENTS, 0);
+            }
+        }
+        return numEntries;
+    }
+
+    /**
+     * Parks the current virtual thread until a file descriptor is polled, or the thread
+     * is interrupted, for up to the specified waiting time.
+     */
+    private int timedPoll(long nanos) throws IOException {
+        long startNanos = System.nanoTime();
+        int numEntries = EPoll.wait(epfd, pollArrayAddress, NUM_EPOLLEVENTS, 0);
+        while (numEntries == 0 && !Thread.currentThread().isInterrupted()) {
+            long remainingNanos = nanos - (System.nanoTime() - startNanos);
+            if (remainingNanos <= 0) {
+                // timeout
+                break;
+            }
+            Poller.pollSelector(epfd, remainingNanos);
+            numEntries = EPoll.wait(epfd, pollArrayAddress, NUM_EPOLLEVENTS, 0);
+        }
+        return numEntries;
+    }
+
     /**
      * Process changes to the interest ops.
      */
     private void processUpdateQueue() {
         assert Thread.holdsLock(this);
diff a/src/java.base/macosx/classes/sun/nio/ch/KQueueSelectorImpl.java b/src/java.base/macosx/classes/sun/nio/ch/KQueueSelectorImpl.java
--- a/src/java.base/macosx/classes/sun/nio/ch/KQueueSelectorImpl.java
+++ b/src/java.base/macosx/classes/sun/nio/ch/KQueueSelectorImpl.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 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.  Oracle designates this
@@ -34,11 +34,10 @@
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
-import jdk.internal.misc.Blocker;
 
 import static sun.nio.ch.KQueue.EVFILT_READ;
 import static sun.nio.ch.KQueue.EVFILT_WRITE;
 import static sun.nio.ch.KQueue.EV_ADD;
 import static sun.nio.ch.KQueue.EV_DELETE;
@@ -113,40 +112,75 @@
         boolean timedPoll = (to > 0);
 
         int numEntries;
         processUpdateQueue();
         processDeregisterQueue();
-        try {
-            begin(blocking);
 
-            do {
-                long startTime = timedPoll ? System.nanoTime() : 0;
-                boolean attempted = Blocker.begin(blocking);
-                try {
+        if (Thread.currentThread().isVirtual()) {
+            numEntries = (timedPoll)
+                    ? timedPoll(TimeUnit.MILLISECONDS.toNanos(to))
+                    : untimedPoll(blocking);
+        } else {
+            try {
+                begin(blocking);
+                do {
+                    long startTime = timedPoll ? System.nanoTime() : 0;
                     numEntries = KQueue.poll(kqfd, pollArrayAddress, MAX_KEVENTS, to);
-                } finally {
-                    Blocker.end(attempted);
-                }
-                if (numEntries == IOStatus.INTERRUPTED && timedPoll) {
-                    // timed poll interrupted so need to adjust timeout
-                    long adjust = System.nanoTime() - startTime;
-                    to -= TimeUnit.NANOSECONDS.toMillis(adjust);
-                    if (to <= 0) {
-                        // timeout expired so no retry
-                        numEntries = 0;
+                    if (numEntries == IOStatus.INTERRUPTED && timedPoll) {
+                        // timed poll interrupted so need to adjust timeout
+                        long adjust = System.nanoTime() - startTime;
+                        to -= TimeUnit.NANOSECONDS.toMillis(adjust);
+                        if (to <= 0) {
+                            // timeout expired so no retry
+                            numEntries = 0;
+                        }
                     }
-                }
-            } while (numEntries == IOStatus.INTERRUPTED);
-            assert IOStatus.check(numEntries);
-
-        } finally {
-            end(blocking);
+                } while (numEntries == IOStatus.INTERRUPTED);
+            } finally {
+                end(blocking);
+            }
         }
+        assert IOStatus.check(numEntries);
+
         processDeregisterQueue();
         return processEvents(numEntries, action);
     }
 
+    /**
+     * If blocking, parks the current virtual thread until a file descriptor is polled
+     * or the thread is interrupted.
+     */
+    private int untimedPoll(boolean block) throws IOException {
+        int numEntries = KQueue.poll(kqfd, pollArrayAddress, MAX_KEVENTS, 0);
+        if (block) {
+            while (numEntries == 0 && !Thread.currentThread().isInterrupted()) {
+                Poller.pollSelector(kqfd, 0);
+                numEntries = KQueue.poll(kqfd, pollArrayAddress, MAX_KEVENTS, 0);
+            }
+        }
+        return numEntries;
+    }
+
+    /**
+     * Parks the current virtual thread until a file descriptor is polled, or the thread
+     * is interrupted, for up to the specified waiting time.
+     */
+    private int timedPoll(long nanos) throws IOException {
+        long startNanos = System.nanoTime();
+        int numEntries = KQueue.poll(kqfd, pollArrayAddress, MAX_KEVENTS, 0);
+        while (numEntries == 0 && !Thread.currentThread().isInterrupted()) {
+            long remainingNanos = nanos - (System.nanoTime() - startNanos);
+            if (remainingNanos <= 0) {
+                // timeout
+                break;
+            }
+            Poller.pollSelector(kqfd, remainingNanos);
+            numEntries = KQueue.poll(kqfd, pollArrayAddress, MAX_KEVENTS, 0);
+        }
+        return numEntries;
+    }
+
     /**
      * Process changes to the interest ops.
      */
     private void processUpdateQueue() {
         assert Thread.holdsLock(this);
diff a/src/java.base/share/classes/java/lang/PinnedThreadPrinter.java b/src/java.base/share/classes/java/lang/PinnedThreadPrinter.java
--- a/src/java.base/share/classes/java/lang/PinnedThreadPrinter.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (c) 2020, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 java.lang;
-
-import java.io.PrintStream;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
-import static java.lang.StackWalker.Option.*;
-import jdk.internal.access.JavaIOPrintStreamAccess;
-import jdk.internal.access.SharedSecrets;
-import jdk.internal.misc.InternalLock;
-import jdk.internal.vm.Continuation;
-
-/**
- * Helper class to print the virtual thread stack trace when pinned.
- *
- * The class maintains a ClassValue with the hashes of stack traces that are pinned by
- * code in that Class. This is used to avoid printing the same stack trace many times.
- */
-class PinnedThreadPrinter {
-    private static final JavaIOPrintStreamAccess JIOPSA = SharedSecrets.getJavaIOPrintStreamAccess();
-    private static final StackWalker STACK_WALKER;
-    static {
-        var options = Set.of(SHOW_REFLECT_FRAMES, RETAIN_CLASS_REFERENCE);
-        PrivilegedAction<StackWalker> pa = () ->
-            LiveStackFrame.getStackWalker(options, VirtualThread.continuationScope());
-        @SuppressWarnings("removal")
-        var stackWalker = AccessController.doPrivileged(pa);
-        STACK_WALKER = stackWalker;
-    }
-
-    private static final ClassValue<Hashes> HASHES = new ClassValue<>() {
-        @Override
-        protected Hashes computeValue(Class<?> type) {
-            return new Hashes();
-        }
-    };
-
-    @SuppressWarnings("serial")
-    private static class Hashes extends LinkedHashMap<Integer, Boolean> {
-        boolean add(int hash) {
-            return (putIfAbsent(hash, Boolean.TRUE) == null);
-        }
-        @Override
-        protected boolean removeEldestEntry(Map.Entry<Integer, Boolean> oldest) {
-            // limit number of hashes
-            return size() > 8;
-        }
-    }
-
-    /**
-     * Returns a hash of the given stack trace. The hash is based on the class,
-     * method and bytecode index.
-     */
-    private static int hash(List<LiveStackFrame> stack) {
-        int hash = 0;
-        for (LiveStackFrame frame : stack) {
-            hash = (31 * hash) + Objects.hash(frame.getDeclaringClass(),
-                    frame.getMethodName(),
-                    frame.getByteCodeIndex());
-        }
-        return hash;
-    }
-
-    /**
-     * Returns true if the frame is native, a class initializer, or holds monitors.
-     */
-    private static boolean isInterestingFrame(LiveStackFrame f) {
-        return f.isNativeMethod()
-                || "<clinit>".equals(f.getMethodName())
-                || (f.getMonitors().length > 0);
-    }
-
-    /**
-     * Prints the current thread's stack trace.
-     *
-     * @param printAll true to print all stack frames, false to only print the
-     *        frames that are native or holding a monitor
-     */
-    static void printStackTrace(PrintStream out, Continuation.Pinned reason, boolean printAll) {
-        List<LiveStackFrame> stack = STACK_WALKER.walk(s ->
-            s.map(f -> (LiveStackFrame) f)
-                    .filter(f -> f.getDeclaringClass() != PinnedThreadPrinter.class)
-                    .collect(Collectors.toList())
-        );
-        Object lockObj = JIOPSA.lock(out);
-        if (lockObj instanceof InternalLock lock && lock.tryLock()) {
-            try {
-                // find the closest frame that is causing the thread to be pinned
-                stack.stream()
-                    .filter(f -> isInterestingFrame(f))
-                    .map(LiveStackFrame::getDeclaringClass)
-                    .findFirst()
-                    .ifPresentOrElse(klass -> {
-                        // print the stack trace if not already seen
-                        int hash = hash(stack);
-                        if (HASHES.get(klass).add(hash)) {
-                            printStackTrace(out, reason, stack, printAll);
-                        }
-                    }, () -> printStackTrace(out, reason, stack, true));  // not found
-
-            } finally {
-                lock.unlock();
-            }
-        }
-    }
-
-    private static void printStackTrace(PrintStream out,
-                                        Continuation.Pinned reason,
-                                        List<LiveStackFrame> stack,
-                                        boolean printAll) {
-        out.format("%s reason:%s%n", Thread.currentThread(), reason);
-        for (LiveStackFrame frame : stack) {
-            var ste = frame.toStackTraceElement();
-            int monitorCount = frame.getMonitors().length;
-            if (monitorCount > 0) {
-                out.format("    %s <== monitors:%d%n", ste, monitorCount);
-            } else if (printAll || isInterestingFrame(frame)) {
-                out.format("    %s%n", ste);
-            }
-        }
-    }
-}
diff a/src/java.base/share/classes/java/lang/ScopedValue.java b/src/java.base/share/classes/java/lang/ScopedValue.java
--- a/src/java.base/share/classes/java/lang/ScopedValue.java
+++ b/src/java.base/share/classes/java/lang/ScopedValue.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2022, Red Hat Inc.
  * 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
@@ -27,11 +27,10 @@
 package java.lang;
 
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.lang.ref.Reference;
-import java.util.concurrent.Callable;
 import java.util.concurrent.StructuredTaskScope;
 import java.util.concurrent.StructureViolationException;
 import java.util.function.Supplier;
 import jdk.internal.access.JavaUtilConcurrentTLRAccess;
 import jdk.internal.access.SharedSecrets;
@@ -62,33 +61,33 @@
  * object <em>bound</em> to some value for the bounded period of execution of a method.
  * The method may invoke another method, which in turn may invoke another. The unfolding
  * execution of the methods define a <em>dynamic scope</em>. Code in these methods with
  * access to the {@code ScopedValue} object may read its value. The {@code ScopedValue}
  * object reverts to being <em>unbound</em> when the original method completes normally or
- * with an exception. The {@code ScopedValue} API supports executing a {@link Runnable#run()
- * Runnable.run}, {@link Callable#call() Callable.call}, or {@link Supplier#get() Supplier.get}
- * method with a {@code ScopedValue} bound to a value.
+ * with an exception. The {@code ScopedValue} API supports executing a {@link Runnable},
+ * or {@link CallableOp} with a {@code ScopedValue} bound to a value.
  *
  * <p> Consider the following example with a scoped value "{@code NAME}" bound to the value
- * "{@code duke}" for the execution of a {@code run} method. The {@code run} method, in
- * turn, invokes {@code doSomething}.
+ * "{@code duke}" for the execution of a {@code Runnable}'s {@code run} method.
+ * The {@code run} method, in turn, invokes a method {@code doSomething}.
+ *
+ *
  * {@snippet lang=java :
  *     // @link substring="newInstance" target="#newInstance" :
  *     private static final ScopedValue<String> NAME = ScopedValue.newInstance();
  *
- *     // @link substring="runWhere" target="#runWhere" :
+ *     // @link substring="runWhere" target="#runWhere(ScopedValue, Object, Runnable)" :
  *     ScopedValue.runWhere(NAME, "duke", () -> doSomething());
  * }
  * Code executed directly or indirectly by {@code doSomething}, with access to the field
  * {@code NAME}, can invoke {@code NAME.get()} to read the value "{@code duke}". {@code
  * NAME} is bound while executing the {@code run} method. It reverts to being unbound when
  * the {@code run} method completes.
  *
  * <p> The example using {@code runWhere} invokes a method that does not return a result.
- * The {@link #callWhere(ScopedValue, Object, Callable) callWhere} and {@link
- * #getWhere(ScopedValue, Object, Supplier) getWhere} can be used to invoke a method that
- * returns a result.
+ * The {@link #callWhere(ScopedValue, Object, CallableOp) callWhere} method can be used
+ * to invoke a method that returns a result.
  * In addition, {@code ScopedValue} defines the {@link #where(ScopedValue, Object)} method
  * for cases where multiple mappings (of {@code ScopedValue} to value) are accumulated
  * in advance of calling a method with all {@code ScopedValue}s bound to their value.
  *
  * <h2>Bindings are per-thread</h2>
@@ -141,11 +140,11 @@
  * {@code ScopedValue} supports sharing across threads. This sharing is limited to
  * structured cases where child threads are started and terminate within the bounded
  * period of execution by a parent thread. When using a {@link StructuredTaskScope},
  * scoped value bindings are <em>captured</em> when creating a {@code StructuredTaskScope}
  * and inherited by all threads started in that task scope with the
- * {@link StructuredTaskScope#fork(Callable) fork} method.
+ * {@link StructuredTaskScope#fork(java.util.concurrent.Callable) fork} method.
  *
  * <p> A {@code ScopedValue} that is shared across threads requires that the value be an
  * immutable object or for all access to the value to be appropriately synchronized.
  *
  * <p> In the following example, the {@code ScopedValue} {@code NAME} is bound to the
@@ -289,17 +288,16 @@
     }
 
     /**
      * A mapping of scoped values, as <em>keys</em>, to values.
      *
-     * <p> A {@code Carrier} is used to accumulate mappings so that an operation (a
-     * {@link Runnable} or {@link Callable}) can be executed with all scoped values in the
+     * <p> A {@code Carrier} is used to accumulate mappings so that an operation (a {@link
+     * Runnable} or {@link CallableOp}) can be executed with all scoped values in the
      * mapping bound to values. The following example runs an operation with {@code k1}
      * bound (or rebound) to {@code v1}, and {@code k2} bound (or rebound) to {@code v2}.
      * {@snippet lang=java :
-     *     // @link substring="where" target="#where(ScopedValue, Object)" :
-     *     ScopedValue.where(k1, v1).where(k2, v2).run(() -> ... );
+     *     ScopedValue.runWhere(ScopedValue.where(k1, v1).where(k2, v2), () -> ... );
      * }
      *
      * <p> A {@code Carrier} is immutable and thread-safe. The {@link
      * #where(ScopedValue, Object) where} method returns a new {@code Carrier} object,
      * it does not mutate an existing mapping.
@@ -381,11 +379,11 @@
             for (Carrier carrier = this;
                  carrier != null && containsAll(carrier.bitmask, bits);
                  carrier = carrier.prev) {
                 if (carrier.getKey() == key) {
                     Object value = carrier.get();
-                    return (T)value;
+                    return (T) value;
                 }
             }
             throw new NoSuchElementException();
         }
 
@@ -404,76 +402,34 @@
          * exception). In that case, the underlying construct of the {@code StructuredTaskScope}
          * is closed and {@link StructureViolationException} is thrown.
          *
          * @param op the operation to run
          * @param <R> the type of the result of the operation
+         * @param <X> type of the exception thrown by the operation
          * @return the result
          * @throws StructureViolationException if a structure violation is detected
-         * @throws Exception if {@code op} completes with an exception
-         * @see ScopedValue#callWhere(ScopedValue, Object, Callable)
+         * @throws X if {@code op} completes with an exception
+         * @see ScopedValue#callWhere(ScopedValue, Object, CallableOp)
          */
-        public <R> R call(Callable<? extends R> op) throws Exception {
+        <R, X extends Throwable> R call(CallableOp<? extends R, X> op) throws X {
             Objects.requireNonNull(op);
             Cache.invalidate(bitmask);
             var prevSnapshot = scopedValueBindings();
             var newSnapshot = new Snapshot(this, prevSnapshot);
             return runWith(newSnapshot, op);
         }
 
-        /**
-         * Invokes a supplier of results with each scoped value in this mapping bound
-         * to its value in the current thread.
-         * When the operation completes (normally or with an exception), each scoped value
-         * in the mapping will revert to being unbound, or revert to its previous value
-         * when previously bound, in the current thread. If {@code op} completes with an
-         * exception then it propagated by this method.
-         *
-         * <p> Scoped values are intended to be used in a <em>structured manner</em>. If code
-         * invoked directly or indirectly by the operation creates a {@link StructuredTaskScope}
-         * but does not {@linkplain StructuredTaskScope#close() close} it, then it is detected
-         * as a <em>structure violation</em> when the operation completes (normally or with an
-         * exception). In that case, the underlying construct of the {@code StructuredTaskScope}
-         * is closed and {@link StructureViolationException} is thrown.
-         *
-         * @param op the operation to run
-         * @param <R> the type of the result of the operation
-         * @return the result
-         * @throws StructureViolationException if a structure violation is detected
-         * @see ScopedValue#getWhere(ScopedValue, Object, Supplier)
-         */
-        public <R> R get(Supplier<? extends R> op) {
-            Objects.requireNonNull(op);
-            Cache.invalidate(bitmask);
-            var prevSnapshot = scopedValueBindings();
-            var newSnapshot = new Snapshot(this, prevSnapshot);
-            return runWith(newSnapshot, new CallableAdapter<R>(op));
-        }
-
-        // A lightweight adapter from Supplier to Callable. This is
-        // used here to create the Callable which is passed to
-        // Carrier#call() in this thread because it needs neither
-        // runtime bytecode generation nor any release fencing.
-        private static final class CallableAdapter<V> implements Callable<V> {
-            private /*non-final*/ Supplier<? extends V> s;
-            CallableAdapter(Supplier<? extends V> s) {
-                this.s = s;
-            }
-            public V call() {
-                return s.get();
-            }
-        }
-
         /**
          * Execute the action with a set of ScopedValue bindings.
          *
          * The VM recognizes this method as special, so any changes to the
          * name or signature require corresponding changes in
          * JVM_FindScopedValueBindings().
          */
         @Hidden
         @ForceInline
-        private <R> R runWith(Snapshot newSnapshot, Callable<R> op) {
+        private <R, X extends Throwable> R runWith(Snapshot newSnapshot, CallableOp<R, X> op) {
             try {
                 Thread.setScopedValueBindings(newSnapshot);
                 Thread.ensureMaterializedForStackWalk(newSnapshot);
                 return ScopedValueContainer.call(op);
             } finally {
@@ -500,11 +456,11 @@
          *
          * @param op the operation to run
          * @throws StructureViolationException if a structure violation is detected
          * @see ScopedValue#runWhere(ScopedValue, Object, Runnable)
          */
-        public void run(Runnable op) {
+        void run(Runnable op) {
             Objects.requireNonNull(op);
             Cache.invalidate(bitmask);
             var prevSnapshot = scopedValueBindings();
             var newSnapshot = new Snapshot(this, prevSnapshot);
             runWith(newSnapshot, op);
@@ -530,19 +486,39 @@
                 Cache.invalidate(bitmask);
             }
         }
     }
 
+    /**
+     * An operation that returns a result and may throw an exception.
+     *
+     * @apiNote Naming TBD. Not proposing to move this to j.u.function.
+     *
+     * @param <T> result type of the operation
+     * @param <X> type of the exception thrown by the operation
+     * @since 23
+     */
+    @PreviewFeature(feature = PreviewFeature.Feature.SCOPED_VALUES)
+    @FunctionalInterface
+    public interface CallableOp<T, X extends Throwable> {
+        /**
+         * Executes this operation.
+         * @return the result, can be null
+         * @throws X if the operation completes with an exception
+         */
+        T call() throws X;
+    }
+
     /**
      * Creates a new {@code Carrier} with a single mapping of a {@code ScopedValue}
      * <em>key</em> to a value. The {@code Carrier} can be used to accumulate mappings so
      * that an operation can be executed with all scoped values in the mapping bound to
      * values. The following example runs an operation with {@code k1} bound (or rebound)
      * to {@code v1}, and {@code k2} bound (or rebound) to {@code v2}.
      * {@snippet lang=java :
-     *     // @link substring="run" target="Carrier#run(Runnable)" :
-     *     ScopedValue.where(k1, v1).where(k2, v2).run(() -> ... );
+     *     // @link substring="runWhere" target="#runWhere(Carrier, Runnable)" :
+     *     ScopedValue.runWhere(ScopedValue.where(k1, v1).where(k2, v2), () -> ... );
      * }
      *
      * @param key the {@code ScopedValue} key
      * @param value the value, can be {@code null}
      * @param <T> the type of the value
@@ -564,65 +540,54 @@
      * but does not {@linkplain StructuredTaskScope#close() close} it, then it is detected
      * as a <em>structure violation</em> when the operation completes (normally or with an
      * exception). In that case, the underlying construct of the {@code StructuredTaskScope}
      * is closed and {@link StructureViolationException} is thrown.
      *
-     * @implNote
-     * This method is implemented to be equivalent to:
-     * {@snippet lang=java :
-     *     // @link substring="call" target="Carrier#call(Callable)" :
-     *     ScopedValue.where(key, value).call(op);
-     * }
-     *
      * @param key the {@code ScopedValue} key
      * @param value the value, can be {@code null}
      * @param <T> the type of the value
      * @param <R> the result type
+     * @param <X> type of the exception thrown by the operation
      * @param op the operation to call
      * @return the result
      * @throws StructureViolationException if a structure violation is detected
-     * @throws Exception if the operation completes with an exception
+     * @throws X if the operation completes with an exception
+     * @since 23
      */
-    public static <T, R> R callWhere(ScopedValue<T> key,
-                                     T value,
-                                     Callable<? extends R> op) throws Exception {
+    public static <T, R, X extends Throwable> R callWhere(ScopedValue<T> key,
+                                                          T value,
+                                                          CallableOp<? extends R, X> op) throws X {
         return where(key, value).call(op);
     }
 
     /**
-     * Invokes a supplier of results with a {@code ScopedValue} bound to a value
-     * in the current thread. When the operation completes (normally or with an
-     * exception), the {@code ScopedValue} will revert to being unbound, or revert to
-     * its previous value when previously bound, in the current thread. If {@code op}
-     * completes with an exception then it propagated by this method.
+     * Calls a value-returning operation with each scoped value in the given mapping bound
+     * to its value in the current thread.
+     * When the operation completes (normally or with an exception), each scoped value
+     * in the mapping will revert to being unbound, or revert to its previous value
+     * when previously bound, in the current thread. If {@code op} completes with an
+     * exception then it propagated by this method.
      *
      * <p> Scoped values are intended to be used in a <em>structured manner</em>. If code
      * invoked directly or indirectly by the operation creates a {@link StructuredTaskScope}
      * but does not {@linkplain StructuredTaskScope#close() close} it, then it is detected
      * as a <em>structure violation</em> when the operation completes (normally or with an
      * exception). In that case, the underlying construct of the {@code StructuredTaskScope}
      * is closed and {@link StructureViolationException} is thrown.
      *
-     * @implNote
-     * This method is implemented to be equivalent to:
-     * {@snippet lang=java :
-     *     // @link substring="get" target="Carrier#get(Supplier)" :
-     *     ScopedValue.where(key, value).get(op);
-     * }
-     *
-     * @param key the {@code ScopedValue} key
-     * @param value the value, can be {@code null}
-     * @param <T> the type of the value
-     * @param <R> the result type
-     * @param op the operation to call
+     * @param carrier the mapping of scoped values, as keys, to values
+     * @param op the operation to run
      * @return the result
+     * @param <R> the result type
+     * @param <X> type of the exception thrown by the operation
      * @throws StructureViolationException if a structure violation is detected
+     * @throws X if the operation completes with an exception
+     * @since 23
      */
-    public static <T, R> R getWhere(ScopedValue<T> key,
-                                    T value,
-                                    Supplier<? extends R> op) {
-        return where(key, value).get(op);
+    public static <R, X extends Throwable> R callWhere(Carrier carrier,
+                                                       CallableOp<? extends R, X> op) throws X {
+        return carrier.call(op);
     }
 
     /**
      * Run an operation with a {@code ScopedValue} bound to a value in the current
      * thread. When the operation completes (normally or with an exception), the
@@ -635,27 +600,44 @@
      * but does not {@linkplain StructuredTaskScope#close() close} it, then it is detected
      * as a <em>structure violation</em> when the operation completes (normally or with an
      * exception). In that case, the underlying construct of the {@code StructuredTaskScope}
      * is closed and {@link StructureViolationException} is thrown.
      *
-     * @implNote
-     * This method is implemented to be equivalent to:
-     * {@snippet lang=java :
-     *     // @link substring="run" target="Carrier#run(Runnable)" :
-     *     ScopedValue.where(key, value).run(op);
-     * }
-     *
      * @param key the {@code ScopedValue} key
      * @param value the value, can be {@code null}
      * @param <T> the type of the value
      * @param op the operation to call
      * @throws StructureViolationException if a structure violation is detected
      */
     public static <T> void runWhere(ScopedValue<T> key, T value, Runnable op) {
         where(key, value).run(op);
     }
 
+    /**
+     * Runs an operation with each scoped value in the given mapping bound to its value
+     * in the current thread.
+     * When the operation completes (normally or with an exception), each scoped value
+     * in the mapping will revert to being unbound, or revert to its previous value
+     * when previously bound, in the current thread. If {@code op} completes with an
+     * exception then it propagated by this method.
+     *
+     * <p> Scoped values are intended to be used in a <em>structured manner</em>. If code
+     * invoked directly or indirectly by the operation creates a {@link StructuredTaskScope}
+     * but does not {@linkplain StructuredTaskScope#close() close} it, then it is detected
+     * as a <em>structure violation</em> when the operation completes (normally or with an
+     * exception). In that case, the underlying construct of the {@code StructuredTaskScope}
+     * is closed and {@link StructureViolationException} is thrown.
+     *
+     * @param carrier the mapping of scoped values, as keys, to values
+     * @param op the operation to run
+     * @throws StructureViolationException if a structure violation is detected
+     * @since 23
+     */
+    public static void runWhere(Carrier carrier, Runnable op) {
+        carrier.run(op);
+    }
+
     private ScopedValue() {
         this.hash = generateKey();
     }
 
     /**
diff a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java
--- a/src/java.base/share/classes/java/lang/System.java
+++ b/src/java.base/share/classes/java/lang/System.java
@@ -2263,10 +2263,11 @@
     private static class Out extends FileOutputStream {
         Out(FileDescriptor fd) {
             super(fd);
         }
 
+        @Override
         public void write(int b) throws IOException {
             boolean attempted = Blocker.begin();
             try {
                 super.write(b);
             } finally {
@@ -2678,18 +2679,10 @@
 
             public Thread currentCarrierThread() {
                 return Thread.currentCarrierThread();
             }
 
-            public <V> V executeOnCarrierThread(Callable<V> task) throws Exception {
-                if (Thread.currentThread() instanceof VirtualThread vthread) {
-                    return vthread.executeOnCarrierThread(task);
-                } else {
-                    return task.call();
-                }
-            }
-
             public <T> T getCarrierThreadLocal(CarrierThreadLocal<T> local) {
                 return ((ThreadLocal<T>)local).getCarrierThreadLocal();
             }
 
             public <T> void setCarrierThreadLocal(CarrierThreadLocal<T> local, T value) {
diff a/src/java.base/share/classes/java/lang/Thread.java b/src/java.base/share/classes/java/lang/Thread.java
--- a/src/java.base/share/classes/java/lang/Thread.java
+++ b/src/java.base/share/classes/java/lang/Thread.java
@@ -160,22 +160,28 @@
  *   // A ThreadFactory that creates virtual threads
  *   ThreadFactory factory = Thread.ofVirtual().factory();
  * }
  *
  * <h2><a id="inheritance">Inheritance when creating threads</a></h2>
- * A {@code Thread} inherits its initial values of {@linkplain InheritableThreadLocal
+ * A {@code Thread} created with one of the public constructors inherits the daemon
+ * status and thread priority from the parent thread at the time that the child {@code
+ * Thread} is created. The {@linkplain ThreadGroup thread group} is also inherited when
+ * not provided to the constructor (and not selected by the security manager).
+ * When using a {@code Thread.Builder} to create a platform thread, the daemon status,
+ * thread priority, and thread group (when not selected by the security manager) are
+ * inherited when not set on the builder. As with the constructors, inheriting from the
+ * parent thread is done when the child {@code Thread} is created.
+ *
+ * <p> A {@code Thread} inherits its initial values of {@linkplain InheritableThreadLocal
  * inheritable-thread-local} variables (including the context class loader) from
  * the parent thread values at the time that the child {@code Thread} is created.
  * The 5-param {@linkplain Thread#Thread(ThreadGroup, Runnable, String, long, boolean)
  * constructor} can be used to create a thread that does not inherit its initial
  * values from the constructing thread. When using a {@code Thread.Builder}, the
  * {@link Builder#inheritInheritableThreadLocals(boolean) inheritInheritableThreadLocals}
  * method can be used to select if the initial values are inherited.
  *
- * <p> Platform threads inherit the daemon status, thread priority, and when not
- * provided (or not selected by a security manager), the thread group.
- *
  * <p> Creating a platform thread {@linkplain AccessController#getContext() captures} the
  * {@linkplain AccessControlContext caller context} to limit the {@linkplain Permission
  * permissions} of the new thread when it executes code that performs a {@linkplain
  * AccessController#doPrivileged(PrivilegedAction) privileged action}. The captured
  * caller context is the new thread's "Inherited {@link AccessControlContext}". Creating
@@ -719,14 +725,15 @@
             int priority = Math.min(parent.getPriority(), g.getMaxPriority());
             this.holder = new FieldHolder(g, task, stackSize, priority, parent.isDaemon());
         }
 
         if (attached && VM.initLevel() < 1) {
-            this.tid = 1;  // primordial thread
+            this.tid = 3;  // primordial thread
         } else {
             this.tid = ThreadIdentifiers.next();
         }
+
         this.name = (name != null) ? name : genThreadName();
 
         if (acc != null) {
             this.inheritedAccessControlContext = acc;
         } else {
diff a/src/java.base/share/classes/java/lang/VirtualThread.java b/src/java.base/share/classes/java/lang/VirtualThread.java
--- a/src/java.base/share/classes/java/lang/VirtualThread.java
+++ b/src/java.base/share/classes/java/lang/VirtualThread.java
@@ -22,10 +22,11 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
 package java.lang;
 
+import java.nio.charset.StandardCharsets;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.concurrent.Callable;
@@ -39,11 +40,10 @@
 import java.util.concurrent.Future;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import jdk.internal.event.VirtualThreadEndEvent;
-import jdk.internal.event.VirtualThreadPinnedEvent;
 import jdk.internal.event.VirtualThreadStartEvent;
 import jdk.internal.event.VirtualThreadSubmitFailedEvent;
 import jdk.internal.misc.CarrierThread;
 import jdk.internal.misc.InnocuousThread;
 import jdk.internal.misc.Unsafe;
@@ -67,17 +67,17 @@
  */
 final class VirtualThread extends BaseVirtualThread {
     private static final Unsafe U = Unsafe.getUnsafe();
     private static final ContinuationScope VTHREAD_SCOPE = new ContinuationScope("VirtualThreads");
     private static final ForkJoinPool DEFAULT_SCHEDULER = createDefaultScheduler();
-    private static final ScheduledExecutorService UNPARKER = createDelayedTaskScheduler();
-    private static final int TRACE_PINNING_MODE = tracePinningMode();
+    private static final ScheduledExecutorService[] DELAYED_TASK_SCHEDULERS = createDelayedTaskSchedulers();
 
     private static final long STATE = U.objectFieldOffset(VirtualThread.class, "state");
     private static final long PARK_PERMIT = U.objectFieldOffset(VirtualThread.class, "parkPermit");
     private static final long CARRIER_THREAD = U.objectFieldOffset(VirtualThread.class, "carrierThread");
     private static final long TERMINATION = U.objectFieldOffset(VirtualThread.class, "termination");
+    private static final long ON_WAITING_LIST = U.objectFieldOffset(VirtualThread.class, "onWaitingList");
 
     // scheduler and continuation
     private final Executor scheduler;
     private final Continuation cont;
     private final Runnable runContinuation;
@@ -104,10 +104,15 @@
      * TIMED_PARKING -> TIMED_PARKED    // cont.yield successful, timed-parked
      * TIMED_PARKING -> TIMED_PINNED    // cont.yield failed, timed-parked on carrier
      *  TIMED_PARKED -> UNPARKED        // unparked, may be scheduled to continue
      *  TIMED_PINNED -> RUNNING         // unparked, continue execution on same carrier
      *
+     *  RUNNABLE -> BLOCKING       // blocking on monitor enter
+     *  BLOCKING -> BLOCKED        // blocked on monitor enter
+     *   BLOCKED -> UNBLOCKED      // unblocked, may be scheduled to continue
+     * UNBLOCKED -> RUNNING        // continue execution after blocked on monitor enter
+     *
      *  RUNNING -> YIELDING        // Thread.yield
      * YIELDING -> YIELDED         // cont.yield successful, may be scheduled to continue
      * YIELDING -> RUNNING         // cont.yield failed
      *  YIELDED -> RUNNING         // continue execution after Thread.yield
      */
@@ -126,24 +131,41 @@
 
     // Thread.yield
     private static final int YIELDING = 10;
     private static final int YIELDED  = 11;         // unmounted but runnable
 
+    // monitor enter
+    private static final int BLOCKING  = 12;
+    private static final int BLOCKED   = 13;        // unmounted
+    private static final int UNBLOCKED = 14;        // unmounted but runnable
+
     private static final int TERMINATED = 99;  // final state
 
     // can be suspended from scheduling when unmounted
     private static final int SUSPENDED = 1 << 8;
 
     // parking permit
     private volatile boolean parkPermit;
 
+    // used to mark thread as ready to be unblocked while it is concurrently blocking
+    private volatile boolean unblocked;
+
+    // a positive value if "responsible thread" blocked on monitor enter, accessed by VM
+    private volatile byte recheckInterval;
+
     // carrier thread when mounted, accessed by VM
     private volatile Thread carrierThread;
 
     // termination object when joining, created lazily if needed
     private volatile CountDownLatch termination;
 
+    // has the value 1 when on the list of virtual threads waiting to be unblocked
+    private volatile byte onWaitingList;
+
+    // next virtual thread on the list of virtual threads waiting to be unblocked
+    private volatile VirtualThread next;
+
     /**
      * Returns the continuation scope used for virtual threads.
      */
     static ContinuationScope continuationScope() {
         return VTHREAD_SCOPE;
@@ -187,22 +209,12 @@
         VThreadContinuation(VirtualThread vthread, Runnable task) {
             super(VTHREAD_SCOPE, wrap(vthread, task));
         }
         @Override
         protected void onPinned(Continuation.Pinned reason) {
-            if (TRACE_PINNING_MODE > 0) {
-                boolean printAll = (TRACE_PINNING_MODE == 1);
-                VirtualThread vthread = (VirtualThread) Thread.currentThread();
-                int oldState = vthread.state();
-                try {
-                    // avoid printing when in transition states
-                    vthread.setState(RUNNING);
-                    PinnedThreadPrinter.printStackTrace(System.out, reason, printAll);
-                } finally {
-                    vthread.setState(oldState);
-                }
-            }
+            // emit JFR event
+            virtualThreadPinnedEvent(reason.reasonCode(), reason.reasonString());
         }
         private static Runnable wrap(VirtualThread vthread, Runnable task) {
             return new Runnable() {
                 @Hidden
                 public void run() {
@@ -224,11 +236,12 @@
             throw new WrongThreadException();
         }
 
         // set state to RUNNING
         int initialState = state();
-        if (initialState == STARTED || initialState == UNPARKED || initialState == YIELDED) {
+        if (initialState == STARTED || initialState == UNPARKED
+                || initialState == UNBLOCKED || initialState == YIELDED) {
             // newly started or continue after parking/blocking/Thread.yield
             if (!compareAndSetState(initialState, RUNNING)) {
                 return;
             }
             // consume parking permit when continuing after parking
@@ -266,10 +279,24 @@
             submitFailed(ree);
             throw ree;
         }
     }
 
+    /**
+     * Submits the runContinuation task the scheduler. For the default scheduler, the task
+     * will be pushed to an external submission queue.
+     * @throws RejectedExecutionException
+     */
+    private void externalSubmitRunContinuation() {
+        if (scheduler == DEFAULT_SCHEDULER
+                && currentCarrierThread() instanceof CarrierThread ct) {
+            externalSubmitRunContinuation(ct.getPool());
+        } else {
+            submitRunContinuation();
+        }
+    }
+
     /**
      * Submits the runContinuation task to given scheduler with a lazy submit.
      * @throws RejectedExecutionException
      * @see ForkJoinPool#lazySubmit(ForkJoinTask)
      */
@@ -383,10 +410,12 @@
      * current thread is the current platform thread.
      */
     @ChangesCurrentThread
     @ReservedStackAccess
     private void unmount() {
+        assert !Thread.holdsLock(interruptLock);
+
         // set Thread.currentThread() to return the platform thread
         Thread carrier = this.carrierThread;
         carrier.setCurrentThread(carrier);
 
         // break connection to carrier thread, synchronized with interrupt
@@ -408,10 +437,11 @@
         notifyJvmtiHideFrames(true);
         Thread carrier = this.carrierThread;
         assert Thread.currentThread() == this
                 && carrier == Thread.currentCarrierThread();
         carrier.setCurrentThread(carrier);
+        setLockId(this.threadId()); // keep lockid of vthread
     }
 
     /**
      * Sets the current thread to the given virtual thread.
      */
@@ -496,10 +526,37 @@
                 submitRunContinuation();
             }
             return;
         }
 
+        // blocking on monitorenter
+        if (s == BLOCKING) {
+            setState(BLOCKED);
+
+            // may have been unblocked while blocking
+            if (unblocked && compareAndSetState(BLOCKED, UNBLOCKED)) {
+                unblocked = false;
+                submitRunContinuation();
+                return;
+            }
+
+            // if thread is the designated responsible thread for a monitor then schedule
+            // it to wakeup so that it can check and recover. See objectMonitor.cpp.
+            int recheckInterval = this.recheckInterval;
+            if (recheckInterval > 0 && state() == BLOCKED) {
+                assert recheckInterval >= 1 && recheckInterval <= 6;
+                // 4 ^ (recheckInterval - 1) = 1, 4, 16, ... 1024
+                long delay = 1 << (recheckInterval - 1) << (recheckInterval - 1);
+                Future<?> unblocker = delayedTaskScheduler().schedule(this::unblock, delay, MILLISECONDS);
+                // cancel if unblocked while scheduling the unblock
+                if (state() != BLOCKED) {
+                    unblocker.cancel(false);
+                }
+            }
+            return;
+        }
+
         assert false;
     }
 
     /**
      * Invoked after the continuation completes.
@@ -559,12 +616,12 @@
             addedToContainer = true;
 
             // scoped values may be inherited
             inheritScopedValueBindings(container);
 
-            // submit task to run thread
-            submitRunContinuation();
+            // submit task to run thread, using externalSubmit if possible
+            externalSubmitRunContinuation();
             started = true;
         } finally {
             if (!started) {
                 afterDone(addedToContainer);
             }
@@ -663,18 +720,10 @@
      * @param nanos the waiting time in nanoseconds
      */
     private void parkOnCarrierThread(boolean timed, long nanos) {
         assert state() == RUNNING;
 
-        VirtualThreadPinnedEvent event;
-        try {
-            event = new VirtualThreadPinnedEvent();
-            event.begin();
-        } catch (OutOfMemoryError e) {
-            event = null;
-        }
-
         setState(timed ? TIMED_PINNED : PINNED);
         try {
             if (!parkPermit) {
                 if (!timed) {
                     U.park(false, 0);
@@ -686,40 +735,40 @@
             setState(RUNNING);
         }
 
         // consume parking permit
         setParkPermit(false);
-
-        if (event != null) {
-            try {
-                event.commit();
-            } catch (OutOfMemoryError e) {
-                // ignore
-            }
-        }
     }
 
+    /**
+     * jdk.VirtualThreadPinned is emitted by HotSpot VM when pinned. Call into VM to
+     * emit event to avoid having a JFR event in Java with the same name (but different ID)
+     * to events emitted by the VM.
+     */
+    private static native void virtualThreadPinnedEvent(int reason, String reasonString);
+
     /**
      * Schedule this virtual thread to be unparked after a given delay.
      */
     @ChangesCurrentThread
     private Future<?> scheduleUnpark(long nanos) {
         assert Thread.currentThread() == this;
         // need to switch to current carrier thread to avoid nested parking
         switchToCarrierThread();
         try {
-            return UNPARKER.schedule(this::unpark, nanos, NANOSECONDS);
+            return delayedTaskScheduler().schedule(this::unpark, nanos, NANOSECONDS);
         } finally {
             switchToVirtualThread(this);
         }
     }
 
     /**
      * Cancels a task if it has not completed.
      */
     @ChangesCurrentThread
     private void cancel(Future<?> future) {
+        assert Thread.currentThread() == this;
         if (!future.isDone()) {
             // need to switch to current carrier thread to avoid nested parking
             switchToCarrierThread();
             try {
                 future.cancel(false);
@@ -754,25 +803,38 @@
                 } else {
                     submitRunContinuation();
                 }
             } else if ((s == PINNED) || (s == TIMED_PINNED)) {
                 // unpark carrier thread when pinned
-                notifyJvmtiDisableSuspend(true);
+                disableSuspendAndPreempt();
                 try {
                     synchronized (carrierThreadAccessLock()) {
                         Thread carrier = carrierThread;
                         if (carrier != null && ((s = state()) == PINNED || s == TIMED_PINNED)) {
                             U.unpark(carrier);
                         }
                     }
                 } finally {
-                    notifyJvmtiDisableSuspend(false);
+                    enableSuspendAndPreempt();
                 }
             }
         }
     }
 
+    /**
+     * Re-enables this virtual thread for scheduling after blocking on monitor enter.
+     * @throws RejectedExecutionException if the scheduler cannot accept a task
+     */
+    private void unblock() {
+        assert !Thread.currentThread().isVirtual();
+        unblocked = true;
+        if (state() == BLOCKED && compareAndSetState(BLOCKED, UNBLOCKED)) {
+            unblocked = false;
+            submitRunContinuation();
+        }
+    }
+
     /**
      * Attempts to yield the current virtual thread (Thread.yield).
      */
     void tryYield() {
         assert Thread.currentThread() == this;
@@ -857,28 +919,28 @@
         return true;
     }
 
     @Override
     void blockedOn(Interruptible b) {
-        notifyJvmtiDisableSuspend(true);
+        disableSuspendAndPreempt();
         try {
             super.blockedOn(b);
         } finally {
-            notifyJvmtiDisableSuspend(false);
+            enableSuspendAndPreempt();
         }
     }
 
     @Override
     @SuppressWarnings("removal")
     public void interrupt() {
         if (Thread.currentThread() != this) {
             checkAccess();
 
             // if current thread is a virtual thread then prevent it from being
-            // suspended when entering or holding interruptLock
+            // suspended or unmounted when entering or holding interruptLock
             Interruptible blocker;
-            notifyJvmtiDisableSuspend(true);
+            disableSuspendAndPreempt();
             try {
                 synchronized (interruptLock) {
                     interrupted = true;
                     blocker = nioBlocker();
                     if (blocker != null) {
@@ -888,17 +950,18 @@
                     // interrupt carrier thread if mounted
                     Thread carrier = carrierThread;
                     if (carrier != null) carrier.setInterrupt();
                 }
             } finally {
-                notifyJvmtiDisableSuspend(false);
+                enableSuspendAndPreempt();
             }
 
             // notify blocker after releasing interruptLock
             if (blocker != null) {
                 blocker.postInterrupt();
             }
+
         } else {
             interrupted = true;
             carrierThread.setInterrupt();
         }
         unpark();
@@ -912,18 +975,18 @@
     @Override
     boolean getAndClearInterrupt() {
         assert Thread.currentThread() == this;
         boolean oldValue = interrupted;
         if (oldValue) {
-            notifyJvmtiDisableSuspend(true);
+            disableSuspendAndPreempt();
             try {
                 synchronized (interruptLock) {
                     interrupted = false;
                     carrierThread.clearInterrupt();
                 }
             } finally {
-                notifyJvmtiDisableSuspend(false);
+                enableSuspendAndPreempt();
             }
         }
         return oldValue;
     }
 
@@ -942,22 +1005,35 @@
                 }
             case UNPARKED:
             case YIELDED:
                 // runnable, not mounted
                 return Thread.State.RUNNABLE;
+            case UNBLOCKED:
+                // if designated responsible thread for monitor then thread is blocked
+                if (isResponsibleForMonitor()) {
+                    return Thread.State.BLOCKED;
+                } else {
+                    return Thread.State.RUNNABLE;
+                }
             case RUNNING:
+                // if designated responsible thread for monitor then thread is blocked
+                if (isResponsibleForMonitor()) {
+                    return Thread.State.BLOCKED;
+                }
                 // if mounted then return state of carrier thread
-                notifyJvmtiDisableSuspend(true);
-                try {
-                    synchronized (carrierThreadAccessLock()) {
-                        Thread carrierThread = this.carrierThread;
-                        if (carrierThread != null) {
-                            return carrierThread.threadState();
+                if (Thread.currentThread() != this) {
+                    disableSuspendAndPreempt();
+                    try {
+                        synchronized (carrierThreadAccessLock()) {
+                            Thread carrierThread = this.carrierThread;
+                            if (carrierThread != null) {
+                                return carrierThread.threadState();
+                            }
                         }
+                    } finally {
+                        enableSuspendAndPreempt();
                     }
-                } finally {
-                    notifyJvmtiDisableSuspend(false);
                 }
                 // runnable, mounted
                 return Thread.State.RUNNABLE;
             case PARKING:
             case TIMED_PARKING:
@@ -968,17 +1044,28 @@
             case PINNED:
                 return State.WAITING;
             case TIMED_PARKED:
             case TIMED_PINNED:
                 return State.TIMED_WAITING;
+            case BLOCKING:
+            case BLOCKED:
+                return State.BLOCKED;
             case TERMINATED:
                 return Thread.State.TERMINATED;
             default:
                 throw new InternalError();
         }
     }
 
+    /**
+     * Returns true if thread is the designated responsible thread for a monitor.
+     * See objectMonitor.cpp for details.
+     */
+    private boolean isResponsibleForMonitor() {
+        return (recheckInterval > 0);
+    }
+
     @Override
     boolean alive() {
         int s = state;
         return (s != NEW && s != TERMINATED);
     }
@@ -1013,17 +1100,17 @@
                 return new StackTraceElement[0];  // unmounted, empty stack
             }
             case RUNNING, PINNED, TIMED_PINNED -> {
                 return null;   // mounted
             }
-            case PARKED, TIMED_PARKED -> {
+            case PARKED, TIMED_PARKED, BLOCKED -> {
                 // unmounted, not runnable
             }
-            case UNPARKED, YIELDED -> {
+            case UNPARKED, UNBLOCKED, YIELDED -> {
                 // unmounted, runnable
             }
-            case PARKING, TIMED_PARKING, YIELDING -> {
+            case PARKING, TIMED_PARKING, BLOCKING, YIELDING -> {
                 return null;  // in transition
             }
             default -> throw new InternalError("" + initialState);
         }
 
@@ -1040,18 +1127,22 @@
         } finally {
             assert state == suspendedState;
             setState(initialState);
         }
         boolean resubmit = switch (initialState) {
-            case UNPARKED, YIELDED -> {
+            case UNPARKED, UNBLOCKED, YIELDED -> {
                 // resubmit as task may have run while suspended
                 yield true;
             }
             case PARKED, TIMED_PARKED -> {
                 // resubmit if unparked while suspended
                 yield parkPermit && compareAndSetState(initialState, UNPARKED);
             }
+            case BLOCKED -> {
+                // resubmit if unblocked while suspended
+                yield unblocked && compareAndSetState(BLOCKED, UNBLOCKED);
+            }
             default -> throw new InternalError();
         };
         if (resubmit) {
             submitRunContinuation();
         }
@@ -1066,46 +1157,72 @@
         if (!name.isEmpty()) {
             sb.append(",");
             sb.append(name);
         }
         sb.append("]/");
-        Thread carrier = carrierThread;
-        if (carrier != null) {
-            // include the carrier thread state and name when mounted
-            notifyJvmtiDisableSuspend(true);
+
+        // add the carrier state and thread name when mounted
+        boolean mounted;
+        if (Thread.currentThread() == this) {
+            mounted = appendCarrierInfo(sb);
+        } else {
+            disableSuspendAndPreempt();
             try {
                 synchronized (carrierThreadAccessLock()) {
-                    carrier = carrierThread;
-                    if (carrier != null) {
-                        String stateAsString = carrier.threadState().toString();
-                        sb.append(stateAsString.toLowerCase(Locale.ROOT));
-                        sb.append('@');
-                        sb.append(carrier.getName());
-                    }
+                    mounted = appendCarrierInfo(sb);
                 }
             } finally {
-                notifyJvmtiDisableSuspend(false);
+                enableSuspendAndPreempt();
             }
         }
-        // include virtual thread state when not mounted
-        if (carrier == null) {
+
+        // add virtual thread state when not mounted
+        if (!mounted) {
             String stateAsString = threadState().toString();
             sb.append(stateAsString.toLowerCase(Locale.ROOT));
         }
+
         return sb.toString();
     }
 
+    /**
+     * Appends the carrier state and thread name to the string buffer if mounted.
+     * @return true if mounted, false if not mounted
+     */
+    private boolean appendCarrierInfo(StringBuilder sb) {
+        assert Thread.currentThread() == this || Thread.holdsLock(carrierThreadAccessLock());
+        Thread carrier = carrierThread;
+        if (carrier != null) {
+            String stateAsString = carrier.threadState().toString();
+            sb.append(stateAsString.toLowerCase(Locale.ROOT));
+            sb.append('@');
+            sb.append(carrier.getName());
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     @Override
     public int hashCode() {
         return (int) threadId();
     }
 
     @Override
     public boolean equals(Object obj) {
         return obj == this;
     }
 
+    /**
+     * Returns a ScheduledExecutorService to execute a delayed task.
+     */
+    private ScheduledExecutorService delayedTaskScheduler() {
+        long tid = Thread.currentThread().threadId();
+        int index = (int) tid & (DELAYED_TASK_SCHEDULERS.length - 1);
+        return DELAYED_TASK_SCHEDULERS[index];
+    }
+
     /**
      * Returns the termination object, creating it if needed.
      */
     private CountDownLatch getTermination() {
         CountDownLatch termination = this.termination;
@@ -1125,10 +1242,26 @@
     private Object carrierThreadAccessLock() {
         // return interruptLock as unmount has to coordinate with interrupt
         return interruptLock;
     }
 
+    /**
+     * Disallow the current thread be suspended or preempted.
+     */
+    private void disableSuspendAndPreempt() {
+        notifyJvmtiDisableSuspend(true);
+        Continuation.pin();
+    }
+
+    /**
+     * Allow the current thread be suspended or preempted.
+     */
+    private void enableSuspendAndPreempt() {
+        Continuation.unpin();
+        notifyJvmtiDisableSuspend(false);
+    }
+
     // -- wrappers for get/set of state, parking permit, and carrier thread --
 
     private int state() {
         return state;  // volatile read
     }
@@ -1139,10 +1272,14 @@
 
     private boolean compareAndSetState(int expectedValue, int newValue) {
         return U.compareAndSetInt(this, STATE, expectedValue, newValue);
     }
 
+    private boolean compareAndSetOnWaitingList(byte expectedValue, byte newValue) {
+        return U.compareAndSetByte(this, ON_WAITING_LIST, expectedValue, newValue);
+    }
+
     private void setParkPermit(boolean newValue) {
         if (parkPermit != newValue) {
             parkPermit = newValue;
         }
     }
@@ -1158,10 +1295,13 @@
     private void setCarrierThread(Thread carrier) {
         // U.putReferenceRelease(this, CARRIER_THREAD, carrier);
         this.carrierThread = carrier;
     }
 
+    @IntrinsicCandidate
+    private static native void setLockId(long tid);
+
     // -- JVM TI support --
 
     @IntrinsicCandidate
     @JvmtiMountTransition
     private native void notifyJvmtiStart();
@@ -1227,39 +1367,77 @@
         };
         return AccessController.doPrivileged(pa);
     }
 
     /**
-     * Creates the ScheduledThreadPoolExecutor used for timed unpark.
+     * Invoked by the VM for the Thread.vthread_scheduler diagnostic command.
      */
-    private static ScheduledExecutorService createDelayedTaskScheduler() {
-        String propValue = GetPropertyAction.privilegedGetProperty("jdk.unparker.maxPoolSize");
-        int poolSize;
+    private static byte[] printDefaultScheduler() {
+        return String.format("%s%n", DEFAULT_SCHEDULER.toString())
+                .getBytes(StandardCharsets.UTF_8);
+    }
+
+    /**
+     * Creates the ScheduledThreadPoolExecutors used to execute delayed tasks.
+     */
+    private static ScheduledExecutorService[] createDelayedTaskSchedulers() {
+        String propName = "jdk.virtualThreadScheduler.timerQueues";
+        String propValue = GetPropertyAction.privilegedGetProperty(propName);
+        int queueCount;
         if (propValue != null) {
-            poolSize = Integer.parseInt(propValue);
+            queueCount = Integer.parseInt(propValue);
+            if (queueCount != Integer.highestOneBit(queueCount)) {
+                throw new RuntimeException("Value of " + propName + " must be power of 2");
+            }
         } else {
-            poolSize = 1;
+            int ncpus = Runtime.getRuntime().availableProcessors();
+            queueCount = Math.max(Integer.highestOneBit(ncpus / 4), 1);
+        }
+        var schedulers = new ScheduledExecutorService[queueCount];
+        for (int i = 0; i < queueCount; i++) {
+            ScheduledThreadPoolExecutor stpe = (ScheduledThreadPoolExecutor)
+                Executors.newScheduledThreadPool(1, task -> {
+                    Thread t = InnocuousThread.newThread("VirtualThread-unparker", task);
+                    t.setDaemon(true);
+                    return t;
+                });
+            stpe.setRemoveOnCancelPolicy(true);
+            schedulers[i] = stpe;
         }
-        ScheduledThreadPoolExecutor stpe = (ScheduledThreadPoolExecutor)
-            Executors.newScheduledThreadPool(poolSize, task -> {
-                return InnocuousThread.newThread("VirtualThread-unparker", task);
-            });
-        stpe.setRemoveOnCancelPolicy(true);
-        return stpe;
+        return schedulers;
     }
 
     /**
-     * Reads the value of the jdk.tracePinnedThreads property to determine if stack
-     * traces should be printed when a carrier thread is pinned when a virtual thread
-     * attempts to park.
+     * Schedule virtual threads that are ready to be scheduled after they blocked on
+     * monitor enter.
      */
-    private static int tracePinningMode() {
-        String propValue = GetPropertyAction.privilegedGetProperty("jdk.tracePinnedThreads");
-        if (propValue != null) {
-            if (propValue.length() == 0 || "full".equalsIgnoreCase(propValue))
-                return 1;
-            if ("short".equalsIgnoreCase(propValue))
-                return 2;
+    private static void unblockVirtualThreads() {
+        while (true) {
+            VirtualThread vthread = takeVirtualThreadListToUnblock();
+            while (vthread != null) {
+                assert vthread.onWaitingList == 1;
+                VirtualThread nextThread = vthread.next;
+
+                // remove from list and unblock
+                vthread.next = null;
+                boolean changed = vthread.compareAndSetOnWaitingList((byte) 1, (byte) 0);
+                assert changed;
+                vthread.unblock();
+
+                vthread = nextThread;
+            }
         }
-        return 0;
+    }
+
+    /**
+     * Retrieves the list of virtual threads that are waiting to be unblocked, waiting
+     * if necessary until a list of one or more threads becomes available.
+     */
+    private static native VirtualThread takeVirtualThreadListToUnblock();
+
+    static {
+        var unblocker = InnocuousThread.newThread("VirtualThread-unblocker",
+                VirtualThread::unblockVirtualThreads);
+        unblocker.setDaemon(true);
+        unblocker.start();
     }
 }
diff a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java
--- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java
+++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java
@@ -292,14 +292,11 @@
      * illustrates some of the implementation decisions in this class.
      *
      *  * Slot k must be read with an acquiring read, which it must
      *    anyway to dereference and run the task if the (acquiring)
      *    CAS succeeds, but uses an explicit acquire fence to support
-     *    the following rechecks even if the CAS is not attempted.  To
-     *    more easily distinguish among kinds of CAS failures, we use
-     *    the compareAndExchange version, and usually handle null
-     *    returns (indicating contention) separately from others.
+     *    the following rechecks even if the CAS is not attempted.
      *
      *  * q.base may change between reading and using its value to
      *    index the slot. To avoid trying to use the wrong t, the
      *    index and slot must be reread (not necessarily immediately)
      *    until consistent, unless this is a local poll by owner, in
@@ -561,32 +558,20 @@
      *    opportunities to over-signal.  Note that in either of these
      *    contexts, signals may be (and often are) unnecessary because
      *    active workers continue scanning after running tasks without
      *    the need to be signalled (which is one reason work stealing
      *    is often faster than alternatives), so additional workers
-     *    aren't needed. We filter out some of these cases by exiting
-     *    retry loops in signalWork if the task responsible for the
-     *    signal has already been taken.
+     *    aren't needed.
      *
      * * For rapidly branching tasks that require full pool resources,
      *   oversignalling is OK, because signalWork will soon have no
      *   more workers to create or reactivate. But for others (mainly
      *   externally submitted tasks), overprovisioning may cause very
      *   noticeable slowdowns due to contention and resource
-     *   wastage. All remedies are intrinsically heuristic. We use a
-     *   strategy that works well in most cases: We track "sources"
-     *   (queue ids) of non-empty (usually polled) queues while
-     *   scanning. These are maintained in the "source" field of
-     *   WorkQueues for use in method helpJoin and elsewhere (see
-     *   below). We also maintain them as arguments/results of
-     *   top-level polls (argument "window" in method scan, with setup
-     *   in method runWorker) as an encoded sliding window of current
-     *   and previous two sources (or INVALID_ID if none), and stop
-     *   signalling when all were from the same source. These
-     *   mechanisms may result in transiently too few workers, but
-     *   once workers poll from a new source, they rapidly reactivate
-     *   others.
+     *   wastage. We reduce impact by deactivating workers when
+     *   queues don't have accessible tasks, but reactivating and
+     *   rescanning if other tasks remain.
      *
      * * Despite these, signal contention and overhead effects still
      *   occur during ramp-up and ramp-down of small computations.
      *
      * Scanning. Method scan performs top-level scanning for (and
@@ -596,29 +581,30 @@
      * method as WorkQueue.poll(), but restarts with a different
      * permutation on each invocation.  The pseudorandom generator
      * need not have high-quality statistical properties in the long
      * term. We use Marsaglia XorShifts, seeded with the Weyl sequence
      * from ThreadLocalRandom probes, which are cheap and
-     * suffice. Scans do not otherwise explicitly take into account
-     * core affinities, loads, cache localities, etc, However, they do
-     * exploit temporal locality (which usually approximates these) by
-     * preferring to re-poll from the same queue (either in method
-     * tryPoll() or scan) after a successful poll before trying others
-     * (see method topLevelExec), which also reduces bookkeeping,
-     * cache traffic, and scanning overhead. But it also reduces
-     * fairness, which is partially counteracted by giving up on
-     * contention.
+     * suffice. Each queue's polling attempt uses a bounded retry
+     * (MAX_SCAN_RETRIES) to avoid becoming stuck when other
+     * scanners/pollers stall.  Scans do not otherwise explicitly take
+     * into account core affinities, loads, cache localities, etc,
+     * However, they do exploit temporal locality (which usually
+     * approximates these) by preferring to re-poll from the same
+     * queue after a successful poll before trying others, which also
+     * reduces bookkeeping, cache traffic, and scanning overhead. But
+     * it also reduces fairness, which is partially counteracted by
+     * giving up on contention.
      *
      * Deactivation. When method scan indicates that no tasks are
-     * found by a worker, it tries to deactivate (in awaitWork),
-     * giving up (and rescanning) on ctl contention. To avoid missed
-     * signals during deactivation, the method rescans and reactivates
-     * if there may have been a missed signal during deactivation,
-     * filtering out most cases in which this is unnecessary. Because
-     * idle workers are often not yet blocked (parked), we use the
-     * WorkQueue parking field to advertise that a waiter actually
-     * needs unparking upon signal.
+     * found by a worker, it tries to deactivate(), giving up (and
+     * rescanning) on ctl contention. To avoid missed signals during
+     * deactivation, the method rescans and reactivates if there may
+     * have been a missed signal during deactivation, filtering out
+     * most cases in which this is unnecessary. Because idle workers
+     * are often not yet blocked (parked), we use the WorkQueue
+     * parking field to advertise that a waiter actually needs
+     * unparking upon signal.
      *
      * Quiescence. Workers scan looking for work, giving up when they
      * don't find any, without being sure that none are available.
      * However, some required functionality relies on consensus about
      * quiescence (also termination, discussed below). The count
@@ -666,13 +652,13 @@
      *
      * Trimming workers. To release resources after periods of lack of
      * use, a worker starting to wait when the pool is quiescent will
      * time out and terminate if the pool has remained quiescent for
      * period given by field keepAlive (default 60sec), which applies
-     * to the first timeout of a fully populated pool. Subsequent (or
-     * other) cases use delays such that, if still quiescent, all will
-     * be released before one additional keepAlive unit elapses.
+     * to the first timeout of a quiescent pool. Subsequent cases use
+     * minimal delays such that, if still quiescent, all will be
+     * released soon therafter.
      *
      * Joining Tasks
      * =============
      *
      * The "Join" part of ForkJoinPools consists of a set of
@@ -895,14 +881,15 @@
      * arrays are better for locality and reduce GC scan time, but
      * large arrays reduce both direct false-sharing and indirect
      * cases due to GC bookkeeping (cardmarks etc), and reduce the
      * number of resizes, which are not especially fast because they
      * require atomic transfers.  Currently, arrays are initialized to
-     * be fairly small.  (Maintenance note: any changes in fields,
-     * queues, or their uses, or JVM layout policies, must be
-     * accompanied by re-evaluation of these placement and sizing
-     * decisions.)
+     * be fairly small but early resizes rapidly increase size by more
+     * than a factor of two until very large. (Maintenance note: any
+     * changes in fields, queues, or their uses, or JVM layout
+     * policies, must be accompanied by re-evaluation of these
+     * placement and sizing decisions.)
      *
      * Style notes
      * ===========
      *
      * Memory ordering relies mainly on atomic operations (CAS,
@@ -952,28 +939,10 @@
      * (5) Internal control methods
      * (6) Callbacks and other support for ForkJoinTask methods
      * (7) Exported methods
      * (8) Static block initializing statics in minimally dependent order
      *
-     * Revision notes
-     * ==============
-     *
-     * The main sources of differences from previous version are:
-     *
-     * * New abstract class ForkJoinTask.InterruptibleTask ensures
-     *   handling of tasks submitted under the ExecutorService
-     *   API are consistent with specs.
-     * * Method quiescent() replaces previous quiescence-related
-     *   checks, relying on versioning and sequence locking instead
-     *   of ReentrantLock.
-     * * Termination processing now ensures that internal data
-     *   structures are maintained consistently enough while stopping
-     *   to interrupt all workers and cancel all tasks. It also uses a
-     *   CountDownLatch instead of a Condition for termination because
-     *   of lock change.
-     * * Many other changes to avoid performance regressions due
-     *   to the above.
      */
 
     // static configuration constants
 
     /**
@@ -981,11 +950,12 @@
      * to park waiting for new work before terminating.
      */
     static final long DEFAULT_KEEPALIVE = 60_000L;
 
     /**
-     * Undershoot tolerance for idle timeouts
+     * Undershoot tolerance for idle timeouts, also serving as the
+     * minimum allowed timeout value.
      */
     static final long TIMEOUT_SLOP = 20L;
 
     /**
      * The default value for common pool maxSpares.  Overridable using
@@ -1027,21 +997,15 @@
     // {pool, workQueue} config bits
     static final int FIFO             = 1 << 0;   // fifo queue or access mode
     static final int CLEAR_TLS        = 1 << 1;   // set for Innocuous workers
     static final int PRESET_SIZE      = 1 << 2;   // size was set by property
 
-    // source history window packing used in scan() and runWorker()
-    static final long RESCAN          = 1L << 63; // must be negative
-    static final long HMASK           = ((((long)SMASK) << 32) |
-                                         (((long)SMASK) << 16)); // history bits
-    static final long NO_HISTORY      = ((((long)INVALID_ID) << 32) | // no 3rd
-                                         (((long)INVALID_ID) << 16)); // no 2nd
-
     // others
     static final int DEREGISTERED     = 1 << 31;  // worker terminating
     static final int UNCOMPENSATE     = 1 << 16;  // tryCompensate return
     static final int IDLE             = 1 << 16;  // phase seqlock/version count
+    static final int MAX_SCAN_RETRIES = 8;        // scan retry limit
 
     /*
      * Bits and masks for ctl and bounds are packed with 4 16 bit subfields:
      * RC: Number of released (unqueued) workers
      * TC: Number of total workers
@@ -1226,22 +1190,18 @@
         // Support for atomic operations
         private static final Unsafe U;
         private static final long PHASE;
         private static final long BASE;
         private static final long TOP;
-        private static final long SOURCE;
         private static final long ARRAY;
 
         final void updateBase(int v) {
             U.putIntVolatile(this, BASE, v);
         }
         final void updateTop(int v) {
             U.putIntOpaque(this, TOP, v);
         }
-        final void forgetSource() {
-            U.putIntOpaque(this, SOURCE, 0);
-        }
         final void updateArray(ForkJoinTask<?>[] a) {
             U.getAndSetReference(this, ARRAY, a);
         }
         final void unlockPhase() {
             U.getAndAddInt(this, PHASE, IDLE);
@@ -1289,46 +1249,57 @@
          * @param internal if caller owns this queue
          * @throws RejectedExecutionException if array could not be resized
          */
         final void push(ForkJoinTask<?> task, ForkJoinPool pool,
                         boolean internal) {
-            int s = top, b = base, cap, m, p, room, newCap; ForkJoinTask<?>[] a;
-            if ((a = array) == null || (cap = a.length) <= 0 ||
-                (room = (m = cap - 1) - (s - b)) < 0) { // could not resize
+            int s = top, b = base, m, cap, room; ForkJoinTask<?>[] a;
+            if ((a = array) != null && (cap = a.length) > 0) { // else disabled
+                if ((room = (m = cap - 1) - (s - b)) >= 0) {
+                    top = s + 1;
+                    long pos = slotOffset(m & s);
+                    if (!internal)
+                        U.putReference(a, pos, task);       // inside lock
+                    else
+                        U.getAndSetReference(a, pos, task); // fully fenced
+                    if (room == 0)                          // resize
+                        growArray(a, cap, s);
+                }
                 if (!internal)
                     unlockPhase();
-                throw new RejectedExecutionException("Queue capacity exceeded");
+                if (room < 0)
+                    throw new RejectedExecutionException("Queue capacity exceeded");
+                else if ((room == 0 || a[m & (s - 1)] == null) && pool != null)
+                    pool.signalWork();
             }
-            top = s + 1;
-            long pos = slotOffset(p = m & s);
-            if (!internal)
-                U.putReference(a, pos, task);         // inside lock
-            else
-                U.getAndSetReference(a, pos, task);   // fully fenced
-            if (room == 0 && (newCap = cap << 1) > 0) {
+        }
+
+        /**
+         * Resizes the queue array unless out of memory.
+         * @param a old array
+         * @param cap old array capacity
+         * @param s current top
+         */
+        private void growArray(ForkJoinTask<?>[] a, int cap, int s) {
+            int newCap = (cap < 1 << 24) ? cap << 2 : cap << 1;
+            if (a != null && a.length == cap && cap > 0 && newCap > 0) {
                 ForkJoinTask<?>[] newArray = null;
-                try {                                 // resize for next time
+                try {
                     newArray = new ForkJoinTask<?>[newCap];
                 } catch (OutOfMemoryError ex) {
                 }
                 if (newArray != null) {               // else throw on next push
-                    int newMask = newCap - 1;         // poll old, push to new
+                    int mask = cap - 1, newMask = newCap - 1;
                     for (int k = s, j = cap; j > 0; --j, --k) {
-                        ForkJoinTask<?> u;
+                        ForkJoinTask<?> u;            // poll old, push to new
                         if ((u = (ForkJoinTask<?>)U.getAndSetReference(
-                                 a, slotOffset(k & m), null)) == null)
+                                 a, slotOffset(k & mask), null)) == null)
                             break;                    // lost to pollers
                         newArray[k & newMask] = u;
                     }
-                    updateArray(newArray);            // fully fenced
+                    updateArray(newArray);           // fully fenced
                 }
-                a = null;                             // always signal
             }
-            if (!internal)
-                unlockPhase();
-            if ((a == null || a[m & (s - 1)] == null) && pool != null)
-                pool.signalWork(a, p);
         }
 
         /**
          * Takes next task, if one exists, in order specified by mode,
          * so acts as either local-pop or local-poll. Called only by owner.
@@ -1336,12 +1307,12 @@
          */
         private ForkJoinTask<?> nextLocalTask(int fifo) {
             ForkJoinTask<?> t = null;
             ForkJoinTask<?>[] a = array;
             int b = base, p = top, cap;
-            if (a != null && (cap = a.length) > 0) {
-                for (int m = cap - 1, s, nb; p - b > 0; ) {
+            if (p - b > 0 && a != null && (cap = a.length) > 0) {
+                for (int m = cap - 1, s, nb;;) {
                     if (fifo == 0 || (nb = b + 1) == p) {
                         if ((t = (ForkJoinTask<?>)U.getAndSetReference(
                                  a, slotOffset(m & (s = p - 1)), null)) != null)
                             updateTop(s);       // else lost race for only task
                         break;
@@ -1353,10 +1324,12 @@
                     }
                     while (b == (b = base)) {
                         U.loadFence();
                         Thread.onSpinWait();    // spin to reduce memory traffic
                     }
+                    if (p - b <= 0)
+                        break;
                 }
             }
             return t;
         }
 
@@ -1415,86 +1388,55 @@
          * Polls for a task. Used only by non-owners.
          *
          * @param pool if nonnull, pool to signal if more tasks exist
          */
         final ForkJoinTask<?> poll(ForkJoinPool pool) {
-            for (;;) {
-                ForkJoinTask<?>[] a = array;
-                int b = base, cap, k;
-                if (a == null || (cap = a.length) <= 0)
-                    break;
-                ForkJoinTask<?> t = a[k = b & (cap - 1)];
-                U.loadFence();
-                if (base == b) {
-                    Object o;
-                    int nb = b + 1, nk = nb & (cap - 1);
-                    if (t == null)
-                        o = a[k];
-                    else if (t == (o = U.compareAndExchangeReference(
-                                       a, slotOffset(k), t, null))) {
+            if ((phase & (IDLE | 1)) == 0 || top - base > 0) {
+                for (boolean stalled = false;;) {
+                    int cap, b, k, nb; ForkJoinTask<?>[] a;
+                    if ((a = array) == null || (cap = a.length) <= 0)
+                        break;
+                    long kp = slotOffset(k = (cap - 1) & (b = base));
+                    int nk = (nb = b + 1) & (cap - 1); // next slot
+                    int sk = (b + 2) & (cap - 1);      // 2nd slot ahead
+                    ForkJoinTask<?> t = a[k];
+                    U.loadFence();
+                    if (base != b)
+                        ;                              // inconsistent
+                    else if (t == null) {
+                        if (a[sk] == null && a[nk] == null && a[k] == null &&
+                            top - b <= 0)
+                            break;                     // empty
+                        if (stalled)
+                            Thread.onSpinWait();
+                        stalled = true;
+                        U.loadFence();                 // reread
+                    }
+                    else if (U.compareAndSetReference(a, kp, t, null)) {
                         updateBase(nb);
                         if (a[nk] != null && pool != null)
-                            pool.signalWork(a, nk); // propagate
+                            pool.signalWork();         // propagate
                         return t;
                     }
-                    if (o == null && a[nk] == null && array == a &&
-                        (phase & (IDLE | 1)) != 0 && top - base <= 0)
-                        break;                    // empty
-                }
-            }
-            return null;
-        }
-
-        /**
-         * Tries to poll next task in FIFO order, failing without
-         * retries on contention or stalls. Used only by topLevelExec
-         * to repoll from the queue obtained from pool.scan.
-         */
-        private ForkJoinTask<?> tryPoll() {
-            ForkJoinTask<?>[] a; int cap;
-            if ((a = array) != null && (cap = a.length) > 0) {
-                for (int b = base, k;;) {  // loop only if inconsistent
-                    ForkJoinTask<?> t = a[k = b & (cap - 1)];
-                    U.loadFence();
-                    if (b == (b = base)) {
-                        Object o;
-                        if (t == null)
-                            o = a[k];
-                        else if (t == (o = U.compareAndExchangeReference(
-                                           a, slotOffset(k), t, null))) {
-                            updateBase(b + 1);
-                            return t;
-                        }
-                        if (o == null)
-                            break;
-                    }
                 }
             }
             return null;
         }
 
         // specialized execution methods
 
         /**
-         * Runs the given (stolen) task if nonnull, as well as
-         * remaining local tasks and/or others available from the
-         * given queue, if any.
+         * Runs the given task, as well as remaining local tasks.
          */
-        final void topLevelExec(ForkJoinTask<?> task, WorkQueue src, int srcId) {
-            int cfg = config, fifo = cfg & FIFO, nstolen = nsteals + 1;
-            if ((srcId & 1) != 0) // don't record external sources
-                source = srcId;
-            if ((cfg & CLEAR_TLS) != 0)
-                ThreadLocalRandom.eraseThreadLocals(Thread.currentThread());
+        final void topLevelExec(ForkJoinTask<?> task, int cfg) {
+            int fifo = cfg & FIFO;
             while (task != null) {
                 task.doExec();
-                if ((task = nextLocalTask(fifo)) == null && src != null &&
-                    (task = src.tryPoll()) != null)
-                    ++nstolen;
+                task = nextLocalTask(fifo);
             }
-            nsteals = nstolen;
-            forgetSource();
+            if ((cfg & CLEAR_TLS) != 0)
+                ThreadLocalRandom.eraseThreadLocals(Thread.currentThread());
         }
 
         /**
          * Deep form of tryUnpush: Traverses from top and removes and
          * runs task if present.
@@ -1632,11 +1574,10 @@
             U = Unsafe.getUnsafe();
             Class<WorkQueue> klass = WorkQueue.class;
             PHASE = U.objectFieldOffset(klass, "phase");
             BASE = U.objectFieldOffset(klass, "base");
             TOP = U.objectFieldOffset(klass, "top");
-            SOURCE = U.objectFieldOffset(klass, "source");
             ARRAY = U.objectFieldOffset(klass, "array");
         }
     }
 
     // static fields (initialized in static initializer below)
@@ -1869,149 +1810,146 @@
             if ((src = w.source) != DEREGISTERED) { // else trimmed on timeout
                 w.source = DEREGISTERED;
                 if (phase != 0) {         // else failed to start
                     replaceable = true;
                     if ((phase & IDLE) != 0)
-                        reactivate(w);    // pool stopped before released
+                        releaseAll();     // pool stopped before released
                 }
             }
         }
-        long c = ctl;
-        if (src != DEREGISTERED)          // decrement counts
+        if (src != DEREGISTERED) {        // decrement counts
+            long c = ctl;
             do {} while (c != (c = compareAndExchangeCtl(
                                    c, ((RC_MASK & (c - RC_UNIT)) |
                                        (TC_MASK & (c - TC_UNIT)) |
                                        (LMASK & c)))));
-        else if ((int)c != 0)
-            replaceable = true;           // signal below to cascade timeouts
-        if (w != null) {                  // cancel remaining tasks
-            for (ForkJoinTask<?> t; (t = w.nextLocalTask()) != null; ) {
-                try {
-                    t.cancel(false);
-                } catch (Throwable ignore) {
+            if (w != null && w.top - w.base > 0) {
+                for (ForkJoinTask<?> t; (t = w.nextLocalTask()) != null; ) {
+                    try {                 // cancel remaining tasks
+                        t.cancel(false);
+                    } catch (Throwable ignore) {
+                    }
                 }
             }
         }
         if ((tryTerminate(false, false) & STOP) == 0 && w != null) {
             WorkQueue[] qs; int n, i;     // remove index unless terminating
             long ns = w.nsteals & 0xffffffffL;
-            int stop = lockRunState() & STOP;
-            if (stop == 0 && (qs = queues) != null && (n = qs.length) > 0 &&
-                qs[i = phase & SMASK & (n - 1)] == w) {
+            if ((lockRunState() & STOP) != 0)
+                replaceable = false;
+            else if ((qs = queues) != null && (n = qs.length) > 0 &&
+                     qs[i = phase & SMASK & (n - 1)] == w) {
                 qs[i] = null;
                 stealCount += ns;         // accumulate steals
             }
             unlockRunState();
+            if (replaceable)
+                signalWork();
         }
-        if ((runState & STOP) == 0 && replaceable)
-            signalWork(null, 0); // may replace unless trimmed or uninitialized
         if (ex != null)
             ForkJoinTask.rethrow(ex);
     }
 
     /**
-     * Releases an idle worker, or creates one if not enough exist,
-     * returning on contention if a signal task is already taken.
-     *
-     * @param a if nonnull, a task array holding task signalled
-     * @param k index of task in array
+     * Releases an idle worker, or creates one if not enough exist.
      */
-    final void signalWork(ForkJoinTask<?>[] a, int k) {
+    final void signalWork() {
         int pc = parallelism;
         for (long c = ctl;;) {
             WorkQueue[] qs = queues;
             long ac = (c + RC_UNIT) & RC_MASK, nc;
             int sp = (int)c, i = sp & SMASK;
-            if (qs == null || qs.length <= i)
+            if ((short)(c >>> RC_SHIFT) >= pc)
+                break;
+            if (qs == null)
+                break;
+            if (qs.length <= i)
                 break;
             WorkQueue w = qs[i], v = null;
+            Thread t = null;
             if (sp == 0) {
                 if ((short)(c >>> TC_SHIFT) >= pc)
                     break;
                 nc = ((c + TC_UNIT) & TC_MASK);
             }
-            else if ((short)(c >>> RC_SHIFT) >= pc || (v = w) == null)
+            else if ((v = w) == null)
                 break;
-            else
+            else {
+                t = v.owner;
                 nc = (v.stackPred & LMASK) | (c & TC_MASK);
+            }
             if (c == (c = compareAndExchangeCtl(c, nc | ac))) {
                 if (v == null)
                     createWorker();
                 else {
                     v.phase = sp;
                     if (v.parking != 0)
-                        U.unpark(v.owner);
+                        U.unpark(t);
                 }
                 break;
             }
-            if (a != null && k >= 0 && k < a.length && a[k] == null)
-                break;
         }
     }
 
     /**
      * Reactivates the given worker, and possibly others if not top of
      * ctl stack. Called only during shutdown to ensure release on
      * termination.
      */
-    private void reactivate(WorkQueue w) {
+    private void releaseAll() {
         for (long c = ctl;;) {
             WorkQueue[] qs; WorkQueue v; int sp, i;
-            if ((qs = queues) == null || (sp = (int)c) == 0 ||
-                qs.length <= (i = sp & SMASK) || (v = qs[i]) == null ||
-                (v != w && w != null && (w.phase & IDLE) == 0))
+            if ((sp = (int)c) == 0 || (qs = queues) == null ||
+                qs.length <= (i = sp & SMASK) || (v = qs[i]) == null)
                 break;
             if (c == (c = compareAndExchangeCtl(
                           c, ((UMASK & (c + RC_UNIT)) | (c & TC_MASK) |
                               (v.stackPred & LMASK))))) {
                 v.phase = sp;
-                if (v == w)
-                    break;
-                if (v.parking != 0)
-                    U.unpark(v.owner);
+                U.unpark(v.owner);
             }
         }
     }
 
     /**
      * Internal version of isQuiescent and related functionality.
-     * @return true if terminating or all workers are inactive and
-     * submission queues are empty and unlocked; if so, setting STOP
-     * if shutdown is enabled
+     * @return positive if stopping, nonnegative if terminating or all
+     * workers are inactive and submission queues are empty and
+     * unlocked; if so, setting STOP if shutdown is enabled
      */
-    private boolean quiescent() {
+    private int quiescent() {
         outer: for (;;) {
             long phaseSum = 0L;
             boolean swept = false;
             for (int e, prevRunState = 0; ; prevRunState = e) {
                 long c = ctl;
                 if (((e = runState) & STOP) != 0)
-                    return true;                          // terminating
+                    return 1;                         // terminating
                 else if ((c & RC_MASK) > 0L)
-                    return false;                         // at least one active
+                    return -1;                        // at least one active
                 else if (!swept || e != prevRunState || (e & RS_LOCK) != 0) {
                     long sum = c;
                     WorkQueue[] qs = queues; WorkQueue q;
                     int n = (qs == null) ? 0 : qs.length;
                     for (int i = 0; i < n; ++i) {         // scan queues
                         if ((q = qs[i]) != null) {
                             int p = q.phase, s = q.top, b = q.base;
                             sum += (p & 0xffffffffL) | ((long)b << 32);
                             if ((p & IDLE) == 0 || s - b > 0) {
                                 if ((i & 1) == 0 && compareAndSetCtl(c, c))
-                                    signalWork(null, 0);  // ensure live
-                                return false;
+                                    signalWork();
+                                return -1;
                             }
                         }
                     }
                     swept = (phaseSum == (phaseSum = sum));
                 }
                 else if ((e & SHUTDOWN) == 0)
-                    return true;
+                    return 0;
                 else if (compareAndSetCtl(c, c) && casRunState(e, e | STOP)) {
-                    interruptAll();                       // confirmed
-                    return true;                          // enable termination
+                    releaseAll();                         // confirmed
+                    return 1;                             // enable termination
                 }
                 else
                     break;                                // restart
             }
         }
@@ -2022,151 +1960,188 @@
      * See above for explanation.
      *
      * @param w caller's WorkQueue (may be null on failed initialization)
      */
     final void runWorker(WorkQueue w) {
-        if (w != null) {
-            int phase = w.phase, r = w.stackPred; // seed from registerWorker
-            long window = (long)((r >>> 16) & SMASK) | NO_HISTORY;
+        if (w != null) {              // use seed from registerWorker
+            int phase = w.phase, r = w.stackPred, cfg = w.config, stop;
             do {
+                stop = runState & STOP;
                 r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift
-            } while ((runState & STOP) == 0 &&
-                     (((window = scan(w, window, r)) < 0L ||
-                       ((phase = awaitWork(w, phase)) & IDLE) == 0)));
+            } while (stop == 0 &&
+                     (scan(w, r, cfg) ||
+                      ((phase = deactivate(w, phase)) & IDLE) == 0 ||
+                      ((phase = awaitWork(w, phase)) & IDLE) == 0));
         }
     }
 
     /**
      * Scans for and if found executes top-level tasks: Tries to poll
-     * each queue starting at initial index with random stride,
-     * returning next scan window and retry indicator.
+     * each queue starting at random index with random stride,
+     * returning retry indicator.
      *
      * @param w caller's WorkQueue
-     * @param window up to three queue indices
      * @param r random seed
-     * @return the next window to use, with RESCAN set for rescan
+     * @param cfg config bits
+     * @return true for rescan
      */
-    private long scan(WorkQueue w, long window, int r) {
-        WorkQueue[] qs = queues;
-        int n = (qs == null) ? 0 : qs.length, step = (r << 1) | 1;
-        long next = window & ~RESCAN;
-        outer: for (int i = (short)window, l = n; l > 0; --l, i += step) {
-            int j, cap; WorkQueue q; ForkJoinTask<?>[] a;
-            if ((q = qs[j = i & SMASK & (n - 1)]) != null &&
-                (a = q.array) != null && (cap = a.length) > 0) {
-                for (int b = q.base;;) {
-                    int nb = b + 1, nk = nb & (cap - 1), k;
-                    ForkJoinTask<?> t = a[k = b & (cap - 1)];
-                    U.loadFence();                // re-read b and t
-                    if (b == (b = q.base)) {      // else inconsistent; retry
-                        Object o;
-                        if (t == null)
-                            o = a[k];
-                        else if (t == (o = U.compareAndExchangeReference(
-                                           a, slotOffset(k), t, null))) {
-                            q.updateBase(nb);
-                            next = RESCAN | ((window << 16) & HMASK) | j;
-                            if (window != next && a[nk] != null)
-                                signalWork(a, nk); // limit propagation
-                            if (w != null)        // always true
-                                w.topLevelExec(t, q, j);
-                            break outer;
-                        }
-                        if (o == null) {
-                            if (next >= 0L && a[nk] != null)
-                                next |= RESCAN;
+    private boolean scan(WorkQueue w, int r, int cfg) {
+        WorkQueue[] qs; int n;
+        boolean rescan = false;
+        if ((qs = queues) != null && (n = qs.length) > 0 && w != null) {
+            int i = r, step = (r >>> 16) | 1, nstolen = 0;
+            sweep: for (int l = n; l > 0; --l, i += step) {
+                int j; WorkQueue q;
+                if ((q = qs[j = i & (n - 1)]) != null) {
+                    for (int retries = 0;;) {
+                        int cap, b, k, nb; ForkJoinTask<?>[] a;
+                        if ((a = q.array) == null || (cap = a.length) <= 0)
                             break;
+                        long kp = slotOffset(k = (cap - 1) & (b = q.base));
+                        int nk = (nb = b + 1) & (cap - 1); // next slot
+                        int sk = (b + 2) & (cap - 1);      // 2nd slot ahead
+                        ForkJoinTask<?> t = a[k];
+                        U.loadFence();
+                        if (q.base != b)
+                            ;                             // inconsistent
+                        else if (t == null) {
+                            if (a[sk] == null && a[nk] == null && a[k] == null)
+                                break;                    // probably empty
+                            U.loadFence();                // reread
+                        }
+                        else if (U.compareAndSetReference(a, kp, t, null)) {
+                            q.base = nb;
+                            w.source = j;                 // volatile write
+                            rescan = true;
+                            if (nstolen++ == 0 && a[nk] != null)
+                                signalWork();             // propagate signal
+                            w.topLevelExec(t, cfg);
+                            if (q.base == nb)
+                                continue;                 // no other pollers
+                        }
+                        if (++retries >= MAX_SCAN_RETRIES) {
+                            rescan = true;
+                            break sweep;                  // randomly move
                         }
                     }
                 }
             }
+            if (nstolen != 0)
+                w.nsteals += nstolen;
         }
-        return next;
+        return rescan;
     }
 
     /**
-     * Tries to inactivate, and if successful, awaits signal or termination.
+     * Deactivates after failing to find work; reactivates on ctl
+     * contention, signal, or possible missed signal
      *
      * @param w the worker (may be null if already terminated)
      * @param phase current phase
-     * @return current phase, with IDLE set if worker should exit
-     */
-    private int awaitWork(WorkQueue w, int phase) {
-        boolean quiet;                           // true if possibly quiescent
-        int active = phase + (IDLE << 1), p = phase | IDLE, e;
-        if (w != null) {
-            w.phase = p;                         // deactivate
-            long np = active & LMASK, pc = ctl;  // try to enqueue
-            long qc = np | ((pc - RC_UNIT) & UMASK);
+     * @return current phase, or IDLE for exit
+     */
+    private int deactivate(WorkQueue w, int phase) {
+        int p = IDLE;
+        if ((runState & STOP) == 0 && w != null) {
+            int active = phase + (IDLE << 1);
+            w.phase = phase | IDLE;
+            long pc = ctl, qc = (active & LMASK) | ((pc - RC_UNIT) & UMASK);
             w.stackPred = (int)pc;               // set ctl stack link
-            if (pc != (pc = compareAndExchangeCtl(pc, qc))) {
-                qc = np | ((pc - RC_UNIT) & UMASK);
-                w.stackPred = (int)pc;           // retry once
-                if (pc != (pc = compareAndExchangeCtl(pc, qc)))
-                    p = w.phase = phase;         // back out
-            }
-            if (p != phase && ((e = runState) & STOP) == 0 &&
-                (!(quiet = (qc & RC_MASK) <= 0L) || (e & SHUTDOWN) == 0 ||
-                 !(quiet = quiescent()) || (runState & STOP) == 0)) {
-                long deadline = 0L;              // not terminating
-                if (quiet) {                     // use timeout if trimmable
-                    int nt = (short)(qc >>> TC_SHIFT);
-                    long delay = keepAlive;      // scale if not at target
-                    if (nt != (nt = Math.max(nt, parallelism)) && nt > 0)
-                        delay = Math.max(TIMEOUT_SLOP, delay / nt);
-                    if ((deadline = delay + System.currentTimeMillis()) == 0L)
-                        deadline = 1L;           // avoid zero
-                }
-                boolean release = quiet;
+            if (!compareAndSetCtl(pc, qc))       // try to enqueue
+                p = w.phase = phase;             // back out on contention
+            else if ((qc & RC_MASK) > 0L || quiescent() <= 0) {
+                boolean release = false;
                 WorkQueue[] qs = queues;         // recheck queues
                 int n = (qs == null) ? 0 : qs.length;
                 for (int l = -n, j = active; l < n; ++l, ++j) {
-                    WorkQueue q; ForkJoinTask<?>[] a; int cap;
-                    if ((p = w.phase) == active) // interleave signal checks
-                        break;
-                    if ((q = qs[j & (n - 1)]) != null &&
-                        (a = q.array) != null && (cap = a.length) > 0 &&
-                        a[q.base & (cap - 1)] != null) {
-                        if (release && qc == ctl && compareAndSetCtl(qc, pc)) {
+                    WorkQueue q = qs[j & (n - 1)];
+                    if (((p = w.phase) & IDLE) == 0)
+                        break;                   // interleave signal checks
+                    if (q != null && q.top - q.base > 0) {
+                        if (!release)            // need multiple or reencounter
+                            release = true;
+                        else if (ctl == qc && compareAndSetCtl(qc, pc)) {
                             p = w.phase = active;
                             break;               // possible missed signal
                         }
-                        release = true;          // track multiple or reencounter
                     }
                     Thread.onSpinWait();         // reduce memory traffic
                 }
-                if (p != active) {               // emulate LockSupport.park
-                    LockSupport.setCurrentBlocker(this);
-                    w.parking = 1;
-                    for (;;) {
-                        if ((runState & STOP) != 0 || (p = w.phase) == active)
-                            break;
-                        U.park(deadline != 0L, deadline);
-                        if ((p = w.phase) == active || (runState & STOP) != 0)
-                            break;
-                        Thread.interrupted();    // clear for next park
-                        if (deadline != 0L && TIMEOUT_SLOP >
-                            deadline - System.currentTimeMillis()) {
-                            long sp = w.stackPred & LMASK, c = ctl;
-                            long nc = sp | (UMASK & (c - TC_UNIT));
-                            if (((int)c & SMASK) == (active & SMASK) &&
-                                compareAndSetCtl(c, nc)) {
-                                w.source = DEREGISTERED;
-                                w.phase = active;
-                                break;           // trimmed on timeout
-                            }
-                            deadline = 0L;       // no longer trimmable
-                        }
-                    }
-                    w.parking = 0;
-                    LockSupport.setCurrentBlocker(null);
-                }
             }
         }
         return p;
     }
 
+    /**
+     * Awaits signal or termination.
+     *
+     * @param w the worker (may be null if already terminated)
+     * @param phase current phase or IDLE if already exiting
+     * @return current phase, with IDLE set if worker should exit
+     */
+    private int awaitWork(WorkQueue w, int phase) {
+        if (phase != IDLE && w != null) {
+            long deadline = 0L, c;       // Use timeout if quiescent
+            long d = (w.source == INVALID_ID) ? TIMEOUT_SLOP : keepAlive;
+            if (((c = ctl) & RC_MASK) <= 0L && (int)c == phase + IDLE &&
+                (deadline = d + System.currentTimeMillis()) == 0L)
+                deadline = 1L;           // avoid zero
+            LockSupport.setCurrentBlocker(this);
+            w.parking = 1;               // enable unpark
+            for (;;) {
+                if ((runState & STOP) != 0)
+                    break;
+                if (((phase = w.phase) & IDLE) == 0)
+                    break;
+                U.park(deadline != 0L, deadline);
+                if (((phase = w.phase) & IDLE) == 0)
+                    break;
+                if ((runState & STOP) != 0)
+                    break;
+                Thread.interrupted();    // clear for next park
+                if (deadline != 0L && TIMEOUT_SLOP >
+                    deadline - System.currentTimeMillis()) {
+                    if (tryTrim(w))
+                        break;
+                    deadline = 0L;       // no longer trimmable
+                }
+            }
+            w.parking = 0;
+            LockSupport.setCurrentBlocker(null);
+        }
+        return phase;
+    }
+
+    /**
+     * Tries to remove and deregister worker after timeout, and release
+     * others to do the same. Fails if was instead signalled first.
+     */
+    private boolean tryTrim(WorkQueue w) {
+        int phase;
+        if (w != null && ((phase = w.phase) & IDLE) != 0) {
+            long sp = w.stackPred & LMASK, c = ctl;
+            long nc = sp | (UMASK & (c - TC_UNIT));
+            int active = phase + IDLE;
+            if ((int)c == active && compareAndSetCtl(c, nc)) {
+                WorkQueue[] vs; WorkQueue v; int vp, i;
+                w.source = DEREGISTERED;
+                w.phase = active;          // try to wake up next waiter
+                if ((vp = (int)nc) != 0 && (vs = queues) != null &&
+                    vs.length > (i = vp & SMASK) && (v = vs[i]) != null &&
+                    compareAndSetCtl(
+                        nc, ((UMASK & (nc + RC_UNIT)) |
+                             (nc & TC_MASK) | (v.stackPred & LMASK)))) {
+                    v.source = INVALID_ID; // enable cascaded timeouts
+                    v.phase = vp;
+                    U.unpark(v.owner);
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Scans for and returns a polled task, if available.  Used only
      * for untracked polls. Begins scan at a random index to avoid
      * systematic unfairness.
      *
@@ -2215,12 +2190,11 @@
             WorkQueue[] qs; WorkQueue v; int i;
             if ((qs = queues) != null && qs.length > (i = sp & SMASK) &&
                 (v = qs[i]) != null &&
                 compareAndSetCtl(c, (c & UMASK) | (v.stackPred & LMASK))) {
                 v.phase = sp;
-                if (v.parking != 0)
-                    U.unpark(v.owner);
+                U.unpark(v.owner);
                 stat = UNCOMPENSATE;
             }
         }
         else if (active > minActive && total >= pc) {   // reduce active workers
             if (compareAndSetCtl(c, ((c - RC_UNIT) & RC_MASK) | (c & ~RC_MASK)))
@@ -2259,11 +2233,10 @@
      * @param task the task
      * @param w caller's WorkQueue
      * @param internal true if w is owned by a ForkJoinWorkerThread
      * @return task status on exit, or UNCOMPENSATE for compensated blocking
      */
-
     final int helpJoin(ForkJoinTask<?> task, WorkQueue w, boolean internal) {
         if (w != null)
             w.tryRemoveAndExec(task, internal);
         int s = 0;
         if (task != null && (s = task.status) >= 0 && internal && w != null) {
@@ -2516,22 +2489,22 @@
      * @param nanos max wait time (Long.MAX_VALUE if effectively untimed)
      * @param interruptible true if return on interrupt
      * @return positive if quiescent, negative if interrupted, else 0
      */
     private int externalHelpQuiesce(long nanos, boolean interruptible) {
-        if (!quiescent()) {
+        if (quiescent() < 0) {
             long startTime = System.nanoTime();
             long maxSleep = Math.min(nanos >>> 8, MAX_SLEEP);
             for (int waits = 0;;) {
                 ForkJoinTask<?> t;
                 if (interruptible && Thread.interrupted())
                     return -1;
                 else if ((t = pollScan(false)) != null) {
                     waits = 0;
                     t.doExec();
                 }
-                else if (quiescent())
+                else if (quiescent() >= 0)
                     break;
                 else if (System.nanoTime() - startTime > nanos)
                     return 0;
                 else if (waits == 0)
                     waits = MIN_SLEEP;
@@ -2752,50 +2725,52 @@
      * if no work and no active workers
      * @param enable if true, terminate when next possible
      * @return runState on exit
      */
     private int tryTerminate(boolean now, boolean enable) {
-        int e = runState;
+        int e = runState, isShutdown;
         if ((e & STOP) == 0) {
             if (now) {
                 int s = lockRunState();
                 runState = e = (s + RS_LOCK) | STOP | SHUTDOWN;
                 if ((s & STOP) == 0)
                     interruptAll();
             }
-            else {
-                int isShutdown = (e & SHUTDOWN);
-                if (isShutdown == 0 && enable)
-                    getAndBitwiseOrRunState(isShutdown = SHUTDOWN);
-                if (isShutdown != 0)
-                    quiescent();                 // may trigger STOP
-                e = runState;
+            else if ((isShutdown = (e & SHUTDOWN)) != 0 || enable) {
+                if (isShutdown == 0)
+                    getAndBitwiseOrRunState(SHUTDOWN);
+                if (quiescent() > 0)
+                    e = runState;
             }
         }
         if ((e & (STOP | TERMINATED)) == STOP) { // help cancel tasks
-            int r = (int)Thread.currentThread().threadId(); // stagger traversals
-            WorkQueue[] qs = queues;
-            int n = (qs == null) ? 0 : qs.length;
-            for (int l = n; l > 0; --l, ++r) {
-                int j = r & SMASK & (n - 1); WorkQueue q; ForkJoinTask<?> t;
-                while ((q = qs[j]) != null && q.source != DEREGISTERED &&
-                       (t = q.poll(null)) != null) {
-                    try {
-                        t.cancel(false);
-                    } catch (Throwable ignore) {
+            if ((ctl & RC_MASK) > 0L) {          // unless was quiescent
+                int r = (int)Thread.currentThread().threadId();
+                WorkQueue[] qs = queues;         // stagger traversals
+                int n = (qs == null) ? 0 : qs.length;
+                for (int l = n; l > 0; --l, ++r) {
+                    WorkQueue q; ForkJoinTask<?> t;
+                    if ((q = qs[r & (n - 1)]) != null &&
+                        q.source != DEREGISTERED) {
+                        while ((t = q.poll(null)) != null) {
+                            try {
+                                t.cancel(false);
+                            } catch (Throwable ignore) {
+                            }
+                        }
                     }
                 }
             }
             if (((e = runState) & TERMINATED) == 0 && ctl == 0L) {
+                e |= TERMINATED;
                 if ((getAndBitwiseOrRunState(TERMINATED) & TERMINATED) == 0) {
                     CountDownLatch done; SharedThreadContainer ctr;
                     if ((done = termination) != null)
                         done.countDown();
                     if ((ctr = container) != null)
                         ctr.close();
                 }
-                e = runState;
             }
         }
         return e;
     }
 
@@ -3499,11 +3474,11 @@
      * threads remain inactive.
      *
      * @return {@code true} if all threads are currently idle
      */
     public boolean isQuiescent() {
-        return quiescent();
+        return quiescent() >= 0;
     }
 
     /**
      * Returns an estimate of the total number of completed tasks that
      * were executed by a thread other than their submitter. The
@@ -3986,19 +3961,21 @@
     /**
      * Invokes tryCompensate to create or re-activate a spare thread to
      * compensate for a thread that performs a blocking operation. When the
      * blocking operation is done then endCompensatedBlock must be invoked
      * with the value returned by this method to re-adjust the parallelism.
+     * @return value to use in endCompensatedBlock
      */
     final long beginCompensatedBlock() {
         int c;
         do {} while ((c = tryCompensate(ctl)) < 0);
         return (c == 0) ? 0L : RC_UNIT;
     }
 
     /**
      * Re-adjusts parallelism after a blocking operation completes.
+     * @param post value from beginCompensatedBlock
      */
     void endCompensatedBlock(long post) {
         if (post > 0L) {
             getAndAddCtl(post);
         }
diff a/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java
--- a/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java
+++ b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java
@@ -310,13 +310,12 @@
     private static final int CLOSED   = 2;
 
     // state: set to SHUTDOWN by any thread, set to CLOSED by owner, read by any thread
     private volatile int state;
 
-    // Counters to support checking that the task scope owner joins before processing
-    // results and attempts join before closing the task scope. These counters are
-    // accessed only by the owner thread.
+    // Counters to support checking that the task scope owner joins before closing the task
+    // scope. These counters are accessed only by the owner thread.
     private int forkRound;         // incremented when the first subtask is forked after join
     private int lastJoinAttempted; // set to the current fork round when join is attempted
     private int lastJoinCompleted; // set to the current fork round when join completes
 
     /**
@@ -368,32 +367,22 @@
         State state();
 
         /**
          * Returns the result of the subtask.
          *
-         * <p> To ensure correct usage, if the scope owner {@linkplain #fork(Callable) forks}
-         * a subtask, then it must join (with {@link #join() join} or {@link #joinUntil(Instant)
-         * joinUntil}) before it can obtain the result of the subtask.
-         *
          * @return the possibly-null result
-         * @throws IllegalStateException if the subtask has not completed, did not complete
-         * successfully, or the current thread is the task scope owner and did not join
-         * after forking
+         * @throws IllegalStateException if the subtask has not completed or did not
+         * complete successfully
          * @see State#SUCCESS
          */
         T get();
 
         /**
          * {@return the exception thrown by the subtask}
          *
-         * <p> To ensure correct usage, if the scope owner {@linkplain #fork(Callable) forks}
-         * a subtask, then it must join (with {@link #join() join} or {@link #joinUntil(Instant)
-         * joinUntil}) before it can obtain the exception thrown by the subtask.
-         *
-         * @throws IllegalStateException if the subtask has not completed, completed with
-         * a result, or the current thread is the task scope owner and did not join after
-         * forking
+         * @throws IllegalStateException if the subtask has not completed or completed
+         * with a result rather than an exception
          * @see State#FAILED
          */
         Throwable exception();
     }
 
@@ -467,20 +456,10 @@
         Thread currentThread = Thread.currentThread();
         if (currentThread != flock.owner() && !flock.containsThread(currentThread))
             throw new WrongThreadException("Current thread not owner or thread in the tree");
     }
 
-    /**
-     * Throws IllegalStateException if the current thread is the owner, and the owner did
-     * not join after forking a subtask in the given fork round.
-     */
-    private void ensureJoinedIfOwner(int round) {
-        if (Thread.currentThread() == flock.owner() && (round > lastJoinCompleted)) {
-            throw newIllegalStateExceptionNoJoin();
-        }
-    }
-
     /**
      * Ensures that the current thread is the owner of this task scope and that it joined
      * (with {@link #join()} or {@link #joinUntil(Instant)}) after {@linkplain #fork(Callable)
      * forking} subtasks.
      *
@@ -574,11 +553,11 @@
                 // new round if first fork after join
                 round++;
             }
         }
 
-        SubtaskImpl<U> subtask = new SubtaskImpl<>(this, task, round);
+        var subtask = new SubtaskImpl<U>(this, task);
         if (s < SHUTDOWN) {
             // create thread to run task
             Thread thread = factory.newThread(subtask);
             if (thread == null) {
                 throw new RejectedExecutionException("Rejected by thread factory");
@@ -842,11 +821,11 @@
             state = CLOSED;
         }
 
         // throw ISE if the owner didn't attempt to join after forking
         if (forkRound > lastJoinAttempted) {
-            lastJoinCompleted = forkRound;
+            lastJoinCompleted = forkRound;  // ensureOwnerAndJoined is a no-op after close
             throw newIllegalStateExceptionNoJoin();
         }
     }
 
     @Override
@@ -872,19 +851,15 @@
             }
         }
 
         private final StructuredTaskScope<? super T> scope;
         private final Callable<? extends T> task;
-        private final int round;
         private volatile Object result;
 
-        SubtaskImpl(StructuredTaskScope<? super T> scope,
-                    Callable<? extends T> task,
-                    int round) {
+        SubtaskImpl(StructuredTaskScope<? super T> scope, Callable<? extends T> task) {
             this.scope = scope;
             this.task = task;
-            this.round = round;
         }
 
         @Override
         public void run() {
             T result = null;
@@ -926,11 +901,10 @@
             }
         }
 
         @Override
         public T get() {
-            scope.ensureJoinedIfOwner(round);
             Object result = this.result;
             if (result instanceof AltResult) {
                 if (result == RESULT_NULL) return null;
             } else if (result != null) {
                 @SuppressWarnings("unchecked")
@@ -941,11 +915,10 @@
                     "Result is unavailable or subtask did not complete successfully");
         }
 
         @Override
         public Throwable exception() {
-            scope.ensureJoinedIfOwner(round);
             Object result = this.result;
             if (result instanceof AltResult alt && alt.state() == State.FAILED) {
                 return alt.exception();
             }
             throw new IllegalStateException(
diff a/src/java.base/share/classes/java/util/concurrent/locks/LockSupport.java b/src/java.base/share/classes/java/util/concurrent/locks/LockSupport.java
--- a/src/java.base/share/classes/java/util/concurrent/locks/LockSupport.java
+++ b/src/java.base/share/classes/java/util/concurrent/locks/LockSupport.java
@@ -33,11 +33,13 @@
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 package java.util.concurrent.locks;
 
-import jdk.internal.misc.VirtualThreads;
+import java.util.concurrent.TimeUnit;
+import jdk.internal.access.JavaLangAccess;
+import jdk.internal.access.SharedSecrets;
 import jdk.internal.misc.Unsafe;
 
 /**
  * Basic thread blocking primitives for creating locks and other
  * synchronization classes.
@@ -174,11 +176,11 @@
      *        this operation has no effect
      */
     public static void unpark(Thread thread) {
         if (thread != null) {
             if (thread.isVirtual()) {
-                VirtualThreads.unpark(thread);
+                JLA.unparkVirtualThread(thread);
             } else {
                 U.unpark(thread);
             }
         }
     }
@@ -214,11 +216,11 @@
     public static void park(Object blocker) {
         Thread t = Thread.currentThread();
         setBlocker(t, blocker);
         try {
             if (t.isVirtual()) {
-                VirtualThreads.park();
+                JLA.parkVirtualThread();
             } else {
                 U.park(false, 0L);
             }
         } finally {
             setBlocker(t, null);
@@ -262,11 +264,11 @@
         if (nanos > 0) {
             Thread t = Thread.currentThread();
             setBlocker(t, blocker);
             try {
                 if (t.isVirtual()) {
-                    VirtualThreads.park(nanos);
+                    JLA.parkVirtualThread(nanos);
                 } else {
                     U.park(false, nanos);
                 }
             } finally {
                 setBlocker(t, null);
@@ -309,15 +311,11 @@
      */
     public static void parkUntil(Object blocker, long deadline) {
         Thread t = Thread.currentThread();
         setBlocker(t, blocker);
         try {
-            if (t.isVirtual()) {
-                VirtualThreads.parkUntil(deadline);
-            } else {
-                U.park(true, deadline);
-            }
+            parkUntil(deadline);
         } finally {
             setBlocker(t, null);
         }
     }
 
@@ -364,11 +362,11 @@
      * the thread to park in the first place. Callers may also determine,
      * for example, the interrupt status of the thread upon return.
      */
     public static void park() {
         if (Thread.currentThread().isVirtual()) {
-            VirtualThreads.park();
+            JLA.parkVirtualThread();
         } else {
             U.park(false, 0L);
         }
     }
 
@@ -403,11 +401,11 @@
      * @param nanos the maximum number of nanoseconds to wait
      */
     public static void parkNanos(long nanos) {
         if (nanos > 0) {
             if (Thread.currentThread().isVirtual()) {
-                VirtualThreads.park(nanos);
+                JLA.parkVirtualThread(nanos);
             } else {
                 U.park(false, nanos);
             }
         }
     }
@@ -442,11 +440,12 @@
      * @param deadline the absolute time, in milliseconds from the Epoch,
      *        to wait until
      */
     public static void parkUntil(long deadline) {
         if (Thread.currentThread().isVirtual()) {
-            VirtualThreads.parkUntil(deadline);
+            long millis = deadline - System.currentTimeMillis();
+            JLA.parkVirtualThread(TimeUnit.MILLISECONDS.toNanos(millis));
         } else {
             U.park(true, deadline);
         }
     }
 
@@ -460,6 +459,7 @@
     // Hotspot implementation via intrinsics API
     private static final Unsafe U = Unsafe.getUnsafe();
     private static final long PARKBLOCKER
         = U.objectFieldOffset(Thread.class, "parkBlocker");
 
+    private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
 }
diff a/src/java.base/share/classes/javax/security/auth/Subject.java b/src/java.base/share/classes/javax/security/auth/Subject.java
--- a/src/java.base/share/classes/javax/security/auth/Subject.java
+++ b/src/java.base/share/classes/javax/security/auth/Subject.java
@@ -434,11 +434,11 @@
     public static <T> T callAs(final Subject subject,
             final Callable<T> action) throws CompletionException {
         Objects.requireNonNull(action);
         if (!SharedSecrets.getJavaLangAccess().allowSecurityManager()) {
             try {
-                return ScopedValue.callWhere(SCOPED_SUBJECT, subject, action);
+                return ScopedValue.callWhere(SCOPED_SUBJECT, subject, action::call);
             } catch (Exception e) {
                 throw new CompletionException(e);
             }
         } else {
             try {
diff a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java
--- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java
+++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java
@@ -502,15 +502,10 @@
      * Returns the Thread object for the current platform thread. If the
      * current thread is a virtual thread then this method returns the carrier.
      */
     Thread currentCarrierThread();
 
-    /**
-     * Executes the given value returning task on the current carrier thread.
-     */
-    <V> V executeOnCarrierThread(Callable<V> task) throws Exception;
-
     /**
      * Returns the value of the current carrier thread's copy of a thread-local.
      */
     <T> T getCarrierThreadLocal(CarrierThreadLocal<T> local);
 
diff a/src/java.base/share/classes/jdk/internal/event/VirtualThreadPinnedEvent.java b/src/java.base/share/classes/jdk/internal/event/VirtualThreadPinnedEvent.java
--- a/src/java.base/share/classes/jdk/internal/event/VirtualThreadPinnedEvent.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (c) 2021, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 jdk.internal.event;
-
-/**
- * Event recording that a virtual thread has parked on its carrier thread.
- */
-public class VirtualThreadPinnedEvent extends Event {
-}
diff a/src/java.base/share/classes/jdk/internal/misc/InternalLock.java b/src/java.base/share/classes/jdk/internal/misc/InternalLock.java
--- a/src/java.base/share/classes/jdk/internal/misc/InternalLock.java
+++ b/src/java.base/share/classes/jdk/internal/misc/InternalLock.java
@@ -37,14 +37,14 @@
  */
 public class InternalLock {
     private static final boolean CAN_USE_INTERNAL_LOCK;
     static {
         String s = System.getProperty("jdk.io.useMonitors");
-        if (s != null && (s.isEmpty() || s.equals("true"))) {
-            CAN_USE_INTERNAL_LOCK = false;
-        } else {
+        if (s != null && s.equals("false")) {
             CAN_USE_INTERNAL_LOCK = true;
+        } else {
+            CAN_USE_INTERNAL_LOCK = false;
         }
     }
 
     private final ReentrantLock lock;
 
diff a/src/java.base/share/classes/jdk/internal/misc/VirtualThreads.java b/src/java.base/share/classes/jdk/internal/misc/VirtualThreads.java
--- a/src/java.base/share/classes/jdk/internal/misc/VirtualThreads.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (c) 2018, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 jdk.internal.misc;
-
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.RejectedExecutionException;
-import jdk.internal.access.JavaLangAccess;
-import jdk.internal.access.SharedSecrets;
-
-/**
- * Defines static methods to support execution in the context of a virtual thread.
- */
-public final class VirtualThreads {
-    private static final JavaLangAccess JLA;
-    static {
-        JLA = SharedSecrets.getJavaLangAccess();
-        if (JLA == null) {
-            throw new InternalError("JavaLangAccess not setup");
-        }
-    }
-    private VirtualThreads() { }
-
-    /**
-     * Parks the current virtual thread until it is unparked or interrupted.
-     * If already unparked then the parking permit is consumed and this method
-     * completes immediately (meaning it doesn't yield). It also completes
-     * immediately if the interrupt status is set.
-     * @throws WrongThreadException if the current thread is not a virtual thread
-     */
-    public static void park() {
-        JLA.parkVirtualThread();
-    }
-
-    /**
-     * Parks the current virtual thread up to the given waiting time or until it
-     * is unparked or interrupted. If already unparked then the parking permit is
-     * consumed and this method completes immediately (meaning it doesn't yield).
-     * It also completes immediately if the interrupt status is set or the waiting
-     * time is {@code <= 0}.
-     * @param nanos the maximum number of nanoseconds to wait
-     * @throws WrongThreadException if the current thread is not a virtual thread
-     */
-    public static void park(long nanos) {
-        JLA.parkVirtualThread(nanos);
-    }
-
-    /**
-     * Parks the current virtual thread until the given deadline or until is is
-     * unparked or interrupted. If already unparked then the parking permit is
-     * consumed and this method completes immediately (meaning it doesn't yield).
-     * It also completes immediately if the interrupt status is set or the
-     * deadline has past.
-     * @param deadline absolute time, in milliseconds, from the epoch
-     * @throws WrongThreadException if the current thread is not a virtual thread
-     */
-    public static void parkUntil(long deadline) {
-        long millis = deadline - System.currentTimeMillis();
-        long nanos = TimeUnit.NANOSECONDS.convert(millis, TimeUnit.MILLISECONDS);
-        park(nanos);
-    }
-
-    /**
-     * Re-enables a virtual thread for scheduling. If the thread was parked then
-     * it will be unblocked, otherwise its next attempt to park will not block
-     * @param thread the virtual thread to unpark
-     * @throws IllegalArgumentException if the thread is not a virtual thread
-     * @throws RejectedExecutionException if the scheduler cannot accept a task
-     */
-    public static void unpark(Thread thread) {
-        JLA.unparkVirtualThread(thread);
-    }
-}
diff a/src/java.base/share/classes/jdk/internal/vm/Continuation.java b/src/java.base/share/classes/jdk/internal/vm/Continuation.java
--- a/src/java.base/share/classes/jdk/internal/vm/Continuation.java
+++ b/src/java.base/share/classes/jdk/internal/vm/Continuation.java
@@ -54,13 +54,27 @@
         PRESERVE_SCOPED_VALUE_CACHE = (value == null) || Boolean.parseBoolean(value);
     }
 
     /** Reason for pinning */
     public enum Pinned {
-        /** Native frame on stack */ NATIVE,
-        /** Monitor held */          MONITOR,
-        /** In critical section */   CRITICAL_SECTION }
+        NATIVE(2, "Native frame or <clinit> on stack"),
+        MONITOR(3, "Monitor held"),
+        CRITICAL_SECTION(4, "In critical section");
+
+        private final int reasonCode;
+        private final String reasonString;
+        Pinned(int reasonCode, String reasonString) {
+            this.reasonCode = reasonCode;
+            this.reasonString = reasonString;
+        }
+        public int reasonCode() {
+            return reasonCode;
+        }
+        public String reasonString() {
+            return reasonString;
+        }
+    }
 
     /** Preemption attempt result */
     public enum PreemptStatus {
         /** Success */                                                      SUCCESS(null),
         /** Permanent failure */                                            PERM_FAIL_UNSUPPORTED(null),
@@ -351,12 +365,10 @@
         return cont.yield0(scope, null);
     }
 
     @Hidden
     private boolean yield0(ContinuationScope scope, Continuation child) {
-        preempted = false;
-
         if (scope != this.scope)
             this.yieldInfo = scope;
         int res = doYield();
         U.storeFence(); // needed to prevent certain transformations by the compiler
 
diff a/src/java.base/share/classes/jdk/internal/vm/ScopedValueContainer.java b/src/java.base/share/classes/jdk/internal/vm/ScopedValueContainer.java
--- a/src/java.base/share/classes/jdk/internal/vm/ScopedValueContainer.java
+++ b/src/java.base/share/classes/jdk/internal/vm/ScopedValueContainer.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this
@@ -22,11 +22,11 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
 package jdk.internal.vm;
 
-import java.util.concurrent.Callable;
+import java.lang.ScopedValue.CallableOp;
 import java.util.concurrent.StructureViolationException;
 import jdk.internal.access.JavaLangAccess;
 import jdk.internal.access.SharedSecrets;
 import jdk.internal.misc.Unsafe;
 
@@ -139,11 +139,11 @@
     }
 
     /**
      * For use by ScopedValue to call a value returning operation in a structured context.
      */
-    public static <V> V call(Callable<V> op) {
+    public static <V, X extends Throwable> V call(CallableOp<V, X> op) {
         if (head() == null) {
             // no need to push scope when stack is empty
             return callWithoutScope(op);
         } else {
             return new ScopedValueContainer().doCall(op);
@@ -151,11 +151,11 @@
     }
 
     /**
      * Call an operation without a scope on the stack.
      */
-    private static <V> V callWithoutScope(Callable<V> op) {
+    private static <V, X extends Throwable> V callWithoutScope(CallableOp<V, X> op) {
         assert head() == null;
         Throwable ex;
         boolean atTop;
         V result;
         try {
@@ -173,11 +173,11 @@
     }
 
     /**
      * Call an operation with this scope on the stack.
      */
-    private <V> V doCall(Callable<V> op) {
+    private <V, X extends Throwable> V doCall(CallableOp<V, X> op) {
         Throwable ex;
         boolean atTop;
         V result;
         push();
         try {
diff a/src/java.base/share/classes/sun/nio/ch/Poller.java b/src/java.base/share/classes/sun/nio/ch/Poller.java
--- a/src/java.base/share/classes/sun/nio/ch/Poller.java
+++ b/src/java.base/share/classes/sun/nio/ch/Poller.java
@@ -140,10 +140,24 @@
         } else {
             assert false;
         }
     }
 
+    /**
+     * Parks the current thread until a Selector's file descriptor is ready.
+     * @param fdVal the Selector's file descriptor
+     * @param nanos the waiting time or 0 to wait indefinitely
+     */
+    static void pollSelector(int fdVal, long nanos) throws IOException {
+        assert nanos >= 0L;
+        Poller poller = POLLERS.masterPoller();
+        if (poller == null) {
+            poller = POLLERS.readPoller(fdVal);
+        }
+        poller.poll(fdVal, nanos, () -> true);
+    }
+
     /**
      * If there is a thread polling the given file descriptor for the given event then
      * the thread is unparked.
      */
     static void stopPoll(int fdVal, int event) {
@@ -337,10 +351,17 @@
                     startPlatformThread("Write-Poller", p::pollerLoop);
                 });
             }
         }
 
+        /**
+         * Returns the master poller, or null if there is no master poller.
+         */
+        Poller masterPoller() {
+            return masterPoller;
+        }
+
         /**
          * Returns the read poller for the given file descriptor.
          */
         Poller readPoller(int fdVal) {
             int index = provider.fdValToIndex(fdVal, readPollers.length);
diff a/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java b/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java
--- a/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java
+++ b/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java
@@ -23,10 +23,11 @@
  * questions.
  */
 
 package sun.security.ssl;
 
+import java.lang.invoke.MethodHandles;
 import java.net.Socket;
 import java.security.*;
 import java.security.cert.*;
 import java.util.*;
 import java.util.concurrent.locks.ReentrantLock;
@@ -49,10 +50,18 @@
  * @author Andreas Sterbenz
  */
 final class X509TrustManagerImpl extends X509ExtendedTrustManager
         implements X509TrustManager {
 
+    static {
+        try {
+            MethodHandles.lookup().ensureInitialized(AnchorCertificates.class);
+        } catch (IllegalAccessException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
+
     private final String validatorType;
 
     /**
      * The Set of trusted X509Certificates.
      */
diff a/src/java.base/share/native/libjava/VirtualThread.c b/src/java.base/share/native/libjava/VirtualThread.c
--- a/src/java.base/share/native/libjava/VirtualThread.c
+++ b/src/java.base/share/native/libjava/VirtualThread.c
@@ -36,11 +36,20 @@
     { "notifyJvmtiEnd",            "()V",  (void *)&JVM_VirtualThreadEnd },
     { "notifyJvmtiMount",          "(Z)V", (void *)&JVM_VirtualThreadMount },
     { "notifyJvmtiUnmount",        "(Z)V", (void *)&JVM_VirtualThreadUnmount },
     { "notifyJvmtiHideFrames",     "(Z)V", (void *)&JVM_VirtualThreadHideFrames },
     { "notifyJvmtiDisableSuspend", "(Z)V", (void *)&JVM_VirtualThreadDisableSuspend },
+    { "setLockId",                 "(J)V", (void *)&JVM_SetLockId},
+    { "takeVirtualThreadListToUnblock", "()" VIRTUAL_THREAD, (void *)&JVM_TakeVirtualThreadListToUnblock},
 };
 
 JNIEXPORT void JNICALL
 Java_java_lang_VirtualThread_registerNatives(JNIEnv *env, jclass clazz) {
     (*env)->RegisterNatives(env, clazz, methods, (sizeof(methods)/sizeof(methods[0])));
 }
+
+JNIEXPORT void JNICALL
+Java_java_lang_VirtualThread_virtualThreadPinnedEvent(JNIEnv *env, jclass ignored,
+                                                      jint reasonCode, jstring reasonString)
+{
+    JVM_VirtualThreadPinnedEvent(reasonCode, reasonString);
+}
diff a/src/java.desktop/share/classes/sun/font/HBShaper.java b/src/java.desktop/share/classes/sun/font/HBShaper.java
--- a/src/java.desktop/share/classes/sun/font/HBShaper.java
+++ b/src/java.desktop/share/classes/sun/font/HBShaper.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this
@@ -457,13 +457,11 @@
          * ScopedValue is needed so that call backs into Java during
          * shaping can locate the correct instances of these to query or update.
          * The alternative of creating bound method handles is far too slow.
          */
         ScopedVars vars = new ScopedVars(font2D, fontStrike, gvData, startPt);
-        ScopedValue.where(scopedVars, vars)
-                   .run(() -> {
-
+        ScopedValue.runWhere(scopedVars, vars, () -> {
             try (Arena arena = Arena.ofConfined()) {
 
                 float startX = (float)startPt.getX();
                 float startY = (float)startPt.getY();
 
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
@@ -53,10 +53,11 @@
   private static AddressField  osThreadField;
   private static AddressField  stackBaseField;
   private static CIntegerField stackSizeField;
   private static CIntegerField terminatedField;
   private static AddressField activeHandlesField;
+  private static CIntegerField lockIdField;
   private static long oopPtrSize;
 
   private static JavaThreadPDAccess access;
 
   // JavaThreadStates read from underlying process
@@ -99,10 +100,11 @@
     osThreadField     = type.getAddressField("_osthread");
     stackBaseField    = type.getAddressField("_stack_base");
     stackSizeField    = type.getCIntegerField("_stack_size");
     terminatedField   = type.getCIntegerField("_terminated");
     activeHandlesField = type.getAddressField("_active_handles");
+    lockIdField        = type.getCIntegerField("_lock_id");
 
     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();
 
@@ -374,10 +376,14 @@
         return "<null>";
     }
     return OopUtilities.threadOopGetName(threadObj);
   }
 
+  public Address getLockId() {
+    return lockIdField.getAddress(addr);
+  }
+
   //
   // Oop traversal
   //
 
   public void oopsDo(AddressVisitor oopVisitor) {
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
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2001, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 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.
@@ -49,10 +49,12 @@
     headerFieldOffset = f.getOffset();
     f = type.getField("_object");
     objectFieldOffset = f.getOffset();
     f = type.getField("_owner");
     ownerFieldOffset = f.getOffset();
+    f = type.getField("_stack_locker");
+    stackLockerFieldOffset = f.getOffset();
     f = type.getField("_next_om");
     nextOMFieldOffset = f.getOffset();
     contentionsField  = new CIntField(type.getCIntegerField("_contentions"), 0);
     waitersField      = new CIntField(type.getCIntegerField("_waiters"), 0);
     recursionsField   = type.getCIntegerField("_recursions");
@@ -85,10 +87,11 @@
   public boolean isOwnedAnonymous() {
     return addr.getAddressAt(ownerFieldOffset).asLongValue() == ANONYMOUS_OWNER;
   }
 
   public Address owner() { return addr.getAddressAt(ownerFieldOffset); }
+  public Address stackLocker() { return addr.getAddressAt(stackLockerFieldOffset); }
   // FIXME
   //  void      set_owner(void* owner);
 
   public int    waiters() { return (int)waitersField.getValue(this); }
 
@@ -115,10 +118,11 @@
   // would confuse the SA's type system.
   private static ObjectHeap    heap;
   private static long          headerFieldOffset;
   private static long          objectFieldOffset;
   private static long          ownerFieldOffset;
+  private static long          stackLockerFieldOffset;
   private static long          nextOMFieldOffset;
   private static CIntField     contentionsField;
   private static CIntField     waitersField;
   private static CIntegerField recursionsField;
   private static long          ANONYMOUS_OWNER;
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
@@ -210,32 +210,24 @@
             JavaThread thread = threads.getJavaThreadAt(i);
             thread.oopsDo(oopVisitor);
         }
     }
 
-    // refer to Threads::owning_thread_from_monitor_owner
-    public JavaThread owningThreadFromMonitor(Address o) {
-        assert(VM.getVM().getCommandLineFlag("LockingMode").getInt() != LockingMode.getLightweight());
+    private JavaThread owningThreadFromMonitor(Address o) {
         if (o == null) return null;
         for (int i = 0; i < getNumberOfThreads(); i++) {
             JavaThread thread = getJavaThreadAt(i);
-            if (o.equals(thread.threadObjectAddress())) {
+            if (o.equals(thread.getLockId())) {
                 return thread;
             }
         }
-
-        for (int i = 0; i < getNumberOfThreads(); i++) {
-            JavaThread thread = getJavaThreadAt(i);
-            if (thread.isLockOwned(o))
-                return thread;
-        }
         return null;
     }
 
     public JavaThread owningThreadFromMonitor(ObjectMonitor monitor) {
-        if (VM.getVM().getCommandLineFlag("LockingMode").getInt() == LockingMode.getLightweight()) {
-            if (monitor.isOwnedAnonymous()) {
+        if (monitor.isOwnedAnonymous()) {
+            if (VM.getVM().getCommandLineFlag("LockingMode").getInt() == LockingMode.getLightweight()) {
                 OopHandle object = monitor.object();
                 for (int i = 0; i < getNumberOfThreads(); i++) {
                     JavaThread thread = getJavaThreadAt(i);
                     if (thread.isLockOwned(object)) {
                         return thread;
@@ -244,15 +236,20 @@
                 // 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;
+            } else {
+                assert(VM.getVM().getCommandLineFlag("LockingMode").getInt() == LockingMode.getLegacy());
+                Address o = (Address)monitor.stackLocker();
+                for (int i = 0; i < getNumberOfThreads(); i++) {
+                    JavaThread thread = getJavaThreadAt(i);
+                    if (thread.isLockOwned(o))
+                        return thread;
+                }
+                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());
         }
     }
 
diff a/src/jdk.jfr/share/classes/jdk/jfr/events/VirtualThreadPinnedEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/VirtualThreadPinnedEvent.java
--- a/src/jdk.jfr/share/classes/jdk/jfr/events/VirtualThreadPinnedEvent.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 jdk.jfr.events;
-
-import jdk.jfr.Category;
-import jdk.jfr.Label;
-import jdk.jfr.Name;
-import jdk.jfr.internal.MirrorEvent;
-
-@Category("Java Application")
-@Label("Virtual Thread Pinned")
-@Name("jdk.VirtualThreadPinned")
-public final class VirtualThreadPinnedEvent extends MirrorEvent {
-}
diff a/src/jdk.jfr/share/classes/jdk/jfr/internal/MirrorEvents.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MirrorEvents.java
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MirrorEvents.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MirrorEvents.java
@@ -38,11 +38,10 @@
 import jdk.jfr.events.SocketReadEvent;
 import jdk.jfr.events.SocketWriteEvent;
 import jdk.jfr.events.TLSHandshakeEvent;
 import jdk.jfr.events.ThreadSleepEvent;
 import jdk.jfr.events.VirtualThreadEndEvent;
-import jdk.jfr.events.VirtualThreadPinnedEvent;
 import jdk.jfr.events.VirtualThreadStartEvent;
 import jdk.jfr.events.VirtualThreadSubmitFailedEvent;
 import jdk.jfr.events.X509CertificateEvent;
 import jdk.jfr.events.X509ValidationEvent;
 
@@ -63,11 +62,10 @@
         register("jdk.internal.event.SocketWriteEvent", SocketWriteEvent.class);
         register("jdk.internal.event.ThreadSleepEvent", ThreadSleepEvent.class);
         register("jdk.internal.event.TLSHandshakeEvent", TLSHandshakeEvent.class);
         register("jdk.internal.event.VirtualThreadStartEvent", VirtualThreadStartEvent.class);
         register("jdk.internal.event.VirtualThreadEndEvent", VirtualThreadEndEvent.class);
-        register("jdk.internal.event.VirtualThreadPinnedEvent", VirtualThreadPinnedEvent.class);
         register("jdk.internal.event.VirtualThreadSubmitFailedEvent", VirtualThreadSubmitFailedEvent.class);
         register("jdk.internal.event.X509CertificateEvent", X509CertificateEvent.class);
         register("jdk.internal.event.X509ValidationEvent", X509ValidationEvent.class);
         register("jdk.internal.event.ErrorThrownEvent", ErrorThrownEvent.class);
         register("jdk.internal.event.ExceptionStatisticsEvent", ExceptionStatisticsEvent.class);
diff a/src/jdk.jfr/share/classes/jdk/jfr/internal/instrument/JDKEvents.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/instrument/JDKEvents.java
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/instrument/JDKEvents.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/instrument/JDKEvents.java
@@ -76,11 +76,10 @@
         jdk.internal.event.SocketWriteEvent.class,
         jdk.internal.event.ThreadSleepEvent.class,
         jdk.internal.event.TLSHandshakeEvent.class,
         jdk.internal.event.VirtualThreadStartEvent.class,
         jdk.internal.event.VirtualThreadEndEvent.class,
-        jdk.internal.event.VirtualThreadPinnedEvent.class,
         jdk.internal.event.VirtualThreadSubmitFailedEvent.class,
         jdk.internal.event.X509CertificateEvent.class,
         jdk.internal.event.X509ValidationEvent.class,
         DirectBufferStatisticsEvent.class,
         InitialSecurityPropertyEvent.class,
diff a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc
--- a/src/jdk.jfr/share/conf/jfr/default.jfc
+++ b/src/jdk.jfr/share/conf/jfr/default.jfc
@@ -73,11 +73,10 @@
     </event>
 
     <event name="jdk.VirtualThreadPinned">
       <setting name="enabled">true</setting>
       <setting name="stackTrace">true</setting>
-      <setting name="threshold">20 ms</setting>
     </event>
 
     <event name="jdk.VirtualThreadSubmitFailed">
       <setting name="enabled">true</setting>
       <setting name="stackTrace">true</setting>
diff a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc
--- a/src/jdk.jfr/share/conf/jfr/profile.jfc
+++ b/src/jdk.jfr/share/conf/jfr/profile.jfc
@@ -73,11 +73,10 @@
     </event>
 
     <event name="jdk.VirtualThreadPinned">
       <setting name="enabled">true</setting>
       <setting name="stackTrace">true</setting>
-      <setting name="threshold">20 ms</setting>
     </event>
 
     <event name="jdk.VirtualThreadSubmitFailed">
       <setting name="enabled">true</setting>
       <setting name="stackTrace">true</setting>
diff a/src/jdk.management/share/classes/com/sun/management/doc-files/threadDump.schema.json b/src/jdk.management/share/classes/com/sun/management/doc-files/threadDump.schema.json
--- /dev/null
+++ b/src/jdk.management/share/classes/com/sun/management/doc-files/threadDump.schema.json
@@ -0,0 +1,78 @@
+{
+  "type": "object",
+  "properties": {
+    "threadDump": {
+      "type": "object",
+      "properties": {
+        "processId": {
+          "type": "string",
+          "description": "The native process id of the Java virtual machine."
+        },
+        "time": {
+          "type": "string",
+          "description": "The time in ISO 8601 format when the thread dump was generated."
+        },
+        "runtimeVersion": {
+          "type": "string",
+          "description": "The runtime version, see java.lang.Runtime.version()."
+        },
+        "threadContainers": {
+          "type": "array",
+          "description": "The array of thread containers.",
+          "items": [
+            {
+              "type": "object",
+              "properties": {
+                "container": {
+                  "type": "string",
+                  "description": "The container name. The container name is unique."
+                },
+                "parent": {
+                  "type": ["string", "null"],
+                  "description": "The parent container name or null for the root container."
+                },
+                "owner": {
+                  "type": ["string", "null"],
+                  "description": "The thread identifier of the owner thread or null if not owned."
+                },
+                "threads": {
+                  "type": "array",
+                  "description": "The array of threads in the thread container.",
+                  "items": [
+                    {
+                      "type": "object",
+                      "properties": {
+                        "tid": {
+                          "type": "string",
+                          "description": "The thread identifier, see java.lang.Thread.threadId()."
+                        },
+                        "name": {
+                          "type": "string",
+                          "description": "The thread name."
+                        },
+                        "stack": {
+                          "type": "array",
+                          "description": "The thread stack. The first element is the top of the stack.",
+                          "items": [
+                            {
+                              "type": "string",
+                              "description": "A stack trace element, see java.lang.StackTraceElement."
+                            }
+                          ]
+                        }
+                      }
+                    }
+                  ]
+                },
+                "threadCount": {
+                  "type": "string",
+                  "description": "The number of threads in the thread container."
+                }
+              }
+            }
+          ]
+        }
+      }
+    }
+  }
+}
diff a/test/hotspot/jtreg/ProblemList-Virtual.txt b/test/hotspot/jtreg/ProblemList-Virtual.txt
--- a/test/hotspot/jtreg/ProblemList-Virtual.txt
+++ b/test/hotspot/jtreg/ProblemList-Virtual.txt
@@ -88,16 +88,52 @@
 # the debuggee, which can lead to I/O poller threads exiting. Because
 # of this no vthreads can complete their reads, and the test times out as a result.
 
 vmTestbase/nsk/jdi/VMOutOfMemoryException/VMOutOfMemoryException001/VMOutOfMemoryException001.java 8285417 generic-all
 
+###
+# Uses GetObjectMonitorUsage to get the list of threads waiting to enter the monitor but we only
+# support getting the waiting platform threads, not unmounted vthreads.
+
+vmTestbase/nsk/jdi/ObjectReference/waitingThreads/waitingthreads002/TestDescription.java 0000000 generic-all
+
+###
+# The test first suspends a vthread and all the carriers and then it resumes the vthread expecting it
+# to run to completion. In mainline it only works because the suspension step happens while the vthread is
+# pinned to the carrier blocked on synchronized. So the carrier only actually suspends on the next unmount
+# transition which in this test happens once the vthread has finished executing the expected code.
+
+vmTestbase/nsk/jdi/ThreadReference/isSuspended/issuspended002/TestDescription.java 0000000 generic-all
+
+###
+# The test sends a StopThread to a vthread expecting that is currently pinned to the carrier blocked on
+# synchronized. Since the vthread is now unmounted StopThread returns JVMTI_ERROR_OPAQUE_FRAME error.
+
+vmTestbase/nsk/jdb/kill/kill001/kill001.java 0000000 generic-all
+
+# Unknown reasons, should be investigated
+vmTestbase/gc/gctests/MemoryEaterMT/MemoryEaterMT.java 0000000 generic-all
+
+
 ##########
 ## Tests incompatible with  with virtual test thread factory.
 ## There is no goal to run all test with virtual test thread factory.
 ## So any test migth be added as incompatible, the bug is not required.
 
 gc/arguments/TestNewSizeThreadIncrease.java 0000000 generic-all
 gc/g1/TestSkipRebuildRemsetPhase.java 0000000 generic-all
+runtime/cds/appcds/jigsaw/classpathtests/EmptyClassInBootClassPath.java 0000000 generic-all
+runtime/cds/appcds/jigsaw/modulepath/OptimizeModuleHandlingTest.java 0000000 generic-all
+gc/g1/TestSkipRebuildRemsetPhase.java 0000000 generic-all
 runtime/ErrorHandling/MachCodeFramesInErrorFile.java 0000000 generic-all
 runtime/Thread/AsyncExceptionOnMonitorEnter.java 0000000 generic-all
 runtime/Thread/StopAtExit.java 0000000 generic-all
 runtime/handshake/HandshakeWalkStackTest.java 0000000 generic-all
+
+# too fragile when main thread is a virtual thread
+runtime/Thread/ThreadCountLimit.java#id0 0000000 generic-all
+
+compiler/interpreter/TestVerifyStackAfterDeopt.java 8316392 macosx-aarch64
+
+compiler/arguments/TestC1Globals.java#id0 8320302 generic-all
+compiler/arguments/TestC1Globals.java#id1 8320302 generic-all
+compiler/arguments/TestC1Globals.java#id2 8320302 generic-all
diff a/test/hotspot/jtreg/ProblemList-Xcomp.txt b/test/hotspot/jtreg/ProblemList-Xcomp.txt
--- a/test/hotspot/jtreg/ProblemList-Xcomp.txt
+++ b/test/hotspot/jtreg/ProblemList-Xcomp.txt
@@ -50,5 +50,22 @@
 vmTestbase/nsk/jvmti/scenarios/capability/CM03/cm03t001/TestDescription.java 8299493 macosx-x64,windows-x64
 
 vmTestbase/nsk/stress/thread/thread006.java 8321476 linux-all
 
 gc/arguments/TestNewSizeFlags.java 8299116 macosx-aarch64
+
+############################################################################
+
+# Loom, fibers branch
+
+gc/g1/mixedgc/TestOldGenCollectionUsage.java                            8284404 generic-all
+gc/whitebox/TestConcMarkCycleWB.java                                    8284404 generic-all
+
+gc/g1/plab/TestPLABPromotion.java                                       8278126 generic-all
+gc/g1/plab/TestPLABResize.java                                          8278126 generic-all
+
+gc/stringdedup/TestStringDeduplicationInterned.java#G1                  8278127 generic-all
+gc/stringdedup/TestStringDeduplicationInterned.java#Parallel            8278127 generic-all
+gc/stringdedup/TestStringDeduplicationInterned.java#Serial              8278127 generic-all
+gc/stringdedup/TestStringDeduplicationInterned.java#Z                   8278127 generic-all
+
+gc/z/TestGarbageCollectorMXBean.java                                    8278129 generic-all
diff a/test/hotspot/jtreg/ProblemList-vthread-Xcomp.txt b/test/hotspot/jtreg/ProblemList-vthread-Xcomp.txt
--- /dev/null
+++ b/test/hotspot/jtreg/ProblemList-vthread-Xcomp.txt
@@ -0,0 +1,34 @@
+#
+# 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.
+#
+
+#############################################################################
+#
+# List of quarantined tests for testing in Xcomp mode with wrapper.
+#
+#############################################################################
+
+####
+## Unexpected results
+compiler/uncommontrap/TestUnstableIfTrap.java
+gc/g1/plab/TestPLABPromotion.java
+gc/whitebox/TestWBGC.java
diff a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt
--- a/test/hotspot/jtreg/ProblemList.txt
+++ b/test/hotspot/jtreg/ProblemList.txt
@@ -186,5 +186,12 @@
 vmTestbase/nsk/jdwp/ThreadReference/ForceEarlyReturn/forceEarlyReturn001/forceEarlyReturn001.java 7199837 generic-all
 
 vmTestbase/nsk/monitoring/ThreadMXBean/ThreadInfo/Multi/Multi005/TestDescription.java 8076494 windows-x64
 
 vmTestbase/nsk/monitoring/ThreadMXBean/findMonitorDeadlockedThreads/find006/TestDescription.java 8310144 macosx-aarch64
+
+#############################################################################
+
+# Loom, fibers branch
+
+serviceability/jvmti/vthread/ContinuationTest/ContinuationTest.java  8029528 generic-all
+serviceability/jvmti/vthread/GetSetLocalTest/GetSetLocalTest.java    0000000 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
@@ -762,5 +762,10 @@
 # JDB tests
 vmTestbase_nsk_jdb = \
   vmTestbase/nsk/jdb
 
 # vmTestbase_*_quick groups are defined in TEST.quick-groups
+
+hotspot_remaining = \
+ :hotspot_all_no_apps \
+  -vmTestbase \
+  -:tier1
diff a/test/hotspot/jtreg/runtime/vthread/JNIMonitor/JNIMonitor.java b/test/hotspot/jtreg/runtime/vthread/JNIMonitor/JNIMonitor.java
--- a/test/hotspot/jtreg/runtime/vthread/JNIMonitor/JNIMonitor.java
+++ b/test/hotspot/jtreg/runtime/vthread/JNIMonitor/JNIMonitor.java
@@ -217,16 +217,20 @@
                 throw new RuntimeException(e);
             }
         }
 
         static void runTest(int nThreads, boolean skipUnlock, boolean throwOnExit) throws Throwable {
-            final Object monitor = new Object();
+            final Object[] monitors = new Object[nThreads];
+            for (int i = 0; i < nThreads; i++) {
+                monitors[i] = new Object();
+            }
             final AtomicReference<Throwable> exception = new AtomicReference();
             // Ensure all our VT's operate of the same carrier, sequentially.
             ExecutorService scheduler = Executors.newSingleThreadExecutor();
             ThreadFactory factory = virtualThreadBuilder(scheduler).factory();
             for (int i = 0 ; i < nThreads; i++) {
+                Object monitor = skipUnlock ? monitors[i] : monitors[0];
                 Thread th = factory.newThread(() -> {
                         try {
                             int res = monitorEnter(monitor);
                             Asserts.assertTrue(res == 0, "monitorEnter should return 0.");
                             Asserts.assertTrue(Thread.holdsLock(monitor), "monitor should be owned");
diff a/test/hotspot/jtreg/serviceability/dcmd/thread/VThreadSchedulerTest.java b/test/hotspot/jtreg/serviceability/dcmd/thread/VThreadSchedulerTest.java
--- /dev/null
+++ b/test/hotspot/jtreg/serviceability/dcmd/thread/VThreadSchedulerTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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
+ * @summary Basic test for jcmd Thread.vthread_scheduler
+ * @library /test/lib
+ * @run main/othervm VThreadSchedulerTest
+ */
+
+import jdk.test.lib.dcmd.PidJcmdExecutor;
+import jdk.test.lib.process.OutputAnalyzer;
+
+public class VThreadSchedulerTest {
+    public static void main(String[] args) throws Exception {
+        OutputAnalyzer output = new PidJcmdExecutor().execute("Thread.vthread_scheduler");
+        output.shouldContain("parallelism");
+    }
+}
diff a/test/hotspot/jtreg/serviceability/jvmti/ObjectMonitorUsage/ObjectMonitorUsage.java b/test/hotspot/jtreg/serviceability/jvmti/ObjectMonitorUsage/ObjectMonitorUsage.java
--- a/test/hotspot/jtreg/serviceability/jvmti/ObjectMonitorUsage/ObjectMonitorUsage.java
+++ b/test/hotspot/jtreg/serviceability/jvmti/ObjectMonitorUsage/ObjectMonitorUsage.java
@@ -281,11 +281,12 @@
     public static void main(String args[]) {
         log("\n### main: started\n");
         check(lockCheck, null, 0, 0, 0);
 
         test(false); // test platform threads
-        test(true);  // test virtual threads
+
+        //test(true);  // test virtual threads
 
         check(lockCheck, null, 0, 0, 0);
         if (getRes() > 0) {
             throw new RuntimeException("Failed status returned from the agent");
         }
diff a/test/hotspot/jtreg/serviceability/jvmti/thread/GetFrameCount/framecnt01/framecnt01.java b/test/hotspot/jtreg/serviceability/jvmti/thread/GetFrameCount/framecnt01/framecnt01.java
--- a/test/hotspot/jtreg/serviceability/jvmti/thread/GetFrameCount/framecnt01/framecnt01.java
+++ b/test/hotspot/jtreg/serviceability/jvmti/thread/GetFrameCount/framecnt01/framecnt01.java
@@ -77,11 +77,11 @@
         while(vThread1.getState() != Thread.State.WAITING) {
             Thread.sleep(1);
         }
 
         // this is too fragile, implementation can change at any time.
-        checkFrames(vThread1, false, 14);
+        checkFrames(vThread1, false, 13);
         LockSupport.unpark(vThread1);
         vThread1.join();
 
         // Test GetFrameCount on live platform thread
         Thread pThread = Thread.ofPlatform().name("PlatformThread-Live").start(() -> {
diff a/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/GetThreadStateTest.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/GetThreadStateTest.java
--- a/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/GetThreadStateTest.java
+++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/GetThreadStateTest.java
@@ -23,36 +23,46 @@
 
 /*
  * @test id=default
  * @bug 8312498
  * @summary Basic test for JVMTI GetThreadState with virtual threads
+ * @modules java.base/java.lang:+open
  * @library /test/lib
- * @run junit/othervm/native GetThreadStateTest
+ * @run junit/othervm/native --enable-native-access=ALL-UNNAMED GetThreadStateTest
  */
 
 /*
  * @test id=no-vmcontinuations
  * @requires vm.continuations
+ * @modules java.base/java.lang:+open
  * @library /test/lib
- * @run junit/othervm/native -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations GetThreadStateTest
+ * @run junit/othervm/native -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations --enable-native-access=ALL-UNNAMED GetThreadStateTest
  */
 
 import java.util.StringJoiner;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.LockSupport;
 
+import jdk.test.lib.thread.VThreadRunner;
 import jdk.test.lib.thread.VThreadPinner;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 import static org.junit.jupiter.api.Assertions.*;
 
 class GetThreadStateTest {
 
     @BeforeAll
     static void setup() {
         System.loadLibrary("GetThreadStateTest");
         init();
+
+        // need >=2 carriers for testing pinning when main thread is a virtual thread
+        if (Thread.currentThread().isVirtual()) {
+            VThreadRunner.ensureParallelism(2);
+        }
     }
 
     /**
      * Test state of new/unstarted thread.
      */
@@ -103,19 +113,26 @@
             thread.join();
         }
     }
 
     /**
-     * Test state of thread waiting to enter a monitor.
+     * Test state of thread waiting to enter a monitor when pinned and not pinned.
      */
-    @Test
-    void testMonitorEnter() throws Exception {
+    @ParameterizedTest
+    @ValueSource(booleans = { true, false })
+    void testMonitorEnter(boolean pinned) throws Exception {
         var started = new AtomicBoolean();
         Object lock = new Object();
         var thread = Thread.ofVirtual().unstarted(() -> {
             started.set(true);
-            synchronized (lock) { }
+            if (pinned) {
+                VThreadPinner.runPinned(() -> {
+                    synchronized (lock) { }
+                });
+            } else {
+                synchronized (lock) { }
+            }
         });
         try {
             synchronized (lock) {
                 // start thread and wait for it to start execution
                 thread.start();
@@ -217,83 +234,30 @@
             thread.join();
         }
     }
 
     /**
-     * Test state of thread parked with LockSupport.park.
-     */
-    @Test
-    void testPark() throws Exception {
-        var started = new AtomicBoolean();
-        var done = new AtomicBoolean();
-        var thread = Thread.ofVirtual().start(() -> {
-            started.set(true);
-            while (!done.get()) {
-                LockSupport.park();
-            }
-        });
-        try {
-            // wait for thread to start execution
-            awaitTrue(started);
-
-            // thread should park
-            int expected = JVMTI_THREAD_STATE_ALIVE |
-                    JVMTI_THREAD_STATE_WAITING |
-                    JVMTI_THREAD_STATE_WAITING_INDEFINITELY |
-                    JVMTI_THREAD_STATE_PARKED;
-            await(thread, expected);
-        } finally {
-            done.set(true);
-            LockSupport.unpark(thread);
-            thread.join();
-        }
-    }
-
-    /**
-     * Test state of thread parked with LockSupport.parkNanos.
+     * Test state of thread parked with LockSupport.park when pinned and not pinned.
      */
-    @Test
-    void testParkNanos() throws Exception {
+    @ParameterizedTest
+    @ValueSource(booleans = { true, false })
+    void testPark(boolean pinned) throws Exception {
         var started = new AtomicBoolean();
         var done = new AtomicBoolean();
         var thread = Thread.ofVirtual().start(() -> {
             started.set(true);
-            while (!done.get()) {
-                LockSupport.parkNanos(Long.MAX_VALUE);
-            }
-        });
-        try {
-            // wait for thread to start execution
-            awaitTrue(started);
-
-            // thread should park
-            int expected = JVMTI_THREAD_STATE_ALIVE |
-                    JVMTI_THREAD_STATE_WAITING |
-                    JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT |
-                    JVMTI_THREAD_STATE_PARKED;
-            await(thread, expected);
-        } finally {
-            done.set(true);
-            LockSupport.unpark(thread);
-            thread.join();
-        }
-    }
-
-    /**
-     * Test state of thread parked with LockSupport.park while holding a monitor.
-     */
-    @Test
-    void testParkWhenPinned() throws Exception {
-        var started = new AtomicBoolean();
-        var done = new AtomicBoolean();
-        var thread = Thread.ofVirtual().start(() -> {
-            VThreadPinner.runPinned(() -> {
-                started.set(true);
+            if (pinned) {
+                VThreadPinner.runPinned(() -> {
+                    while (!done.get()) {
+                        LockSupport.park();
+                    }
+                });
+            } else {
                 while (!done.get()) {
                     LockSupport.park();
                 }
-            });
+            }
         });
         try {
             // wait for thread to start execution
             awaitTrue(started);
 
@@ -309,23 +273,30 @@
             thread.join();
         }
     }
 
     /**
-     * Test state of thread parked with LockSupport.parkNanos while holding a monitor.
+     * Test state of thread parked with LockSupport.parkNanos when pinned and not pinned.
      */
-    @Test
-    void testParkNanosWhenPinned() throws Exception {
+    @ParameterizedTest
+    @ValueSource(booleans = { true, false })
+    void testParkNanos(boolean pinned) throws Exception {
         var started = new AtomicBoolean();
         var done = new AtomicBoolean();
         var thread = Thread.ofVirtual().start(() -> {
-            VThreadPinner.runPinned(() -> {
-                started.set(true);
+            started.set(true);
+            if (pinned) {
+                VThreadPinner.runPinned(() -> {
+                    while (!done.get()) {
+                        LockSupport.parkNanos(Long.MAX_VALUE);
+                    }
+                });
+            } else {
                 while (!done.get()) {
                     LockSupport.parkNanos(Long.MAX_VALUE);
                 }
-            });
+            }
         });
         try {
             // wait for thread to start execution
             awaitTrue(started);
 
diff a/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadStateMountedTest/GetThreadStateMountedTest.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadStateMountedTest/GetThreadStateMountedTest.java
--- a/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadStateMountedTest/GetThreadStateMountedTest.java
+++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadStateMountedTest/GetThreadStateMountedTest.java
@@ -249,17 +249,18 @@
         if (errCount > 0) {
             throw new RuntimeException("Test failed, " + errCount + " errors");
         }
     }
 
+    private static native void runFromNative(Runnable runnable);
+
     private static Thread createPinnedVThread(Runnable runnable) {
-        final Object syncObj = new Object();
-        return Thread.ofVirtual().unstarted(() -> {
-            synchronized (syncObj) {
-                runnable.run();
-            }
-        });
+        return Thread.ofVirtual().unstarted(() -> runFromNative(runnable));
+    }
+
+    private static void runUpcall(Runnable runnable) {
+        runnable.run();
     }
 
     // Native implementation of suspendWaiting.
     // Returns false if the method is not able to reach the desired state in several tries.
     private static native boolean trySuspendInWaitingState(Thread vthread);
diff a/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadStateMountedTest/libGetThreadStateMountedTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadStateMountedTest/libGetThreadStateMountedTest.cpp
--- a/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadStateMountedTest/libGetThreadStateMountedTest.cpp
+++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadStateMountedTest/libGetThreadStateMountedTest.cpp
@@ -189,10 +189,20 @@
 }
 
 
 static std::atomic<bool> time_to_exit(false);
 
+extern "C" JNIEXPORT void JNICALL
+Java_GetThreadStateMountedTest_runFromNative(JNIEnv* jni, jclass clazz, jobject runnable) {
+  jmethodID mid = jni->GetStaticMethodID(clazz, "runUpcall", "(Ljava/lang/Runnable;)V");
+  if (mid == nullptr) {
+    jni->FatalError("failed to get runUpcall method");
+    return;
+  }
+  jni->CallStaticVoidMethod(clazz, mid, runnable);
+}
+
 extern "C" JNIEXPORT void JNICALL
 Java_GetThreadStateMountedTest_waitInNative(JNIEnv* jni, jclass clazz) {
   // Notify main thread that we are ready
   jfieldID fid = jni->GetStaticFieldID(clazz, "waitInNativeReady", "Z");
   if (fid == nullptr) {
diff a/test/hotspot/jtreg/serviceability/jvmti/vthread/StopThreadTest/StopThreadTest.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/StopThreadTest/StopThreadTest.java
--- a/test/hotspot/jtreg/serviceability/jvmti/vthread/StopThreadTest/StopThreadTest.java
+++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/StopThreadTest/StopThreadTest.java
@@ -23,25 +23,29 @@
 
 /*
  * @test id=default
  * @summary Verifies JVMTI StopThread support for virtual threads.
  * @requires vm.continuations
+ * @library /test/lib
  * @run main/othervm/native -agentlib:StopThreadTest StopThreadTest
  */
 
 /*
  * @test id=no-vmcontinuations
  * @summary Verifies JVMTI StopThread support for bound virtual threads.
- * @run main/othervm/native -agentlib:StopThreadTest -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations StopThreadTest
+ * @library /test/lib
+ * @run main/othervm/native -agentlib:StopThreadTest -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations -DboundVThread=true StopThreadTest
  */
 
 /*
  * @test id=platform
  * @summary Verifies JVMTI StopThread support for platform threads.
+ * @library /test/lib
  * @run main/othervm/native -agentlib:StopThreadTest StopThreadTest platform
  */
 
+import jdk.test.lib.Platform;
 import java.lang.AssertionError;
 
 /*
  *     The test exercises the JVMTI function: StopThread(jthread).
  *     The test creates a new virtual or platform thread.
@@ -51,11 +55,13 @@
  *      - method C() that forces agent to send AssertionError exception to its own thread
  *     All cases are using JVMTI StopThread to send an AssertionError object.
  */
 public class StopThreadTest {
     private static final String agentLib = "StopThreadTest";
+    static final boolean isBoundVThread = Boolean.getBoolean("boundVThread");
     static final int JVMTI_ERROR_NONE = 0;
+    static final int JVMTI_ERROR_OPAQUE_FRAME = 32;
     static final int THREAD_NOT_SUSPENDED = 13;
     static final int PASSED = 0;
     static final int FAILED = 2;
 
     static void log(String str) { System.out.println(str); }
@@ -102,11 +108,11 @@
             if (is_virtual) {
                 testTaskThread = Thread.ofVirtual().name("TestTaskThread").start(testTask);
             } else {
                 testTaskThread = Thread.ofPlatform().name("TestTaskThread").start(testTask);
             }
-            TestTask.ensureAtPointA();
+            TestTask.ensureAtPointA(testTaskThread);
 
             if (is_virtual) { // this check is for virtual target thread only
                 log("\nMain #A.1: unsuspended");
                 retCode = stopThread(testTaskThread);
                 if (retCode != THREAD_NOT_SUSPENDED) {
@@ -117,14 +123,17 @@
             }
 
             log("\nMain #A.2: suspended");
             suspendThread(testTaskThread);
             retCode = stopThread(testTaskThread);
-            if (retCode != JVMTI_ERROR_NONE) {
-                throwFailed("Main #A.2: expected JVMTI_ERROR_NONE instead of: " + retCode);
+            int expectedRetCode = (is_virtual && !isBoundVThread && (Platform.isX64() || Platform.isAArch64())) ? JVMTI_ERROR_OPAQUE_FRAME : JVMTI_ERROR_NONE;
+            String expectedRetCodeName = (is_virtual && !isBoundVThread && (Platform.isX64() || Platform.isAArch64())) ?
+                "JVMTI_ERROR_OPAQUE_FRAME" : "JVMTI_ERROR_NONE";
+            if (retCode != expectedRetCode) {
+                throwFailed("Main #A.2: expected " + expectedRetCodeName + " instead of: " + retCode);
             } else {
-                log("Main #A.2: got expected JVMTI_ERROR_NONE");
+                log("Main #A.2: got expected " + expectedRetCodeName);
             }
             resumeThread(testTaskThread);
         }
         log("\nMain #B: method B() must be blocked in a breakpoint event handler");
         {
@@ -177,12 +186,13 @@
             } catch (InterruptedException e) {
                 throw new RuntimeException("Interruption in TestTask.sleep: \n\t" + e);
             }
         }
 
-        static void ensureAtPointA() {
-            while (!atPointA) {
+        static void ensureAtPointA(Thread vt) {
+            // wait while the thread state is not the expected one
+            while (vt.getState() != Thread.State.BLOCKED) {
                 sleep(1);
             }
         }
 
         // Ensure thread is finished.
@@ -201,11 +211,14 @@
             } catch (AssertionError ex) {
                 log("TestTask.run: caught expected AssertionError from method A()");
                 seenExceptionFromA = true;
             }
             Thread.interrupted();
-            if (!seenExceptionFromA) {
+            if (!seenExceptionFromA
+                && !(is_virtual
+                     && !isBoundVThread
+                     && (Platform.isX64() || (Platform.isAArch64())) )) {
                 StopThreadTest.setFailed("TestTask.run: expected AssertionError from method A()");
             }
             sleep(1); // to cause yield
 
             boolean seenExceptionFromB = false;
@@ -239,11 +252,10 @@
         // StopThread is used to send an AssertionError object two times:
         //  - when not suspended: THREAD_NOT_SUSPENDED is expected
         //  - when suspended: JVMTI_ERROR_NONE is expected
         static void A() {
             log("TestTask.A: started");
-            atPointA = true;
             synchronized (lock) {
             }
             log("TestTask.A: finished");
         }
 
diff a/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume1/SuspendResume1.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume1/SuspendResume1.java
--- a/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume1/SuspendResume1.java
+++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume1/SuspendResume1.java
@@ -23,19 +23,33 @@
 
 /*
  * @test id=default
  * @summary Test SuspendThread/ResumeThread, SuspendThreadList/ResumeThreadList
  *          for virtual threads.
+ * @requires vm.debug != true
  * @library /test/lib
  * @compile SuspendResume1.java
  * @run driver jdk.test.lib.FileInstaller . .
  * @run main/othervm/native/timeout=600
  *      -Djdk.virtualThreadScheduler.maxPoolSize=1
  *      -agentlib:SuspendResume1
  *      SuspendResume1
  */
 
+/*
+ * @test id=debug
+ * @requires vm.debug == true
+ * @library /test/lib
+ * @compile SuspendResume1.java
+ * @run driver jdk.test.lib.FileInstaller . .
+ * @run main/othervm/native/timeout=600
+ *      -Djdk.virtualThreadScheduler.maxPoolSize=1
+ *      -agentlib:SuspendResume1
+ *      -XX:-VerifyContinuations
+ *      SuspendResume1
+ */
+
  /*
  * @test id=no-vmcontinuations
  * @requires vm.continuations
  * @library /test/lib
  * @compile SuspendResume1.java
@@ -166,11 +180,11 @@
 
     // ensure thread is ready
     public void ensureReady() {
         try {
             while (!threadReady) {
-                sleep(1000);
+                sleep(100);
             }
         } catch (InterruptedException e) {
             throw new RuntimeException("Interruption while preparing tested thread: \n\t" + e);
         }
     }
diff a/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume2/SuspendResume2.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume2/SuspendResume2.java
--- a/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume2/SuspendResume2.java
+++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume2/SuspendResume2.java
@@ -22,19 +22,33 @@
  */
 
 /*
  * @test id=default
  * @summary Test SuspendAllVirtualThreads/ResumeAllVirtualThreads
+ * @requires vm.debug != true
  * @library /test/lib
  * @compile SuspendResume2.java
  * @run driver jdk.test.lib.FileInstaller . .
  * @run main/othervm/native
  *      -Djdk.virtualThreadScheduler.maxPoolSize=1
  *      -agentlib:SuspendResume2
  *      SuspendResume2
  */
 
+/*
+ * @test id=debug
+ * @requires vm.debug == true
+ * @library /test/lib
+ * @compile SuspendResume2.java
+ * @run driver jdk.test.lib.FileInstaller . .
+ * @run main/othervm/native
+ *      -Djdk.virtualThreadScheduler.maxPoolSize=1
+ *      -agentlib:SuspendResume2
+ *      -XX:-VerifyContinuations
+ *      SuspendResume2
+ */
+
 /*
  * @test id=no-vmcontinuations
  * @requires vm.continuations
  * @library /test/lib
  * @compile SuspendResume2.java
@@ -146,11 +160,11 @@
         int i = 0;
         int n = 1000;
         while (!shouldFinish) {
             if (n <= 0) {
                 n = 1000;
-                SuspendResume2.sleep(1);
+                SuspendResume2.sleep(10);
             }
             if (i > n) {
                 i = 0;
                 n = n - 1;
             }
@@ -160,11 +174,11 @@
 
     // ensure thread is ready
     public void ensureReady() {
         try {
             while (!threadReady) {
-                sleep(1);
+                sleep(100);
             }
         } catch (InterruptedException e) {
             throw new RuntimeException("Interruption while preparing tested thread: \n\t" + e);
         }
     }
diff a/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendWithInterruptLock/SuspendWithInterruptLock.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendWithInterruptLock/SuspendWithInterruptLock.java
--- a/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendWithInterruptLock/SuspendWithInterruptLock.java
+++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendWithInterruptLock/SuspendWithInterruptLock.java
@@ -30,17 +30,24 @@
  * @run main/othervm SuspendWithInterruptLock
  */
 
 /**
  * @test id=xint
- * @summary Do not suspend virtual threads in a critical section.
- * @bug 8311218
+ * @requires vm.debug != true
  * @requires vm.continuations
  * @library /testlibrary
  * @run main/othervm -Xint SuspendWithInterruptLock
  */
 
+/**
+ * @test id=xint-debug
+ * @requires vm.debug == true
+ * @requires vm.continuations
+ * @library /testlibrary
+ * @run main/othervm -Xint -XX:-VerifyContinuations SuspendWithInterruptLock
+ */
+
 import jvmti.JVMTIUtils;
 
 public class SuspendWithInterruptLock {
     static volatile boolean done;
 
diff a/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/VThreadEventTest.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/VThreadEventTest.java
--- a/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/VThreadEventTest.java
+++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/VThreadEventTest.java
@@ -26,185 +26,134 @@
  * @bug 8312174
  * @summary missing JVMTI events from vthreads parked during JVMTI attach
  * @requires vm.continuations
  * @requires vm.jvmti
  * @requires vm.compMode != "Xcomp"
+ * @modules java.base/java.lang:+open
+ * @library /test/lib
  * @run main/othervm/native
- *     -Djdk.virtualThreadScheduler.parallelism=9
  *     -Djdk.attach.allowAttachSelf=true -XX:+EnableDynamicAgentLoading VThreadEventTest attach
  */
 
 import com.sun.tools.attach.VirtualMachine;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.LockSupport;
 import java.util.List;
 import java.util.ArrayList;
-
-/*
- * The test uses custom implementation of the CountDownLatch class.
- * The reason is we want the state of tested thread to be predictable.
- * With java.util.concurrent.CountDownLatch it is not clear what thread state is expected.
- */
-class CountDownLatch {
-    private int count = 0;
-
-    CountDownLatch(int count) {
-        this.count = count;
-    }
-
-    public synchronized void countDown() {
-        count--;
-        notify();
-    }
-
-    public synchronized void await() throws InterruptedException {
-        while (count > 0) {
-            wait(1);
-        }
-    }
-}
+import jdk.test.lib.thread.VThreadRunner;
 
 public class VThreadEventTest {
-    static final int TCNT1 = 10;
-    static final int TCNT2 = 4;
-    static final int TCNT3 = 4;
-    static final int THREAD_CNT = TCNT1 + TCNT2 + TCNT3;
+    static final int PARKED_THREAD_COUNT = 4;
+    static final int SPINNING_THREAD_COUNT = 4;
 
     private static void log(String msg) { System.out.println(msg); }
 
     private static native int threadEndCount();
     private static native int threadMountCount();
     private static native int threadUnmountCount();
 
     private static volatile boolean attached;
-    private static boolean failed;
-    private static List<Thread> test1Threads = new ArrayList(TCNT1);
-
-    private static CountDownLatch ready0 = new CountDownLatch(THREAD_CNT);
-    private static CountDownLatch ready1 = new CountDownLatch(TCNT1);
-    private static CountDownLatch ready2 = new CountDownLatch(THREAD_CNT);
-    private static CountDownLatch mready = new CountDownLatch(1);
-
-    private static void await(CountDownLatch dumpedLatch) {
-        try {
-            dumpedLatch.await();
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        }
+
+    // called by agent when it is initialized and has enabled events
+    static void agentStarted() {
+        attached = true;
     }
 
-    // The test1 vthreads are kept unmounted until interrupted after agent attach.
-    static final Runnable test1 = () -> {
-        synchronized (test1Threads) {
-            test1Threads.add(Thread.currentThread());
-        }
-        log("test1 vthread started");
-        ready0.countDown();
-        await(mready);
-        ready1.countDown(); // to guaranty state is not State.WAITING after await(mready)
-        try {
-            Thread.sleep(20000); // big timeout to keep unmounted until interrupted
-        } catch (InterruptedException ex) {
-            // it is expected, ignore
+    public static void main(String[] args) throws Exception {
+        VThreadRunner.ensureParallelism(SPINNING_THREAD_COUNT+1);
+
+        // start threads that park (unmount)
+        var threads1 = new ArrayList<Thread>();
+        for (int i = 0; i < PARKED_THREAD_COUNT; i++) {
+            var started = new AtomicBoolean();
+            var thread = Thread.startVirtualThread(() -> {
+                started.set(true);
+                LockSupport.park();
+            });
+
+            // wait for thread to start execution + park
+            while (!started.get()) {
+                Thread.sleep(10);
+            }
+            await(thread, Thread.State.WAITING);
+            threads1.add(thread);
         }
-        ready2.countDown();
-    };
-
-    // The test2 vthreads are kept mounted until agent attach.
-    static final Runnable test2 = () -> {
-        log("test2 vthread started");
-        ready0.countDown();
-        await(mready);
-        while (!attached) {
-            // keep mounted
+
+        // start threads that spin (stay mounted)
+        var threads2 = new ArrayList<Thread>();
+        for (int i = 0; i < SPINNING_THREAD_COUNT; i++) {
+            var started = new AtomicBoolean();
+            var thread = Thread.startVirtualThread(() -> {
+                started.set(true);
+                while (!attached) {
+                    Thread.onSpinWait();
+                }
+            });
+
+            // wait for thread to start execution
+            while (!started.get()) {
+                Thread.sleep(10);
+            }
+            threads2.add(thread);
         }
-        ready2.countDown();
-    };
-
-    // The test3 vthreads are kept mounted until agent attach.
-    static final Runnable test3 = () -> {
-        log("test3 vthread started");
-        ready0.countDown();
-        await(mready);
+
+        // attach to the current VM
+        VirtualMachine vm = VirtualMachine.attach(String.valueOf(ProcessHandle.current().pid()));
+        vm.loadAgentLibrary("VThreadEventTest");
+
+        // wait for agent to start
         while (!attached) {
-            // keep mounted
+            Thread.sleep(10);
         }
-        LockSupport.parkNanos(10_000_000L); // will cause extra mount and unmount
-        ready2.countDown();
-    };
 
-    public static void main(String[] args) throws Exception {
-        if (Runtime.getRuntime().availableProcessors() < 8) {
-            log("WARNING: test expects at least 8 processors.");
+        // unpark the threads that were parked
+        for (Thread thread : threads1) {
+            LockSupport.unpark(thread);
         }
-        try (ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor()) {
-            for (int i = 0; i < TCNT1; i++) {
-                executorService.execute(test1);
-            }
-            for (int i = 0; i < TCNT2; i++) {
-                executorService.execute(test2);
-            }
-            for (int i = 0; i < TCNT3; i++) {
-                executorService.execute(test3);
-            }
-            await(ready0);
-            mready.countDown();
-            await(ready1); // to guarantee state is not State.TIMED_WAITING after await(mready) in test1()
-            // wait for test1 threads to reach TIMED_WAITING state in sleep()
-            for (Thread t : test1Threads) {
-                Thread.State state = t.getState();
-                log("DBG: state: " + state);
-                while (state != Thread.State.TIMED_WAITING) {
-                    Thread.sleep(10);
-                    state = t.getState();
-                    log("DBG: state: " + state);
-                }
-            }
-
-            VirtualMachine vm = VirtualMachine.attach(String.valueOf(ProcessHandle.current().pid()));
-            vm.loadAgentLibrary("VThreadEventTest");
-            Thread.sleep(200); // to allow the agent to get ready
 
-            attached = true;
-            for (Thread t : test1Threads) {
-                 t.interrupt();
-            }
-            ready2.await();
+        // wait for all threads to terminate
+        for (Thread thread : threads1) {
+            thread.join();
         }
-        // wait until all VirtualThreadEnd events have been sent
-        for (int sleepNo = 1; threadEndCount() < THREAD_CNT; sleepNo++) {
-            Thread.sleep(100);
-            if (sleepNo % 100 == 0) { // 10 sec period of waiting
-                log("main: waited seconds: " + sleepNo/10);
-            }
+        for (Thread thread : threads2) {
+            thread.join();
         }
+
         int threadEndCnt = threadEndCount();
         int threadMountCnt = threadMountCount();
         int threadUnmountCnt = threadUnmountCount();
-        int threadEndExp = THREAD_CNT;
-        int threadMountExp = THREAD_CNT - TCNT2;
-        int threadUnmountExp = THREAD_CNT + TCNT3;
 
-        log("ThreadEnd cnt: "     + threadEndCnt     + " (expected: " + threadEndExp + ")");
-        log("ThreadMount cnt: "   + threadMountCnt   + " (expected: " + threadMountExp + ")");
-        log("ThreadUnmount cnt: " + threadUnmountCnt + " (expected: " + threadUnmountExp + ")");
+        int threadCount = PARKED_THREAD_COUNT + SPINNING_THREAD_COUNT;
+        log("VirtualThreadEnd events: " + threadEndCnt + ", expected: " + threadCount);
+        log("VirtualThreadMount events: " + threadMountCnt + ", expected: " + PARKED_THREAD_COUNT);
+        log("VirtualThreadUnmount events: " + threadUnmountCnt + ", expected: " + threadCount);
 
-        if (threadEndCnt != threadEndExp) {
-            log("FAILED: unexpected count of ThreadEnd events");
+
+        boolean failed = false;
+        if (threadEndCnt != threadCount) {
+            log("FAILED: unexpected count of VirtualThreadEnd events");
             failed = true;
         }
-        if (threadMountCnt != threadMountExp) {
-            log("FAILED: unexpected count of ThreadMount events");
+        if (threadMountCnt != PARKED_THREAD_COUNT) {
+            log("FAILED: unexpected count of VirtualThreadMount events");
             failed = true;
         }
-        if (threadUnmountCnt != threadUnmountExp) {
-            log("FAILED: unexpected count of ThreadUnmount events");
+        if (threadUnmountCnt != threadCount) {
+            log("FAILED: unexpected count of VirtualThreadUnmount events");
             failed = true;
         }
         if (failed) {
             throw new RuntimeException("FAILED: event count is wrong");
         }
     }
 
+    private static void await(Thread thread, Thread.State expectedState) throws InterruptedException {
+        Thread.State state = thread.getState();
+        while (state != expectedState) {
+            assert state != Thread.State.TERMINATED : "Thread has terminated";
+            Thread.sleep(10);
+            state = thread.getState();
+        }
+    }
+
 }
 
diff a/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/libVThreadEventTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/libVThreadEventTest.cpp
--- a/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/libVThreadEventTest.cpp
+++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/libVThreadEventTest.cpp
@@ -63,10 +63,15 @@
 JNIEXPORT jint JNICALL
 Agent_OnAttach(JavaVM *vm, char *options, void *reserved) {
   jvmtiEventCallbacks callbacks;
   jvmtiCapabilities caps;
   jvmtiError err;
+  JNIEnv *env;
+  jsize nVMs;
+  jint res;
+  jclass clazz;
+  jmethodID mid;
 
   LOG("Agent_OnAttach started\n");
   if (vm->GetEnv(reinterpret_cast<void **>(&jvmti), JVMTI_VERSION) != JNI_OK || !jvmti) {
     LOG("Could not initialize JVMTI env\n");
     return JNI_ERR;
@@ -95,10 +100,45 @@
 
   err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, EXT_EVENT_VIRTUAL_THREAD_UNMOUNT, nullptr);
   check_jvmti_error(err, "SetEventNotificationMode for VirtualThreadUnmount");
 
   LOG("vthread events enabled\n");
+
+  // call VThreadEventTest.agentStarted to notify test that agent has started
+
+  res = JNI_GetCreatedJavaVMs(&vm, 1, &nVMs);
+  if (res != JNI_OK) {
+      LOG("JNI_GetCreatedJavaVMs failed: %d\n", res);
+      return JNI_ERR;
+  }
+
+  res = vm->GetEnv((void **) &env, JNI_VERSION_21);
+  if (res != JNI_OK) {
+    LOG("GetEnv failed: %d\n", res);
+    return JNI_ERR;
+  }
+
+  clazz = env->FindClass("VThreadEventTest");
+  if (clazz == NULL) {
+      LOG("FindClass failed\n");
+      return JNI_ERR;
+  }
+
+  mid = env->GetStaticMethodID(clazz, "agentStarted", "()V");
+  if (mid == NULL) {
+      LOG("GetStaticMethodID failed\n");
+      return JNI_ERR;
+  }
+
+  env->CallStaticVoidMethod(clazz, mid);
+  if (env->ExceptionOccurred()) {
+      LOG("CallStaticVoidMethod failed\n");
+      return JNI_ERR;
+  }
+
+  LOG("Agent_OnAttach done\n");
+
   return JVMTI_ERROR_NONE;
 }
 
 } // extern "C"
 
diff a/test/jaxp/ProblemList-Virtual.txt b/test/jaxp/ProblemList-Virtual.txt
--- /dev/null
+++ b/test/jaxp/ProblemList-Virtual.txt
@@ -0,0 +1,4 @@
+####
+## Tests using custom path and con't be executed with virtual test thread factory
+
+javax/xml/jaxp/module/ServiceProviderTest/BasicModularXMLParserTest.java 000000 generic-all
diff a/test/jdk/ProblemList-Virtual.txt b/test/jdk/ProblemList-Virtual.txt
--- a/test/jdk/ProblemList-Virtual.txt
+++ b/test/jdk/ProblemList-Virtual.txt
@@ -50,30 +50,85 @@
 
 javax/management/remote/mandatory/connection/DeadLockTest.java 8309069 windows-x64
 
 javax/management/remote/mandatory/connection/ConnectionTest.java 8308352 windows-x64
 
+## DatagramSocket blocks ops are interruptible when invoked on virtual threads
+java/nio/channels/DatagramChannel/InterruptibleOrNot.java 0000000 generic-all
+java/net/DatagramSocket/InterruptibleDatagramSocket.java 0000000 generic-all
+
+# Underlying implementation is non-blocking
+java/nio/channels/SocketChannel/ShortWrite.java 0000000 generic-all
+
+sun/security/ssl/SSLSessionImpl/ResumptionUpdateBoundValues.java 8325384 generic-all
+
+jdk/jfr/event/compiler/TestCompilerCompile.java 8325392 generic-all
+
+
 ##########
 ## Tests incompatible with virtual test thread factory.
 ## There is no goal to run all test with virtual test thread factory.
 ## So any test might be added as incompatible, the bug id is not required.
 
+# test runs with jdk.virtualThreadScheduler.maxPoolSize=1
+java/lang/Thread/virtual/GetStackTraceWhenRunnable.java 0000000 generic-all
+
 # Incorrect stack/threadgroup/exception expectations for main thread
+com/sun/net/httpserver/bugs/B6431193.java 0000000 generic-all
 java/lang/StackWalker/DumpStackTest.java 0000000 generic-all
 java/lang/StackWalker/StackWalkTest.java 0000000 generic-all
 java/lang/StackWalker/CallerFromMain.java 0000000 generic-all
 java/lang/Thread/MainThreadTest.java 0000000 generic-all
 java/lang/Thread/UncaughtExceptionsTest.java 0000000 generic-all
-java/lang/Thread/virtual/GetStackTraceWhenRunnable.java 0000000 generic-all
 java/lang/invoke/condy/CondyNestedResolutionTest.java 0000000 generic-all
 java/lang/ref/OOMEInReferenceHandler.java 0000000 generic-all
 java/util/concurrent/locks/Lock/OOMEInAQS.java 0000000 generic-all
 jdk/internal/vm/Continuation/Scoped.java 0000000 generic-all
+jdk/jfr/event/runtime/TestThreadDumpEvent.java 0000000 generic-all
+jdk/jfr/event/runtime/TestThreadStartEvent.java 0000000 generic-all
+jdk/jfr/jvm/TestGetAllEventClasses.java 0000000 generic-all
 
 # The problems with permissions
-jdk/jfr/startupargs/TestDumpOnExit.java 0000000 generic-all
+java/io/File/GetXSpace.java 0000000 generic-all
+java/io/FilePermission/MergeName.java 0000000 generic-all
 java/lang/SecurityManager/modules/CustomSecurityManagerTest.java 0000000 generic-all
+java/net/httpclient/security/Security.java 0000000 generic-all
+java/security/AccessController/LimitedDoPrivileged.java	0000000 generic-all
 java/util/PluggableLocale/PermissionTest.java 0000000 generic-all
 java/util/Properties/StoreReproducibilityTest.java 0000000 generic-all
 java/util/Properties/StoreReproducibilityTest.java 0000000 generic-all
 javax/management/ImplementationVersion/ImplVersionTest.java 0000000 generic-all
 javax/management/remote/mandatory/version/ImplVersionTest.java 0000000 generic-all
+
+
+jdk/jfr/api/consumer/security/TestStreamingRemote.java 0000000 generic-all
+jdk/jfr/api/consumer/TestRecordedEventGetThread.java 0000000 generic-all
+jdk/jfr/api/consumer/TestRecordedObject.java 0000000 generic-all
+jdk/jfr/startupargs/TestDumpOnExit.java 0000000 generic-all
+
+sun/security/util/Resources/early/EarlyResources.java 0000000 generic-all
+tools/jimage/JImageToolTest.java 0000000 generic-all
+
+com/sun/management/DiagnosticCommandMBean/DcmdMBeanPermissionsTest.java 0000000 generic-all
+
+# Not supported on virtual threads
+com/sun/management/ThreadMXBean/ThreadAllocatedMemory.java#G1 0000000 generic-all
+com/sun/management/ThreadMXBean/ThreadAllocatedMemory.java#Serial 0000000 generic-all
+java/lang/management/ManagementFactory/ProxyTypeMapping.java 0000000 generic-all
+java/lang/management/ManagementFactory/ValidateOpenTypes.java 0000000 generic-all
+java/lang/management/ThreadMXBean/Locks.java 0000000 generic-all
+java/lang/management/ThreadMXBean/ThreadCpuTime.java 0000000 generic-all
+java/lang/management/ThreadMXBean/ThreadUserTime.java 0000000 generic-all
+
+# Too sensitive to thread counts
+java/lang/management/ThreadMXBean/ResetPeakThreadCount.java 0000000 generic-all
+
+# need to be reexamined
+sun/management/jmxremote/startstop/JMXStatusTest.java 0000000 generic-all
+sun/management/jmxremote/startstop/JMXStatusPerfCountersTest.java 0000000 generic-all
+sun/management/jmxremote/startstop/JMXStartStopTest.java 0000000 generic-all
+
+# Regression due to JDK-8329331
+java/foreign/TestSegments.java 			8331033 linux-x64,windows-x64
+java/foreign/TestSegmentAllocators.java		8331033 linux-x64,windows-x64
+java/foreign/TestSlices.java			8331033 linux-x64,windows-x64
+java/foreign/TestSegmentCopy.java		8331033 linux-x64,windows-x64
diff a/test/jdk/ProblemList-vthread-Xcomp.txt b/test/jdk/ProblemList-vthread-Xcomp.txt
--- /dev/null
+++ b/test/jdk/ProblemList-vthread-Xcomp.txt
@@ -0,0 +1,29 @@
+#
+# 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.
+#
+
+#############################################################################
+#
+# List of quarantined tests for testing in Xcomp mode with wrapper.
+#
+#############################################################################
+
diff a/test/jdk/java/lang/ScopedValue/ManyBindings.java b/test/jdk/java/lang/ScopedValue/ManyBindings.java
--- a/test/jdk/java/lang/ScopedValue/ManyBindings.java
+++ b/test/jdk/java/lang/ScopedValue/ManyBindings.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.
@@ -122,11 +122,11 @@
             newArray[index] = new KeyAndValue<>(key, newValue);
             n--;
         }
 
         // invoke recursively
-        carrier.run(() -> {
+        ScopedValue.runWhere(carrier, () -> {
             test(newArray, depth+1);
         });
 
         // check that the scoped values have the original values
         check(array);
diff a/test/jdk/java/lang/ScopedValue/ScopedValueAPI.java b/test/jdk/java/lang/ScopedValue/ScopedValueAPI.java
--- a/test/jdk/java/lang/ScopedValue/ScopedValueAPI.java
+++ b/test/jdk/java/lang/ScopedValue/ScopedValueAPI.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 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,12 +26,12 @@
  * @summary Test ScopedValue API
  * @enablePreview
  * @run junit ScopedValueAPI
  */
 
+import java.lang.ScopedValue.CallableOp;
 import java.util.NoSuchElementException;
-import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadFactory;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
@@ -46,94 +46,66 @@
     private static Stream<ThreadFactory> factories() {
         return Stream.of(Thread.ofPlatform().factory(), Thread.ofVirtual().factory());
     }
 
     /**
-     * Test that the run method is invoked.
+     * Test that runWhere invokes the Runnable's run method.
      */
     @ParameterizedTest
     @MethodSource("factories")
-    void testRun(ThreadFactory factory) throws Exception {
+    void testRunWhere(ThreadFactory factory) throws Exception {
         test(factory, () -> {
             class Box { static boolean executed; }
             ScopedValue<String> name = ScopedValue.newInstance();
             ScopedValue.runWhere(name, "duke", () -> { Box.executed = true; });
             assertTrue(Box.executed);
         });
     }
 
     /**
-     * Test the run method throwing an exception.
+     * Test runWhere when the run method throws an exception.
      */
     @ParameterizedTest
     @MethodSource("factories")
-    void testRunThrows(ThreadFactory factory) throws Exception {
+    void testRunWhereThrows(ThreadFactory factory) throws Exception {
         test(factory, () -> {
             class FooException extends RuntimeException {  }
             ScopedValue<String> name = ScopedValue.newInstance();
             Runnable op = () -> { throw new FooException(); };
             assertThrows(FooException.class, () -> ScopedValue.runWhere(name, "duke", op));
             assertFalse(name.isBound());
         });
     }
 
     /**
-     * Test that the call method is invoked.
+     * Test that callWhere invokes the CallableOp's call method.
      */
     @ParameterizedTest
     @MethodSource("factories")
-    void testCall(ThreadFactory factory) throws Exception {
+    void testCallWhere(ThreadFactory factory) throws Exception {
         test(factory, () -> {
             ScopedValue<String> name = ScopedValue.newInstance();
             String result = ScopedValue.callWhere(name, "duke", name::get);
             assertEquals("duke", result);
         });
     }
 
     /**
-     * Test that the get method is invoked.
+     * Test callWhere when the call method throws an exception.
      */
     @ParameterizedTest
     @MethodSource("factories")
-    void testGetWhere(ThreadFactory factory) throws Exception {
-        test(factory, () -> {
-            ScopedValue<String> name = ScopedValue.newInstance();
-            String result = ScopedValue.getWhere(name, "duke", (Supplier<String>)(name::get));
-            assertEquals("duke", result);
-        });
-    }
-
-    /**
-     * Test the call method throwing an exception.
-     */
-    @ParameterizedTest
-    @MethodSource("factories")
-    void testCallThrows(ThreadFactory factory) throws Exception {
+    void testCallWhereThrows(ThreadFactory factory) throws Exception {
         test(factory, () -> {
             class FooException extends RuntimeException {  }
             ScopedValue<String> name = ScopedValue.newInstance();
-            Callable<Void> op = () -> { throw new FooException(); };
+            CallableOp<Void, RuntimeException> op = () -> { throw new FooException(); };
             assertThrows(FooException.class, () -> ScopedValue.callWhere(name, "duke", op));
             assertFalse(name.isBound());
         });
     }
 
-    /**
-     * Test the get(Supplier) method throwing an exception.
-     */
-    @ParameterizedTest
-    @MethodSource("factories")
-    void testGetThrows(ThreadFactory factory) throws Exception {
-        test(factory, () -> {
-            class FooException extends RuntimeException {  }
-            ScopedValue<String> name = ScopedValue.newInstance();
-            Supplier<Void> op = () -> { throw new FooException(); };
-            assertThrows(FooException.class, () -> ScopedValue.getWhere(name, "duke", op));
-            assertFalse(name.isBound());
-        });
-    }
-
     /**
      * Test get method.
      */
     @ParameterizedTest
     @MethodSource("factories")
@@ -142,36 +114,27 @@
             ScopedValue<String> name1 = ScopedValue.newInstance();
             ScopedValue<String> name2 = ScopedValue.newInstance();
             assertThrows(NoSuchElementException.class, name1::get);
             assertThrows(NoSuchElementException.class, name2::get);
 
-            // run
+            // runWhere
             ScopedValue.runWhere(name1, "duke", () -> {
                 assertEquals("duke", name1.get());
                 assertThrows(NoSuchElementException.class, name2::get);
 
             });
             assertThrows(NoSuchElementException.class, name1::get);
             assertThrows(NoSuchElementException.class, name2::get);
 
-            // call
+            // callWhere
             ScopedValue.callWhere(name1, "duke", () -> {
                 assertEquals("duke", name1.get());
                 assertThrows(NoSuchElementException.class, name2::get);
                 return null;
             });
             assertThrows(NoSuchElementException.class, name1::get);
             assertThrows(NoSuchElementException.class, name2::get);
-
-            // get
-            ScopedValue.getWhere(name1, "duke", () -> {
-                assertEquals("duke", name1.get());
-                assertThrows(NoSuchElementException.class, name2::get);
-                return null;
-            });
-            assertThrows(NoSuchElementException.class, name1::get);
-            assertThrows(NoSuchElementException.class, name2::get);
         });
     }
 
     /**
      * Test isBound method.
@@ -183,28 +146,19 @@
             ScopedValue<String> name1 = ScopedValue.newInstance();
             ScopedValue<String> name2 = ScopedValue.newInstance();
             assertFalse(name1.isBound());
             assertFalse(name2.isBound());
 
-            // run
+            // runWhere
             ScopedValue.runWhere(name1, "duke", () -> {
                 assertTrue(name1.isBound());
                 assertFalse(name2.isBound());
             });
             assertFalse(name1.isBound());
             assertFalse(name2.isBound());
 
-            // call
-            ScopedValue.callWhere(name1, "duke", () -> {
-                assertTrue(name1.isBound());
-                assertFalse(name2.isBound());
-                return null;
-            });
-            assertFalse(name1.isBound());
-            assertFalse(name2.isBound());
-
-            // call
+            // callWhere
             ScopedValue.callWhere(name1, "duke", () -> {
                 assertTrue(name1.isBound());
                 assertFalse(name2.isBound());
                 return null;
             });
@@ -222,17 +176,17 @@
         test(factory, () -> {
             ScopedValue<String> name = ScopedValue.newInstance();
             assertNull(name.orElse(null));
             assertEquals("default", name.orElse("default"));
 
-            // run
+            // runWhere
             ScopedValue.runWhere(name, "duke", () -> {
                 assertEquals("duke", name.orElse(null));
                 assertEquals("duke", name.orElse("default"));
             });
 
-            // call
+            // callWhere
             ScopedValue.callWhere(name, "duke", () -> {
                 assertEquals("duke", name.orElse(null));
                 assertEquals("duke", name.orElse("default"));
                 return null;
             });
@@ -248,16 +202,16 @@
         test(factory, () -> {
             class FooException extends RuntimeException { }
             ScopedValue<String> name = ScopedValue.newInstance();
             assertThrows(FooException.class, () -> name.orElseThrow(FooException::new));
 
-            // run
+            // runWhere
             ScopedValue.runWhere(name, "duke", () -> {
                 assertEquals("duke", name.orElseThrow(FooException::new));
             });
 
-            // call
+            // callWhere
             ScopedValue.callWhere(name, "duke", () -> {
                 assertEquals("duke", name.orElseThrow(FooException::new));
                 return null;
             });
         });
@@ -271,42 +225,31 @@
     void testTwoBindings(ThreadFactory factory) throws Exception {
         test(factory, () -> {
             ScopedValue<String> name = ScopedValue.newInstance();
             ScopedValue<Integer> age = ScopedValue.newInstance();
 
-            // run
-            ScopedValue.where(name, "duke").where(age, 100).run(() -> {
+            // runWhere
+            var carrier = ScopedValue.where(name, "duke").where(age, 100);
+            ScopedValue.runWhere(carrier, () -> {
                 assertTrue(name.isBound());
                 assertTrue(age.isBound());
                 assertEquals("duke", name.get());
                 assertEquals(100, (int) age.get());
             });
             assertFalse(name.isBound());
             assertFalse(age.isBound());
 
-            // call
-            ScopedValue.where(name, "duke").where(age, 100).call(() -> {
-                assertTrue(name.isBound());
-                assertTrue(age.isBound());
-                assertEquals("duke", name.get());
-                assertEquals(100, (int) age.get());
-                return null;
-            });
-            assertFalse(name.isBound());
-            assertFalse(age.isBound());
-
-            // get
-            ScopedValue.where(name, "duke").where(age, 100).get(() -> {
+            // callWhere
+            ScopedValue.callWhere(carrier, () -> {
                 assertTrue(name.isBound());
                 assertTrue(age.isBound());
                 assertEquals("duke", name.get());
                 assertEquals(100, (int) age.get());
                 return null;
             });
             assertFalse(name.isBound());
             assertFalse(age.isBound());
-
         });
     }
 
     /**
      * Test rebinding.
@@ -315,11 +258,11 @@
     @MethodSource("factories")
     void testRebinding(ThreadFactory factory) throws Exception {
         test(factory, () -> {
             ScopedValue<String> name = ScopedValue.newInstance();
 
-            // run
+            // runWhere
             ScopedValue.runWhere(name, "duke", () -> {
                 assertTrue(name.isBound());
                 assertEquals("duke", name.get());
 
                 ScopedValue.runWhere(name, "duchess", () -> {
@@ -330,11 +273,11 @@
                 assertTrue(name.isBound());
                 assertEquals("duke", name.get());
             });
             assertFalse(name.isBound());
 
-            // call
+            // callWhere
             ScopedValue.callWhere(name, "duke", () -> {
                 assertTrue(name.isBound());
                 assertEquals("duke", name.get());
 
                 ScopedValue.callWhere(name, "duchess", () -> {
@@ -346,27 +289,10 @@
                 assertTrue(name.isBound());
                 assertEquals("duke", name.get());
                 return null;
             });
             assertFalse(name.isBound());
-
-            // get
-            ScopedValue.getWhere(name, "duke", () -> {
-                assertTrue(name.isBound());
-                assertEquals("duke", name.get());
-
-                ScopedValue.where(name, "duchess").get(() -> {
-                    assertTrue(name.isBound());
-                    assertEquals("duchess", name.get());
-                    return null;
-                });
-
-                assertTrue(name.isBound());
-                assertEquals("duke", name.get());
-                return null;
-            });
-            assertFalse(name.isBound());
         });
     }
 
     /**
      * Test rebinding from null vaue to another value.
@@ -375,11 +301,11 @@
     @MethodSource("factories")
     void testRebindingFromNull(ThreadFactory factory) throws Exception {
         test(factory, () -> {
             ScopedValue<String> name = ScopedValue.newInstance();
 
-            // run
+            // runWhere
             ScopedValue.runWhere(name, null, () -> {
                 assertTrue(name.isBound());
                 assertNull(name.get());
 
                 ScopedValue.runWhere(name, "duchess", () -> {
@@ -390,11 +316,11 @@
                 assertTrue(name.isBound());
                 assertNull(name.get());
             });
             assertFalse(name.isBound());
 
-            // call
+            // callWhere
             ScopedValue.callWhere(name, null, () -> {
                 assertTrue(name.isBound());
                 assertNull(name.get());
 
                 ScopedValue.callWhere(name, "duchess", () -> {
@@ -406,27 +332,10 @@
                 assertTrue(name.isBound());
                 assertNull(name.get());
                 return null;
             });
             assertFalse(name.isBound());
-
-            // getWhere
-            ScopedValue.getWhere(name, null, () -> {
-                assertTrue(name.isBound());
-                assertNull(name.get());
-
-                ScopedValue.getWhere(name, "duchess", () -> {
-                    assertTrue(name.isBound());
-                    assertTrue("duchess".equals(name.get()));
-                    return null;
-                });
-
-                assertTrue(name.isBound());
-                assertNull(name.get());
-                return null;
-            });
-            assertFalse(name.isBound());
         });
     }
 
     /**
      * Test rebinding to null value.
@@ -435,11 +344,11 @@
     @MethodSource("factories")
     void testRebindingToNull(ThreadFactory factory) throws Exception {
         test(factory, () -> {
             ScopedValue<String> name = ScopedValue.newInstance();
 
-            // run
+            // runWhere
             ScopedValue.runWhere(name, "duke", () -> {
                 assertTrue(name.isBound());
                 assertEquals("duke", name.get());
 
                 ScopedValue.runWhere(name, null, () -> {
@@ -450,11 +359,11 @@
                 assertTrue(name.isBound());
                 assertEquals("duke", name.get());
             });
             assertFalse(name.isBound());
 
-            // call
+            // callWhere
             ScopedValue.callWhere(name, "duke", () -> {
                 assertTrue(name.isBound());
                 assertEquals("duke", name.get());
 
                 ScopedValue.callWhere(name, null, () -> {
@@ -466,27 +375,10 @@
                 assertTrue(name.isBound());
                 assertEquals("duke", name.get());
                 return null;
             });
             assertFalse(name.isBound());
-
-            // get
-            ScopedValue.where(name, "duke").get(() -> {
-                assertTrue(name.isBound());
-                assertEquals("duke", name.get());
-
-                ScopedValue.where(name, null).get(() -> {
-                    assertTrue(name.isBound());
-                    assertNull(name.get());
-                    return null;
-                });
-
-                assertTrue(name.isBound());
-                assertEquals("duke", name.get());
-                return null;
-            });
-            assertFalse(name.isBound());
         });
     }
 
     /**
      * Test Carrier.get.
@@ -515,22 +407,23 @@
      */
     @Test
     void testNullPointerException() {
         ScopedValue<String> name = ScopedValue.newInstance();
 
-        assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "value"));
-        assertThrows(NullPointerException.class, () -> ScopedValue.runWhere(null, "value", () -> { }));
-        assertThrows(NullPointerException.class, () -> ScopedValue.getWhere(null, "value", () -> null));
+        assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "duke"));
+
+        assertThrows(NullPointerException.class, () -> ScopedValue.runWhere(null, "duke", () -> { }));
+        assertThrows(NullPointerException.class, () -> ScopedValue.runWhere(name, "duke", null));
+
+        assertThrows(NullPointerException.class, () -> ScopedValue.callWhere(null, "duke", () -> ""));
+        assertThrows(NullPointerException.class, () -> ScopedValue.callWhere(name, "duke", null));
 
         assertThrows(NullPointerException.class, () -> name.orElseThrow(null));
 
         var carrier = ScopedValue.where(name, "duke");
-        assertThrows(NullPointerException.class, () -> carrier.where(null, "value"));
+        assertThrows(NullPointerException.class, () -> carrier.where(null, "duke"));
         assertThrows(NullPointerException.class, () -> carrier.get((ScopedValue<?>)null));
-        assertThrows(NullPointerException.class, () -> carrier.get((Supplier<?>)null));
-        assertThrows(NullPointerException.class, () -> carrier.run(null));
-        assertThrows(NullPointerException.class, () -> carrier.call(null));
     }
 
     @FunctionalInterface
     private interface ThrowingRunnable {
         void run() throws Exception;
diff a/test/jdk/java/lang/ScopedValue/StressStackOverflow.java b/test/jdk/java/lang/ScopedValue/StressStackOverflow.java
--- a/test/jdk/java/lang/ScopedValue/StressStackOverflow.java
+++ b/test/jdk/java/lang/ScopedValue/StressStackOverflow.java
@@ -45,12 +45,12 @@
  * @requires vm.continuations
  * @enablePreview
  * @run main/othervm/timeout=300 -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations StressStackOverflow
  */
 
+import java.lang.ScopedValue.CallableOp;
 import java.time.Duration;
-import java.util.concurrent.Callable;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.StructureViolationException;
 import java.util.concurrent.StructuredTaskScope;
 import java.util.function.Supplier;
 
@@ -66,16 +66,16 @@
         TestFailureException(String s) { super(s); }
     }
 
     static final long DURATION_IN_NANOS = Duration.ofMinutes(1).toNanos();
 
-    // Test the ScopedValue recovery mechanism for stack overflows. We implement both Callable
+    // Test the ScopedValue recovery mechanism for stack overflows. We implement both CallableOp
     // and Runnable interfaces. Which one gets tested depends on the constructor argument.
-    class DeepRecursion implements Callable<Object>, Supplier<Object>, Runnable {
+    class DeepRecursion implements CallableOp<Object, RuntimeException>, Supplier<Object>, Runnable {
 
         enum Behaviour {
-            CALL, GET, RUN;
+            CALL, RUN;
             private static final Behaviour[] values = values();
             public static Behaviour choose(ThreadLocalRandom tlr) {
                 return values[tlr.nextInt(3)];
             }
         }
@@ -94,20 +94,19 @@
                 }
 
                 var nextRandomFloat = ThreadLocalRandom.current().nextFloat();
                 try {
                     switch (behaviour) {
-                        case CALL -> ScopedValue.where(el, el.get() + 1).call(() -> fibonacci_pad(20, this));
-                        case GET -> ScopedValue.where(el, el.get() + 1).get(() -> fibonacci_pad(20, this));
-                        case RUN -> ScopedValue.where(el, el.get() + 1).run(() -> fibonacci_pad(20, this));
+                        case CALL -> ScopedValue.callWhere(el, el.get() + 1, () -> fibonacci_pad(20, this));
+                        case RUN -> ScopedValue.runWhere(el, el.get() + 1, () -> fibonacci_pad(20, this));
                     }
                     if (!last.equals(el.get())) {
                         throw testFailureException;
                     }
                 } catch (StackOverflowError e) {
                     if (nextRandomFloat <= 0.1) {
-                        ScopedValue.where(el, el.get() + 1).run(this);
+                        ScopedValue.runWhere(el, el.get() + 1, this);
                     }
                 } catch (TestFailureException e) {
                     throw e;
                 } catch (Throwable throwable) {
                     // StackOverflowErrors cause many different failures. These include
@@ -184,11 +183,12 @@
         }
     }
 
     public void run() {
         try {
-            ScopedValue.where(inheritedValue, 42).where(el, 0).run(() -> {
+            var carrier = ScopedValue.where(inheritedValue, 42).where(el, 0);
+            ScopedValue.runWhere(carrier, () -> {
                 try (var scope = new StructuredTaskScope<>()) {
                     try {
                         if (ThreadLocalRandom.current().nextBoolean()) {
                             // Repeatedly test Scoped Values set by ScopedValue::call(), get(), and run()
                             final var deepRecursion
diff a/test/jdk/java/lang/Thread/virtual/BusyMonitorEnterWhenPinned.java b/test/jdk/java/lang/Thread/virtual/BusyMonitorEnterWhenPinned.java
--- /dev/null
+++ b/test/jdk/java/lang/Thread/virtual/BusyMonitorEnterWhenPinned.java
@@ -0,0 +1,141 @@
+/*
+ * 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
+ * @summary Test that a virtual thread waiting to enter a monitor, while pinning its
+ *   carrier, will retry until it enters the monitor. This avoids starvation when the
+ *   monitor is exited, an unmounted thread is the chosen successor, and the successor
+ *   can't continue because there are no carriers available.
+ * @modules java.base/java.lang:+open
+ * @library /test/lib
+ * @run main/othervm --enable-native-access=ALL-UNNAMED BusyMonitorEnterWhenPinned
+ */
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import jdk.test.lib.thread.VThreadPinner;
+
+public class BusyMonitorEnterWhenPinned {
+    public static void main(String[] args) throws Exception {
+        int iterations = (args.length > 0) ? Integer.parseInt(args[0]) : 10;
+        for (int i = 1; i <= iterations; i++) {
+            System.out.printf("%s -- iteration %d --%n", Instant.now(), i);
+            run();
+            System.out.println();
+        }
+    }
+
+    static void run() throws Exception {
+        var threads = new ArrayList<Thread>();
+
+        Object lock = new Object();
+        synchronized (lock) {
+
+            // start virtual threads that block on monitorenter
+            for (int i = 0; i < 100; i++) {
+                var started = new CountDownLatch(1);
+                Thread thread = Thread.startVirtualThread(() -> {
+                    started.countDown();
+                    synchronized (lock) {
+                        spin(20);
+                    }
+                });
+
+                // wait for thread to start and block
+                started.await();
+                await(thread, Thread.State.BLOCKED);
+                threads.add(thread);
+            }
+
+            // start virtual threads that block on monitorenter while pinned
+            int carriersAvailable = Runtime.getRuntime().availableProcessors();
+            if (Thread.currentThread().isVirtual()) {
+                carriersAvailable--;
+            }
+            for (int i = 0; i < 100; i++) {
+                var started = new CountDownLatch(1);
+                Thread thread = Thread.startVirtualThread(() -> {
+                    started.countDown();
+                    VThreadPinner.runPinned(() -> {
+                        synchronized (lock) {
+                            spin(20);
+                        }
+                    });
+                });
+
+                // if there are carriers available when wait until the thread blocks.
+                if (carriersAvailable > 0) {
+                    System.out.printf("%s waiting for thread #%d to block%n",
+                            Instant.now(), thread.threadId());
+                    started.await();
+                    await(thread, Thread.State.BLOCKED);
+                    carriersAvailable--;
+                }
+                threads.add(thread);
+            }
+
+        } // exit monitor
+
+        // wait for all threads to terminate
+        int threadsRemaining = threads.size();
+        while (threadsRemaining > 0) {
+            System.out.printf("%s waiting for %d threads to terminate%n",
+                    Instant.now(), threadsRemaining);
+            int terminated = 0;
+            for (Thread t : threads) {
+                if (t.join(Duration.ofSeconds(1))) {
+                    terminated++;
+                }
+            }
+            threadsRemaining = threads.size() - terminated;
+        }
+        System.out.printf("%s done%n", Instant.now());
+    }
+
+    /**
+     * Spin for the given number of milliseconds.
+     */
+    static void spin(long millis) {
+        long nanos = TimeUnit.MILLISECONDS.toNanos(millis);
+        long start = System.nanoTime();
+        while ((System.nanoTime() - start) < nanos) {
+            Thread.onSpinWait();
+        }
+    }
+
+    /**
+     * Wait for a thread to reach the expected state.
+     */
+    static void await(Thread thread, Thread.State expectedState) throws InterruptedException {
+        Thread.State state = thread.getState();
+        while (state != expectedState) {
+            assert state != Thread.State.TERMINATED : "Thread has terminated";
+            Thread.sleep(10);
+            state = thread.getState();
+        }
+    }
+}
diff a/test/jdk/java/lang/Thread/virtual/JfrEvents.java b/test/jdk/java/lang/Thread/virtual/JfrEvents.java
--- a/test/jdk/java/lang/Thread/virtual/JfrEvents.java
+++ b/test/jdk/java/lang/Thread/virtual/JfrEvents.java
@@ -38,27 +38,23 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.locks.LockSupport;
-import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import jdk.jfr.EventType;
 import jdk.jfr.Recording;
 import jdk.jfr.consumer.RecordedEvent;
 import jdk.jfr.consumer.RecordingFile;
 
 import jdk.test.lib.thread.VThreadPinner;
-import jdk.test.lib.thread.VThreadRunner.ThrowingRunnable;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
 import static org.junit.jupiter.api.Assertions.*;
 
 class JfrEvents {
 
     /**
@@ -91,104 +87,263 @@
             assertEquals(100, endCount);
         }
     }
 
     /**
-     * Arguments for testVirtualThreadPinned to test jdk.VirtualThreadPinned event.
-     *   [0] label/description
-     *   [1] the operation to park/wait
-     *   [2] the Thread.State when parked/waiting
-     *   [3] the action to unpark/notify the thread
+     * Test jdk.VirtualThreadPinned event when parking while pinned.
      */
-    static Stream<Arguments> pinnedCases() {
-        Object lock = new Object();
-
-        // park with native frame on stack
-        var finish1 = new AtomicBoolean();
-        var parkWhenPinned = Arguments.of(
-            "LockSupport.park when pinned",
-            (ThrowingRunnable<Exception>) () -> {
+    @ParameterizedTest
+    @ValueSource(booleans = { true, false })
+    void testParkWhenPinned(boolean timed) throws Exception {
+        try (Recording recording = new Recording()) {
+            recording.enable("jdk.VirtualThreadPinned");
+            recording.start();
+
+            var started = new AtomicBoolean();
+            var done = new AtomicBoolean();
+            var vthread = Thread.startVirtualThread(() -> {
                 VThreadPinner.runPinned(() -> {
-                    while (!finish1.get()) {
-                        LockSupport.park();
+                    started.set(true);
+                    while (!done.get()) {
+                        if (timed) {
+                            LockSupport.parkNanos(Long.MAX_VALUE);
+                        } else {
+                            LockSupport.park();
+                        }
                     }
                 });
-            },
-            Thread.State.WAITING,
-                (Consumer<Thread>) t -> {
-                    finish1.set(true);
-                    LockSupport.unpark(t);
+            });
+
+            try {
+                // wait for thread to start and park
+                awaitTrue(started);
+                await(vthread, timed ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
+            } finally {
+                done.set(true);
+                LockSupport.unpark(vthread);
+                vthread.join();
+                recording.stop();
+            }
+
+            assertContainsPinnedEvent(recording, vthread);
+        }
+    }
+
+    /**
+     * Test jdk.VirtualThreadPinned event when blocking on monitor while pinned.
+     */
+    @Test
+    void testBlockWhenPinned() throws Exception {
+        try (Recording recording = new Recording()) {
+            recording.enable("jdk.VirtualThreadPinned");
+            recording.start();
+
+            Object lock = new Object();
+
+            var started = new AtomicBoolean();
+            var vthread = Thread.ofVirtual().unstarted(() -> {
+                VThreadPinner.runPinned(() -> {
+                    started.set(true);
+                    synchronized (lock) { }
+                });
+            });
+
+            try {
+                synchronized (lock) {
+                    vthread.start();
+                    // wait for thread to start and block
+                    awaitTrue(started);
+                    await(vthread, Thread.State.BLOCKED);
                 }
-        );
+            } finally {
+                vthread.join();
+                recording.stop();
+            }
+
+            assertContainsPinnedEvent(recording, vthread);
+        }
+    }
 
-        // timed park with native frame on stack
-        var finish2 = new AtomicBoolean();
-        var timedParkWhenPinned = Arguments.of(
-            "LockSupport.parkNanos when pinned",
-            (ThrowingRunnable<Exception>) () -> {
+    /**
+     * Test jdk.VirtualThreadPinned event when waiting with Object.wait.
+     */
+    @ParameterizedTest
+    @ValueSource(booleans = { true, false })
+    void testObjectWait(boolean timed) throws Exception {
+        try (Recording recording = new Recording()) {
+            recording.enable("jdk.VirtualThreadPinned");
+            recording.start();
+
+            Object lock = new Object();
+
+            var started = new AtomicBoolean();
+            var vthread = Thread.startVirtualThread(() -> {
                 VThreadPinner.runPinned(() -> {
-                    while (!finish2.get()) {
-                        LockSupport.parkNanos(Long.MAX_VALUE);
+                    started.set(true);
+                    synchronized (lock) {
+                        try {
+                            if (timed) {
+                                lock.wait(Long.MAX_VALUE);
+                            } else {
+                                lock.wait();
+                            }
+                        } catch (InterruptedException e) {
+                            fail();
+                        }
                     }
                 });
-            },
-            Thread.State.TIMED_WAITING,
-            (Consumer<Thread>) t -> {
-                finish2.set(true);
-                LockSupport.unpark(t);
+            });
+
+            try {
+                // wait for thread to start and wait
+                awaitTrue(started);
+                await(vthread, timed ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
+            } finally {
+                synchronized (lock) {
+                    lock.notifyAll();
+                }
+                vthread.join();
+                recording.stop();
             }
-        );
 
-        return Stream.of(parkWhenPinned, timedParkWhenPinned);
+            assertContainsPinnedEvent(recording, vthread);
+        }
     }
 
     /**
-     * Test jdk.VirtualThreadPinned event.
+     * Test jdk.VirtualThreadPinned event when parking in a class initializer.
      */
-    @ParameterizedTest
-    @MethodSource("pinnedCases")
-    void testVirtualThreadPinned(String label,
-                                 ThrowingRunnable<Exception> parker,
-                                 Thread.State expectedState,
-                                 Consumer<Thread> unparker) throws Exception {
+    @Test
+    void testParkInClassInitializer() throws Exception {
+        class TestClass {
+            static {
+                LockSupport.park();
+            }
+            static void m() {
+                // do nothing
+            }
+        }
 
         try (Recording recording = new Recording()) {
             recording.enable("jdk.VirtualThreadPinned");
+            recording.start();
+
+            var started = new AtomicBoolean();
+            Thread vthread = Thread.startVirtualThread(() -> {
+                started.set(true);
+                TestClass.m();
+            });
+
+            try {
+                // wait for it to start and park
+                awaitTrue(started);
+                await(vthread, Thread.State.WAITING);
+            } finally {
+                LockSupport.unpark(vthread);
+                vthread.join();
+                recording.stop();
+            }
+
+            assertContainsPinnedEvent(recording, vthread);
+        }
+    }
+
+    /**
+     * Test jdk.VirtualThreadPinned event when blocking on monitor in a class initializer.
+     */
+    @Test
+    void testBlockInClassInitializer() throws Exception {
+        class LockHolder {
+            static final Object lock = new Object();
+        }
+        class TestClass {
+            static {
+                synchronized (LockHolder.lock) { }
+            }
+            static void m() {
+                // no nothing
+            }
+        }
 
+        try (Recording recording = new Recording()) {
+            recording.enable("jdk.VirtualThreadPinned");
             recording.start();
+
+            var started = new AtomicBoolean();
+            Thread vthread = Thread.ofVirtual().unstarted(() -> {
+                started.set(true);
+                TestClass.m();
+            });
+
             try {
-                var exception = new AtomicReference<Throwable>();
-                var thread = Thread.ofVirtual().start(() -> {
-                    try {
-                        parker.run();
-                    } catch (Throwable e) {
-                        exception.set(e);
-                    }
-                });
-                try {
-                    // wait for thread to park/wait
-                    Thread.State state = thread.getState();
-                    while (state != expectedState) {
-                        assertTrue(state != Thread.State.TERMINATED, thread.toString());
-                        Thread.sleep(10);
-                        state = thread.getState();
-                    }
-                } finally {
-                    unparker.accept(thread);
-                    thread.join();
-                    assertNull(exception.get());
+                synchronized (LockHolder.lock) {
+                    vthread.start();
+                    // wait for thread to start and block
+                    awaitTrue(started);
+                    await(vthread, Thread.State.BLOCKED);
                 }
             } finally {
+                vthread.join();
                 recording.stop();
             }
 
-            Map<String, Integer> events = sumEvents(recording);
-            System.err.println(events);
+            assertContainsPinnedEvent(recording, vthread);
+        }
+    }
 
-            // should have at least one pinned event
-            int pinnedCount = events.getOrDefault("jdk.VirtualThreadPinned", 0);
-            assertTrue(pinnedCount >= 1, "Expected one or more events");
+    /**
+     * Test jdk.VirtualThreadPinned event when waiting for a class initializer.
+     */
+    @Test
+    void testWaitingForClassInitializer() throws Exception {
+        class TestClass {
+            static {
+                LockSupport.park();
+            }
+            static void m() {
+                // do nothing
+            }
+        }
+
+        try (Recording recording = new Recording()) {
+            recording.enable("jdk.VirtualThreadPinned");
+            recording.start();
+
+            var started1 = new AtomicBoolean();
+            var started2 = new AtomicBoolean();
+
+            Thread vthread1 =  Thread.ofVirtual().unstarted(() -> {
+                started1.set(true);
+                TestClass.m();
+            });
+            Thread vthread2 = Thread.ofVirtual().unstarted(() -> {
+                started2.set(true);
+                TestClass.m();
+            });
+
+            try {
+                // start first virtual thread and wait for it to start + park
+                vthread1.start();
+                awaitTrue(started1);
+                await(vthread1, Thread.State.WAITING);
+
+                // start second virtual thread and wait for it to start
+                vthread2.start();
+                awaitTrue(started2);
+
+                // give time for second virtual thread to wait on the MutexLocker
+                Thread.sleep(3000);
+
+            } finally {
+                LockSupport.unpark(vthread1);
+                vthread1.join();
+                vthread2.join();
+                recording.stop();
+            }
+
+            // the recording should have a pinned event for vthread2
+            assertContainsPinnedEvent(recording, vthread2);
         }
     }
 
     /**
      * Test jdk.VirtualThreadSubmitFailed event.
@@ -208,13 +363,11 @@
                 // start a thread
                 Thread thread = factory.newThread(LockSupport::park);
                 thread.start();
 
                 // wait for thread to park
-                while (thread.getState() != Thread.State.WAITING) {
-                    Thread.sleep(10);
-                }
+                await(thread, Thread.State.WAITING);
 
                 // shutdown scheduler
                 pool.shutdown();
 
                 // unpark, the submit should fail
@@ -230,18 +383,27 @@
                 } catch (RejectedExecutionException expected) { }
             } finally {
                 recording.stop();
             }
 
-            Map<String, Integer> events = sumEvents(recording);
-            System.err.println(events);
-
-            int count = events.getOrDefault("jdk.VirtualThreadSubmitFailed", 0);
-            assertEquals(2, count);
+            List<RecordedEvent> submitFailedEvents = find(recording, "jdk.VirtualThreadSubmitFailed");
+            System.err.println(submitFailedEvents);
+            assertTrue(submitFailedEvents.size() == 2, "Expected two events");
         }
     }
 
+    /**
+     * Returns the list of events in the given recording with the given name.
+     */
+    private static List<RecordedEvent> find(Recording recording, String name) throws IOException {
+        Path recordingFile = recordingFile(recording);
+        return RecordingFile.readAllEvents(recordingFile)
+                .stream()
+                .filter(e -> e.getEventType().getName().equals(name))
+                .toList();
+    }
+
     /**
      * Read the events from the recording and return a map of event name to count.
      */
     private static Map<String, Integer> sumEvents(Recording recording) throws IOException {
         Path recordingFile = recordingFile(recording);
@@ -262,6 +424,40 @@
             recordingFile = Path.of("recording-" + recording.getId() + "-pid" + h.pid() + ".jfr");
             recording.dump(recordingFile);
         }
         return recordingFile;
     }
+
+    /**
+     * Assert that a recording contains a jdk.VirtualThreadPinned event on the given thread.
+     */
+    private void assertContainsPinnedEvent(Recording recording, Thread thread) throws IOException {
+        List<RecordedEvent> pinnedEvents = find(recording, "jdk.VirtualThreadPinned");
+        assertTrue(pinnedEvents.size() > 0, "No jdk.VirtualThreadPinned events in recording");
+        System.err.println(pinnedEvents);
+
+        long tid = thread.threadId();
+        assertTrue(pinnedEvents.stream()
+                        .anyMatch(e -> e.getThread().getJavaThreadId() == tid),
+                "jdk.VirtualThreadPinned for javaThreadId = " + tid + " not found");
+    }
+
+    /**
+     * Waits for the given boolean to be set to true.
+     */
+    private void awaitTrue(AtomicBoolean b) throws InterruptedException {
+        while (!b.get()) {
+            Thread.sleep(10);
+        }
+    }
+
+    /**
+     * Waits for the given thread to reach a given state.
+     */
+    private static void await(Thread thread, Thread.State expectedState) throws InterruptedException {
+        Thread.State state = thread.getState();
+        while (state != expectedState) {
+            Thread.sleep(10);
+            state = thread.getState();
+        }
+    }
 }
diff a/test/jdk/java/lang/Thread/virtual/MonitorEnterExit.java b/test/jdk/java/lang/Thread/virtual/MonitorEnterExit.java
--- /dev/null
+++ b/test/jdk/java/lang/Thread/virtual/MonitorEnterExit.java
@@ -0,0 +1,663 @@
+/*
+ * 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 id=default
+ * @summary Test virtual thread with monitor enter/exit
+ * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+ * @modules java.base/java.lang:+open
+ * @library /test/lib
+ * @run junit/othervm --enable-native-access=ALL-UNNAMED MonitorEnterExit
+ */
+
+/*
+ * @test id=LM_LEGACY
+ * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+ * @modules java.base/java.lang:+open
+ * @library /test/lib
+ * @run junit/othervm -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
+ */
+
+/*
+ * @test id=LM_LIGHTWEIGHT
+ * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+ * @modules java.base/java.lang:+open
+ * @library /test/lib
+ * @run junit/othervm -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
+ */
+
+/*
+ * @test id=Xint-LM_LEGACY
+ * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+ * @modules java.base/java.lang:+open
+ * @library /test/lib
+ * @run junit/othervm -Xint -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
+ */
+
+/*
+ * @test id=Xint-LM_LIGHTWEIGHT
+ * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+ * @modules java.base/java.lang:+open
+ * @library /test/lib
+ * @run junit/othervm -Xint -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
+ */
+
+/*
+ * @test id=Xcomp-TieredStopAtLevel1-LM_LEGACY
+ * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+ * @modules java.base/java.lang:+open
+ * @library /test/lib
+ * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=1 -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
+ */
+
+/*
+ * @test id=Xcomp-TieredStopAtLevel1-LM_LIGHTWEIGHT
+ * @modules java.base/java.lang:+open
+ * @library /test/lib
+ * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=1 -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
+ */
+
+/*
+ * @test id=Xcomp-noTieredCompilation-LM_LEGACY
+ * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+ * @modules java.base/java.lang:+open
+ * @library /test/lib
+ * @run junit/othervm -Xcomp -XX:-TieredCompilation -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
+ */
+
+/*
+ * @test id=Xcomp-noTieredCompilation-LM_LIGHTWEIGHT
+ * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+ * @modules java.base/java.lang:+open
+ * @library /test/lib
+ * @run junit/othervm -Xcomp -XX:-TieredCompilation -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
+ */
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.LockSupport;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import jdk.test.lib.thread.VThreadRunner;
+import jdk.test.lib.thread.VThreadPinner;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.RepeatedTest;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.api.condition.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assumptions.*;
+
+class MonitorEnterExit {
+    static final int MAX_VTHREAD_COUNT = 4 * Runtime.getRuntime().availableProcessors();
+    static final int MAX_ENTER_DEPTH = 256;
+
+    /**
+     * Test monitor enter with no contention.
+     */
+    @Test
+    void testEnterNoContention() throws Exception {
+        var lock = new Object();
+        VThreadRunner.run(() -> {
+            synchronized (lock) {
+                assertTrue(Thread.holdsLock(lock));
+            }
+            assertFalse(Thread.holdsLock(lock));
+        });
+    }
+
+    /**
+     * Test monitor enter with contention, monitor is held by platform thread.
+     */
+    @Test
+    void testEnterWhenHeldByPlatformThread() throws Exception {
+        testEnterWithContention();
+    }
+
+    /**
+     * Test monitor enter with contention, monitor is held by virtual thread.
+     */
+    @Test
+    void testEnterWhenHeldByVirtualThread() throws Exception {
+        VThreadRunner.run(this::testEnterWithContention);
+    }
+
+    /**
+     * Test monitor enter with contention, monitor will be held by caller thread.
+     */
+    private void testEnterWithContention() throws Exception {
+        var lock = new Object();
+        var started = new CountDownLatch(1);
+        var entered = new AtomicBoolean();
+        var vthread = Thread.ofVirtual().unstarted(() -> {
+            started.countDown();
+            synchronized (lock) {
+                assertTrue(Thread.holdsLock(lock));
+                entered.set(true);
+            }
+            assertFalse(Thread.holdsLock(lock));
+        });
+        try {
+            synchronized (lock) {
+                vthread.start();
+
+                // wait for thread to start and block
+                started.await();
+                await(vthread, Thread.State.BLOCKED);
+
+                assertFalse(entered.get());
+            }
+        } finally {
+            vthread.join();
+        }
+        assertTrue(entered.get());
+    }
+
+    /**
+     * Test monitor reenter.
+     */
+    @Test
+    void testReenter() throws Exception {
+        var lock = new Object();
+        VThreadRunner.run(() -> {
+            testReenter(lock, 0);
+            assertFalse(Thread.holdsLock(lock));
+        });
+    }
+
+    private void testReenter(Object lock, int depth) {
+        if (depth < MAX_ENTER_DEPTH) {
+            synchronized (lock) {
+                assertTrue(Thread.holdsLock(lock));
+                testReenter(lock, depth + 1);
+                assertTrue(Thread.holdsLock(lock));
+            }
+        }
+    }
+
+    /**
+     * Test monitor reenter when there are other threads blocked trying to enter.
+     */
+    @Test
+    void testReenterWithContention() throws Exception {
+        var lock = new Object();
+        VThreadRunner.run(() -> {
+            List<Thread> threads = new ArrayList<>();
+            testReenter(lock, 0, threads);
+
+            // wait for threads to terminate
+            for (Thread vthread : threads) {
+                vthread.join();
+            }
+        });
+    }
+
+    private void testReenter(Object lock, int depth, List<Thread> threads) throws Exception {
+        if (depth < MAX_ENTER_DEPTH) {
+            synchronized (lock) {
+                assertTrue(Thread.holdsLock(lock));
+
+                // start platform or virtual thread that blocks waiting to enter
+                var started = new CountDownLatch(1);
+                ThreadFactory factory = ThreadLocalRandom.current().nextBoolean()
+                        ? Thread.ofPlatform().factory()
+                        : Thread.ofVirtual().factory();
+                var thread = factory.newThread(() -> {
+                    started.countDown();
+                    synchronized (lock) {
+                        /* do nothing */
+                    }
+                });
+                thread.start();
+
+                // wait for thread to start and block
+                started.await();
+                await(thread, Thread.State.BLOCKED);
+                threads.add(thread);
+
+                // test reenter
+                testReenter(lock, depth + 1, threads);
+            }
+        }
+    }
+
+    /**
+     * Test monitor enter when pinned.
+     */
+    @Test
+    void testEnterWhenPinned() throws Exception {
+        var lock = new Object();
+        VThreadPinner.runPinned(() -> {
+            synchronized (lock) {
+                assertTrue(Thread.holdsLock(lock));
+            }
+            assertFalse(Thread.holdsLock(lock));
+        });
+    }
+
+    /**
+     * Test monitor reenter when pinned.
+     */
+    @Test
+    void testReenterWhenPinned() throws Exception {
+        VThreadRunner.run(() -> {
+            var lock = new Object();
+            synchronized (lock) {
+                VThreadPinner.runPinned(() -> {
+                    assertTrue(Thread.holdsLock(lock));
+                    synchronized (lock) {
+                        assertTrue(Thread.holdsLock(lock));
+                    }
+                    assertTrue(Thread.holdsLock(lock));
+                });
+            }
+            assertFalse(Thread.holdsLock(lock));
+        });
+    }
+
+    /**
+     * Test contended monitor enter when pinned. Monitor is held by platform thread.
+     */
+    @Test
+    void testContendedEnterWhenPinnedHeldByPlatformThread() throws Exception {
+        testEnterWithContentionWhenPinned();
+    }
+
+    /**
+     * Test contended monitor enter when pinned. Monitor is held by virtual thread.
+     */
+    @Test
+    void testContendedEnterWhenPinnedHeldByVirtualThread() throws Exception {
+        // need at least two carrier threads
+        int previousParallelism = VThreadRunner.ensureParallelism(2);
+        try {
+            VThreadRunner.run(this::testEnterWithContentionWhenPinned);
+        } finally {
+            VThreadRunner.setParallelism(previousParallelism);
+        }
+    }
+
+    /**
+     * Test contended monitor enter when pinned, monitor will be held by caller thread.
+     */
+    private void testEnterWithContentionWhenPinned() throws Exception {
+        var lock = new Object();
+        var started = new CountDownLatch(1);
+        var entered = new AtomicBoolean();
+        Thread vthread  = Thread.ofVirtual().unstarted(() -> {
+            VThreadPinner.runPinned(() -> {
+                started.countDown();
+                synchronized (lock) {
+                    entered.set(true);
+                }
+            });
+        });
+        synchronized (lock) {
+            // start thread and wait for it to block
+            vthread.start();
+            started.await();
+            await(vthread, Thread.State.BLOCKED);
+            assertFalse(entered.get());
+        }
+        vthread.join();
+
+        // check thread entered monitor
+        assertTrue(entered.get());
+    }
+
+    /**
+     * Test that parking while holding a monitor releases the carrier.
+     */
+    @ParameterizedTest
+    @ValueSource(booleans = { true, false })
+    void testReleaseWhenParked(boolean reenter) throws Exception {
+        assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
+        try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
+            Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
+
+            var lock = new Object();
+
+            // thread enters (and maybe reenters) a monitor and parks
+            var started = new CountDownLatch(1);
+            var vthread1 = builder.start(() -> {
+                started.countDown();
+                synchronized (lock) {
+                    if (reenter) {
+                        synchronized (lock) {
+                            LockSupport.park();
+                        }
+                    } else {
+                        LockSupport.park();
+                    }
+                }
+            });
+
+            try {
+                // wait for thread to start and park
+                started.await();
+                await(vthread1, Thread.State.WAITING);
+
+                // carrier should be released, use it for another thread
+                var executed = new AtomicBoolean();
+                var vthread2 = builder.start(() -> {
+                    executed.set(true);
+                });
+                vthread2.join();
+                assertTrue(executed.get());
+            } finally {
+                LockSupport.unpark(vthread1);
+                vthread1.join();
+            }
+        }
+    }
+
+    /**
+     * Test that blocking waiting to enter a monitor releases the carrier.
+     */
+    @Test
+    void testReleaseWhenBlocked() throws Exception {
+        assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
+        try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
+            Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
+
+            var lock = new Object();
+
+            // thread enters monitor
+            var started = new CountDownLatch(1);
+            var vthread1 = builder.unstarted(() -> {
+                started.countDown();
+                synchronized (lock) {
+                }
+            });
+
+            try {
+                synchronized (lock) {
+                    // start thread and wait for it to block
+                    vthread1.start();
+                    started.await();
+                    await(vthread1, Thread.State.BLOCKED);
+
+                    // carrier should be released, use it for another thread
+                    var executed = new AtomicBoolean();
+                    var vthread2 = builder.start(() -> {
+                        executed.set(true);
+                    });
+                    vthread2.join();
+                    assertTrue(executed.get());
+                }
+            } finally {
+                vthread1.join();
+            }
+        }
+    }
+
+    /**
+     * Test lots of virtual threads parked while holding a monitor. If the number of
+     * virtual threads exceeds the number of carrier threads then this test will hang if
+     * parking doesn't release the carrier.
+     */
+    @Test
+    void testManyParkedThreads() throws Exception {
+        Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
+        var done = new AtomicBoolean();
+        for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
+            var lock = new Object();
+            var started = new CountDownLatch(1);
+            var vthread = Thread.ofVirtual().start(() -> {
+                started.countDown();
+                synchronized (lock) {
+                    while (!done.get()) {
+                        LockSupport.park();
+                    }
+                }
+            });
+            // wait for thread to start and park
+            started.await();
+            await(vthread, Thread.State.WAITING);
+            vthreads[i] = vthread;
+        }
+
+        // cleanup
+        done.set(true);
+        for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
+            var vthread = vthreads[i];
+            LockSupport.unpark(vthread);
+            vthread.join();
+        }
+    }
+
+    /**
+     * Test lots of virtual threads blocked waiting to enter a monitor. If the number
+     * of virtual threads exceeds the number of carrier threads this test will hang if
+     * carriers aren't released.
+     */
+    @Test
+    void testManyBlockedThreads() throws Exception {
+        Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
+        var lock = new Object();
+        synchronized (lock) {
+            for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
+                var started = new CountDownLatch(1);
+                var vthread = Thread.ofVirtual().start(() -> {
+                    started.countDown();
+                    synchronized (lock) {
+                    }
+                });
+                // wait for thread to start and block
+                started.await();
+                await(vthread, Thread.State.BLOCKED);
+                vthreads[i] = vthread;
+            }
+        }
+
+        // cleanup
+        for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
+            vthreads[i].join();
+        }
+    }
+
+    /**
+     * Returns a stream of elements that are ordered pairs of platform and virtual thread
+     * counts. 0,2,4,..16 platform threads. 2,4,6,..32 virtual threads.
+     */
+    static Stream<Arguments> threadCounts() {
+        return IntStream.range(0, 17)
+                .filter(i -> i % 2 == 0)
+                .mapToObj(i -> i)
+                .flatMap(np -> IntStream.range(2, 33)
+                        .filter(i -> i % 2 == 0)
+                        .mapToObj(vp -> Arguments.of(np, vp)));
+    }
+
+    /**
+     * Test mutual exclusion of monitors with platform and virtual threads
+     */
+    @ParameterizedTest
+    @MethodSource("threadCounts")
+    void testMutualExclusion(int nPlatformThreads, int nVirtualThreads) throws Exception {
+        class Counter {
+            int count;
+            synchronized void increment() {
+                count++;
+                Thread.yield();
+            }
+        }
+        var counter = new Counter();
+        int nThreads = nPlatformThreads + nVirtualThreads;
+        var threads = new Thread[nThreads];
+        int index = 0;
+        for (int i = 0; i < nPlatformThreads; i++) {
+            threads[index] = Thread.ofPlatform()
+                    .name("platform-" + index)
+                    .unstarted(counter::increment);
+            index++;
+        }
+        for (int i = 0; i < nVirtualThreads; i++) {
+            threads[index] = Thread.ofVirtual()
+                    .name("virtual-" + index)
+                    .unstarted(counter::increment);
+            index++;
+        }
+        // start all threads
+        for (Thread thread : threads) {
+            thread.start();
+        }
+        // wait for all threads to terminate
+        for (Thread thread : threads) {
+            thread.join();
+        }
+        assertEquals(nThreads, counter.count);
+    }
+
+    /**
+     * Test unblocking a virtual thread waiting to enter a monitor held by a platform thread.
+     */
+    @RepeatedTest(20)
+    void testUnblockingByPlatformThread() throws Exception {
+        testUnblocking();
+    }
+
+    /**
+     * Test unblocking a virtual thread waiting to enter a monitor held by another
+     * virtual thread.
+     */
+    @RepeatedTest(20)
+    void testUnblockingByVirtualThread() throws Exception {
+        VThreadRunner.run(this::testUnblocking);
+    }
+
+    /**
+     * Test unblocking a virtual thread waiting to enter a monitor, monitor will be
+     * initially be held by caller thread.
+     */
+    private void testUnblocking() throws Exception {
+        var lock = new Object();
+        var started = new CountDownLatch(1);
+        var entered = new AtomicBoolean();
+        var vthread = Thread.ofVirtual().unstarted(() -> {
+            started.countDown();
+            synchronized (lock) {
+                entered.set(true);
+            }
+        });
+        try {
+            synchronized (lock) {
+                vthread.start();
+                started.await();
+
+                // random delay before exiting monitor
+                switch (ThreadLocalRandom.current().nextInt(4)) {
+                    case 0 -> { /* no delay */}
+                    case 1 -> Thread.onSpinWait();
+                    case 2 -> Thread.yield();
+                    case 3 -> await(vthread, Thread.State.BLOCKED);
+                    default -> fail();
+                }
+
+                assertFalse(entered.get());
+            }
+        } finally {
+            vthread.join();
+        }
+        assertTrue(entered.get());
+    }
+
+    /**
+     * Test that unblocking a virtual thread waiting to enter a monitor does not consume
+     * the thread's parking permit.
+     */
+    @Test
+    void testParkingPermitNotConsumed() throws Exception {
+        var lock = new Object();
+        var started = new CountDownLatch(1);
+        var vthread = Thread.ofVirtual().unstarted(() -> {
+            started.countDown();
+            LockSupport.unpark(Thread.currentThread());
+            synchronized (lock) { }  // should block
+            LockSupport.park();      // should not park
+        });
+
+        synchronized (lock) {
+            vthread.start();
+            // wait for thread to start and block
+            started.await();
+            await(vthread, Thread.State.BLOCKED);
+        }
+        vthread.join();
+    }
+
+    /**
+     * Test that unblocking a virtual thread waiting to enter a monitor does not make
+     * available the thread's parking permit.
+     */
+    @Test
+    void testParkingPermitNotOffered() throws Exception {
+        var lock = new Object();
+        var started = new CountDownLatch(1);
+        var vthread = Thread.ofVirtual().unstarted(() -> {
+            started.countDown();
+            synchronized (lock) { }  // should block
+            LockSupport.park();      // should park
+        });
+
+        synchronized (lock) {
+            vthread.start();
+            // wait for thread to start and block
+            started.await();
+            await(vthread, Thread.State.BLOCKED);
+        }
+
+        try {
+            // wait for thread to park, it should not terminate
+            await(vthread, Thread.State.WAITING);
+            vthread.join(Duration.ofMillis(100));
+            assertEquals(Thread.State.WAITING, vthread.getState());
+        } finally {
+            LockSupport.unpark(vthread);
+            vthread.join();
+        }
+    }
+
+    /**
+     * Waits for the given thread to reach a given state.
+     */
+    private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
+        Thread.State state = thread.getState();
+        while (state != expectedState) {
+            assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
+            Thread.sleep(10);
+            state = thread.getState();
+        }
+    }
+}
diff a/test/jdk/java/lang/Thread/virtual/MonitorWaitNotify.java b/test/jdk/java/lang/Thread/virtual/MonitorWaitNotify.java
--- a/test/jdk/java/lang/Thread/virtual/MonitorWaitNotify.java
+++ b/test/jdk/java/lang/Thread/virtual/MonitorWaitNotify.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 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.
@@ -27,11 +27,14 @@
  * @modules java.base/java.lang:+open
  * @library /test/lib
  * @run junit MonitorWaitNotify
  */
 
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.LockSupport;
 
 import jdk.test.lib.thread.VThreadRunner;
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 
@@ -152,10 +155,209 @@
                 }
             }
         });
     }
 
+    /**
+     * Testing invoking Object.wait with interrupt status set.
+     */
+    @Test
+    void testWaitWithInterruptSet() throws Exception {
+        VThreadRunner.run(() -> {
+            Object obj = new Object();
+            synchronized (obj) {
+                Thread.currentThread().interrupt();
+                assertThrows(InterruptedException.class, obj::wait);
+                assertFalse(Thread.currentThread().isInterrupted());
+            }
+        });
+    }
+
+    /**
+     * Test interrupting a virtual thread waiting in Object.wait.
+     */
+    @Test
+    void testInterruptWait() throws Exception {
+        var lock = new Object();
+        var started = new CountDownLatch(1);
+        var interruptedException = new AtomicBoolean();
+        var vthread = Thread.ofVirtual().start(() -> {
+            started.countDown();
+            synchronized (lock) {
+                try {
+                    lock.wait();
+                } catch (InterruptedException e) {
+                    interruptedException.set(true);
+                }
+            }
+        });
+
+        // wait for thread to start and wait
+        started.await();
+        await(vthread, Thread.State.WAITING);
+
+        // interrupt thread, it should throw InterruptedException and terminate
+        vthread.interrupt();
+        vthread.join();
+        assertTrue(interruptedException.get());
+    }
+
+    /**
+     * Test interrupting a virtual thread blocked waiting to reenter after waiting.
+     */
+    @Test
+    void testInterruptReenter() throws Exception {
+        var lock = new Object();
+        var started = new CountDownLatch(1);
+        var interruptedException = new AtomicBoolean();
+        var vthread = Thread.ofVirtual().start(() -> {
+            started.countDown();
+            synchronized (lock) {
+                try {
+                    lock.wait();
+                } catch (InterruptedException e) {
+                    interruptedException.set(true);
+                }
+            }
+        });
+
+        // wait for thread to start and wait
+        started.await();
+        await(vthread, Thread.State.WAITING);
+
+        // notify, thread should block waiting to reenter
+        synchronized (lock) {
+            lock.notifyAll();
+            await(vthread, Thread.State.BLOCKED);
+            vthread.interrupt();
+        }
+
+        vthread.join();
+        assertFalse(interruptedException.get());
+        assertTrue(vthread.isInterrupted());
+    }
+
+    /**
+     * Test that Object.wait does not consume the thread's parking permit.
+     */
+    @Test
+    void testParkingPermitNotConsumed() throws Exception {
+        var lock = new Object();
+        var started = new CountDownLatch(1);
+        var completed = new AtomicBoolean();
+        var vthread = Thread.ofVirtual().start(() -> {
+            started.countDown();
+            LockSupport.unpark(Thread.currentThread());
+            synchronized (lock) {
+                try {
+                    lock.wait();
+                } catch (InterruptedException e) {
+                    fail("wait interrupted");
+                }
+            }
+            LockSupport.park();      // should not park
+            completed.set(true);
+        });
+
+        // wait for thread to start and wait
+        started.await();
+        await(vthread, Thread.State.WAITING);
+
+        // wakeup thread
+        synchronized (lock) {
+            lock.notifyAll();
+        }
+
+        // thread should terminate
+        vthread.join();
+        assertTrue(completed.get());
+    }
+
+    /**
+     * Test that Object.wait does not make available the thread's parking permit.
+     */
+    @Test
+    void testParkingPermitNotOffered() throws Exception {
+        var lock = new Object();
+        var started = new CountDownLatch(1);
+        var readyToPark = new CountDownLatch(1);
+        var completed = new AtomicBoolean();
+        var vthread = Thread.ofVirtual().start(() -> {
+            started.countDown();
+            synchronized (lock) {
+                try {
+                    lock.wait();
+                } catch (InterruptedException e) {
+                    fail("wait interrupted");
+                }
+            }
+            readyToPark.countDown();
+            LockSupport.park();      // should park
+            completed.set(true);
+        });
+
+        // wait for thread to start and wait
+        started.await();
+        await(vthread, Thread.State.WAITING);
+
+        // wakeup thread
+        synchronized (lock) {
+            lock.notifyAll();
+        }
+
+        // thread should park
+        readyToPark.await();
+        await(vthread, Thread.State.WAITING);
+
+        LockSupport.unpark(vthread);
+
+        // thread should terminate
+        vthread.join();
+        assertTrue(completed.get());
+    }
+
+    /**
+     * Test that wait(long) throws IAE when timeout is negative.
+     */
+    @Test
+    void testIllegalArgumentException() throws Exception {
+        VThreadRunner.run(() -> {
+            Object obj = new Object();
+            synchronized (obj) {
+                assertThrows(IllegalArgumentException.class, () -> obj.wait(-1L));
+                assertThrows(IllegalArgumentException.class, () -> obj.wait(-1000L));
+                assertThrows(IllegalArgumentException.class, () -> obj.wait(Long.MIN_VALUE));
+            }
+        });
+    }
+
+    /**
+     * Test that wait throws IMSE when not owner.
+     */
+    @Test
+    void testIllegalMonitorStateException() throws Exception {
+        VThreadRunner.run(() -> {
+            Object obj = new Object();
+            assertThrows(IllegalMonitorStateException.class, () -> obj.wait());
+            assertThrows(IllegalMonitorStateException.class, () -> obj.wait(0));
+            assertThrows(IllegalMonitorStateException.class, () -> obj.wait(1000));
+            assertThrows(IllegalMonitorStateException.class, () -> obj.wait(Long.MAX_VALUE));
+        });
+    }
+
+    /**
+     * Waits for the given thread to reach a given state.
+     */
+    private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
+        Thread.State state = thread.getState();
+        while (state != expectedState) {
+            assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
+            Thread.sleep(10);
+            state = thread.getState();
+        }
+    }
+
     /**
      * Schedule a thread to be interrupted after a delay.
      */
     private static void scheduleInterrupt(Thread thread, long delay) {
         Runnable interruptTask = () -> {
diff a/test/jdk/java/lang/Thread/virtual/MonitorsTest.java b/test/jdk/java/lang/Thread/virtual/MonitorsTest.java
--- /dev/null
+++ b/test/jdk/java/lang/Thread/virtual/MonitorsTest.java
@@ -0,0 +1,415 @@
+/*
+ * 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 id=default
+ * @summary Test virtual threads using synchronized
+ * @library /test/lib
+ * @modules java.base/java.lang:+open
+ * @run junit/othervm -Xint MonitorsTest
+ * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=3 MonitorsTest
+ * @run junit/othervm -Xcomp -XX:-TieredCompilation MonitorsTest
+ * @run junit/othervm MonitorsTest
+ */
+
+/**
+ * @test id=gc
+ * @requires vm.debug
+ * @library /test/lib
+ * @modules java.base/java.lang:+open
+ * @run junit/othervm -XX:+UnlockDiagnosticVMOptions -XX:+FullGCALot -XX:FullGCALotInterval=1000 MonitorsTest
+ */
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.*;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class MonitorsTest {
+    final int CARRIER_COUNT = 8;
+    ExecutorService scheduler = Executors.newFixedThreadPool(CARRIER_COUNT);
+
+    static final Object globalLock = new Object();
+    static volatile boolean finish = false;
+    static volatile int counter = 0;
+
+    /////////////////////////////////////////////////////////////////////
+    //////////////////////////// BASIC TESTS ////////////////////////////
+    /////////////////////////////////////////////////////////////////////
+
+    static final Runnable FOO = () -> {
+        Object lock = new Object();
+        synchronized(lock) {
+            while(!finish) {
+                Thread.yield();
+            }
+        }
+        System.out.println("Exiting FOO from thread " + Thread.currentThread().getName());
+    };
+
+    static final Runnable BAR = () -> {
+        synchronized(globalLock) {
+            counter++;
+        }
+        System.out.println("Exiting BAR from thread " + Thread.currentThread().getName());
+    };
+
+    /**
+     *  Test yield while holding monitor.
+     */
+    @Test
+    void testBasic() throws Exception {
+        final int VT_COUNT = CARRIER_COUNT;
+
+        // Create first batch of VT threads.
+        Thread firstBatch[] = new Thread[VT_COUNT];
+        for (int i = 0; i < VT_COUNT; i++) {
+            firstBatch[i] = ThreadBuilders.virtualThreadBuilder(scheduler).name("FirstBatchVT-" + i).start(FOO);
+        }
+
+        // Give time for all threads to reach Thread.yield
+        Thread.sleep(1000);
+
+        // Create second batch of VT threads.
+        Thread secondBatch[] = new Thread[VT_COUNT];
+        for (int i = 0; i < VT_COUNT; i++) {
+            secondBatch[i] = ThreadBuilders.virtualThreadBuilder(scheduler).name("SecondBatchVT-" + i).start(BAR);
+        }
+
+        while(counter != VT_COUNT) {}
+
+        finish = true;
+
+        for (int i = 0; i < VT_COUNT; i++) {
+            firstBatch[i].join();
+        }
+        for (int i = 0; i < VT_COUNT; i++) {
+            secondBatch[i].join();
+        }
+    }
+
+    static final Runnable BAR2 = () -> {
+        synchronized(globalLock) {
+            counter++;
+        }
+        recursive2(10);
+        System.out.println("Exiting BAR2 from thread " + Thread.currentThread().getName() + "with counter=" + counter);
+    };
+
+    static void recursive2(int count) {
+        synchronized(Thread.currentThread()) {
+            if (count > 0) {
+                recursive2(count - 1);
+            } else {
+                synchronized(globalLock) {
+                    counter++;
+                    Thread.yield();
+                }
+            }
+        }
+    }
+
+    /**
+     *  Test yield while holding monitor with recursive locking.
+     */
+    @Test
+    void testRecursive() throws Exception {
+        final int VT_COUNT = CARRIER_COUNT;
+        counter = 0;
+        finish = false;
+
+        // Create first batch of VT threads.
+        Thread firstBatch[] = new Thread[VT_COUNT];
+        for (int i = 0; i < VT_COUNT; i++) {
+            firstBatch[i] = ThreadBuilders.virtualThreadBuilder(scheduler).name("FirstBatchVT-" + i).start(FOO);
+        }
+
+        // Give time for all threads to reach Thread.yield
+        Thread.sleep(1000);
+
+        // Create second batch of VT threads.
+        Thread secondBatch[] = new Thread[VT_COUNT];
+        for (int i = 0; i < VT_COUNT; i++) {
+            secondBatch[i] = ThreadBuilders.virtualThreadBuilder(scheduler).name("SecondBatchVT-" + i).start(BAR2);
+        }
+
+        while(counter != 2*VT_COUNT) {}
+
+        finish = true;
+
+        for (int i = 0; i < VT_COUNT; i++) {
+            firstBatch[i].join();
+        }
+        for (int i = 0; i < VT_COUNT; i++) {
+            secondBatch[i].join();
+        }
+    }
+
+    static final Runnable FOO3 = () -> {
+        synchronized(globalLock) {
+            while(!finish) {
+                Thread.yield();
+            }
+        }
+        System.out.println("Exiting FOO3 from thread " + Thread.currentThread().getName());
+    };
+
+    /**
+     *  Test contention on monitorenter.
+     */
+    @Test
+    void testContention() throws Exception {
+        final int VT_COUNT = CARRIER_COUNT * 8;
+        counter = 0;
+        finish = false;
+
+        // Create batch of VT threads.
+        Thread batch[] = new Thread[VT_COUNT];
+        for (int i = 0; i < VT_COUNT; i++) {
+            batch[i] = ThreadBuilders.virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(FOO3);
+        }
+
+        // Give time for all threads to reach synchronized(globalLock)
+        Thread.sleep(2000);
+
+        finish = true;
+
+        for (int i = 0; i < VT_COUNT; i++) {
+            batch[i].join();
+        }
+    }
+
+    /////////////////////////////////////////////////////////////////////
+    //////////////////////////// MAIN TESTS /////////////////////////////
+    /////////////////////////////////////////////////////////////////////
+
+    static final int MONITORS_CNT = 12;
+    static Object[] globalLockArray;
+    static AtomicInteger workerCount = new AtomicInteger(0);
+
+    static void recursive4_1(int depth, int lockNumber) {
+        if (depth > 0) {
+            recursive4_1(depth - 1, lockNumber);
+        } else {
+            if (Math.random() < 0.5) {
+                Thread.yield();
+            }
+            recursive4_2(lockNumber);
+        }
+    }
+
+    static void recursive4_2(int lockNumber) {
+        if (lockNumber + 2 <= MONITORS_CNT - 1) {
+            lockNumber += 2;
+            synchronized(globalLockArray[lockNumber]) {
+                Thread.yield();
+                recursive4_2(lockNumber);
+            }
+        }
+    }
+
+    static final Runnable FOO4 = () -> {
+        while (!finish) {
+            int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITORS_CNT - 1);
+            synchronized(globalLockArray[lockNumber]) {
+                recursive4_1(lockNumber, lockNumber);
+            }
+        }
+        workerCount.getAndIncrement();
+        System.out.println("Exiting FOO4 from thread " + Thread.currentThread().getName());
+    };
+
+    /**
+     *  Test contention on monitorenter with extra monitors on stack shared by all threads.
+     */
+    @Test
+    void testContentionMultipleMonitors() throws Exception {
+        final int VT_COUNT = CARRIER_COUNT * 8;
+        workerCount.getAndSet(0);
+        finish = false;
+
+        globalLockArray = new Object[MONITORS_CNT];
+        for (int i = 0; i < MONITORS_CNT; i++) {
+            globalLockArray[i] = new Object();
+        }
+
+        Thread batch[] = new Thread[VT_COUNT];
+        for (int i = 0; i < VT_COUNT; i++) {
+            batch[i] = ThreadBuilders.virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(FOO4);
+        }
+
+        Thread.sleep(10000);
+        finish = true;
+
+        for (int i = 0; i < VT_COUNT; i++) {
+            batch[i].join();
+        }
+
+        if (workerCount.get() != VT_COUNT) {
+            throw new RuntimeException("testContentionMultipleMonitors2 failed. Expected " + VT_COUNT + "but found " + workerCount.get());
+        }
+    }
+
+
+    static void recursive5_1(int depth, int lockNumber, Object[] myLockArray) {
+        if (depth > 0) {
+            recursive5_1(depth - 1, lockNumber, myLockArray);
+        } else {
+            if (Math.random() < 0.5) {
+                Thread.yield();
+            }
+            recursive5_2(lockNumber, myLockArray);
+        }
+    }
+
+    static void recursive5_2(int lockNumber, Object[] myLockArray) {
+        if (lockNumber + 2 <= MONITORS_CNT - 1) {
+            lockNumber += 2;
+            synchronized (myLockArray[lockNumber]) {
+                if (Math.random() < 0.5) {
+                    Thread.yield();
+                }
+                synchronized (globalLockArray[lockNumber]) {
+                    Thread.yield();
+                    recursive5_2(lockNumber, myLockArray);
+                }
+            }
+        }
+    }
+
+    static final Runnable FOO5 = () -> {
+        Object[] myLockArray = new Object[MONITORS_CNT];
+        for (int i = 0; i < MONITORS_CNT; i++) {
+            myLockArray[i] = new Object();
+        }
+
+        while (!finish) {
+            int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITORS_CNT - 1);
+            synchronized (myLockArray[lockNumber]) {
+                synchronized (globalLockArray[lockNumber]) {
+                    recursive5_1(lockNumber, lockNumber, myLockArray);
+                }
+            }
+        }
+        workerCount.getAndIncrement();
+        System.out.println("Exiting FOO5 from thread " + Thread.currentThread().getName());
+    };
+
+    /**
+     *  Test contention on monitorenter with extra monitors on stack both local only and shared by all threads.
+     */
+    @Test
+    void testContentionMultipleMonitors2() throws Exception {
+        final int VT_COUNT = CARRIER_COUNT * 8;
+        workerCount.getAndSet(0);
+        finish = false;
+
+        globalLockArray = new Object[MONITORS_CNT];
+        for (int i = 0; i < MONITORS_CNT; i++) {
+            globalLockArray[i] = new Object();
+        }
+
+        // Create batch of VT threads.
+        Thread batch[] = new Thread[VT_COUNT];
+        for (int i = 0; i < VT_COUNT; i++) {
+            //Thread.ofVirtual().name("FirstBatchVT-" + i).start(FOO);
+            batch[i] = ThreadBuilders.virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(FOO5);
+        }
+
+        Thread.sleep(10000);
+
+        finish = true;
+
+        for (int i = 0; i < VT_COUNT; i++) {
+            batch[i].join();
+        }
+
+        if (workerCount.get() != VT_COUNT) {
+            throw new RuntimeException("testContentionMultipleMonitors2 failed. Expected " + VT_COUNT + "but found " + workerCount.get());
+        }
+    }
+
+    static synchronized void recursive6(int depth, Object myLock) {
+        if (depth > 0) {
+            recursive6(depth - 1, myLock);
+        } else {
+            if (Math.random() < 0.5) {
+                Thread.yield();
+            } else {
+                synchronized (myLock) {
+                    Thread.yield();
+                }
+            }
+        }
+    }
+
+    static final Runnable FOO6 = () -> {
+        Object myLock = new Object();
+
+        while (!finish) {
+            int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITORS_CNT - 1);
+            synchronized (myLock) {
+                synchronized (globalLockArray[lockNumber]) {
+                    recursive6(lockNumber, myLock);
+                }
+            }
+        }
+        workerCount.getAndIncrement();
+        System.out.println("Exiting FOO5 from thread " + Thread.currentThread().getName());
+    };
+
+    /**
+     *  Test contention on monitorenter with synchronized methods.
+     */
+    @Test
+    void testContentionMultipleMonitors3() throws Exception {
+        final int VT_COUNT = CARRIER_COUNT * 8;
+        workerCount.getAndSet(0);
+        finish = false;
+
+
+        globalLockArray = new Object[MONITORS_CNT];
+        for (int i = 0; i < MONITORS_CNT; i++) {
+            globalLockArray[i] = new Object();
+        }
+
+        // Create batch of VT threads.
+        Thread batch[] = new Thread[VT_COUNT];
+        for (int i = 0; i < VT_COUNT; i++) {
+            batch[i] = ThreadBuilders.virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(FOO6);
+        }
+
+        Thread.sleep(10000);
+
+        finish = true;
+
+        for (int i = 0; i < VT_COUNT; i++) {
+            batch[i].join();
+        }
+
+        if (workerCount.get() != VT_COUNT) {
+            throw new RuntimeException("testContentionMultipleMonitors2 failed. Expected " + VT_COUNT + "but found " + workerCount.get());
+        }
+    }
+}
diff a/test/jdk/java/lang/Thread/virtual/SynchronizedNative.java b/test/jdk/java/lang/Thread/virtual/SynchronizedNative.java
--- /dev/null
+++ b/test/jdk/java/lang/Thread/virtual/SynchronizedNative.java
@@ -0,0 +1,474 @@
+/*
+ * 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 id=default
+ * @summary Test virtual threads with a synchronized native method and a native method
+ *      that enter/exits a monitor with JNI MonitorEnter/MonitorExit
+ * @requires vm.continuations
+ * @modules java.base/java.lang:+open
+ * @library /test/lib
+ * @run junit/othervm SynchronizedNative
+ */
+
+/*
+ * @test id=Xint
+ * @requires vm.continuations
+ * @modules java.base/java.lang:+open
+ * @library /test/lib
+ * @run junit/othervm -Xint SynchronizedNative
+ */
+
+/*
+ * @test id=Xcomp-TieredStopAtLevel1
+ * @requires vm.continuations
+ * @modules java.base/java.lang:+open
+ * @library /test/lib
+ * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=1 SynchronizedNative
+ */
+
+/*
+ * @test id=Xcomp-noTieredCompilation
+ * @requires vm.continuations
+ * @modules java.base/java.lang:+open
+ * @library /test/lib
+ * @run junit/othervm -Xcomp -XX:-TieredCompilation SynchronizedNative
+ */
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Phaser;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.LockSupport;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import jdk.test.lib.thread.VThreadRunner;
+import jdk.test.lib.thread.VThreadPinner;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import static org.junit.jupiter.api.Assertions.*;
+
+class SynchronizedNative {
+    private static int initialParallelism;
+
+    @BeforeAll
+    static void setup() throws Exception {
+        // need at least two carriers when main thread is a virtual thread
+        if (Thread.currentThread().isVirtual()) {
+            initialParallelism = VThreadRunner.ensureParallelism(2);
+        }
+        System.loadLibrary("SynchronizedNative");
+    }
+
+    @AfterAll
+    static void finish() {
+        // restore parallelism if needed
+        if (initialParallelism > 0) {
+            VThreadRunner.setParallelism(initialParallelism);
+        }
+    }
+
+    /**
+     * Test entering a monitor with a synchronized native method, no contention.
+     */
+    @Test
+    void testEnter() throws Exception {
+        Object lock = this;
+        VThreadRunner.run(() -> {
+            runWithSynchronizedNative(() -> {
+                assertTrue(Thread.holdsLock(lock));
+            });
+            assertFalse(Thread.holdsLock(lock));
+        });
+    }
+
+    /**
+     * Test reentering a monitor with synchronized native method, no contention.
+     */
+    @Test
+    void testReenter() throws Exception {
+        Object lock = this;
+        VThreadRunner.run(() -> {
+
+            // enter, reenter with a synchronized native method
+            synchronized (lock) {
+                runWithSynchronizedNative(() -> {
+                    assertTrue(Thread.holdsLock(lock));
+                });
+                assertTrue(Thread.holdsLock(lock));
+            }
+            assertFalse(Thread.holdsLock(lock));
+
+            // enter with synchronized native method, renter with synchronized statement
+            runWithSynchronizedNative(() -> {
+                assertTrue(Thread.holdsLock(lock));
+                synchronized (lock) {
+                    assertTrue(Thread.holdsLock(lock));
+                }
+                assertTrue(Thread.holdsLock(lock));
+            });
+            assertFalse(Thread.holdsLock(lock));
+
+            // enter with synchronized native method, reenter with synchronized native method
+            runWithSynchronizedNative(() -> {
+                assertTrue(Thread.holdsLock(lock));
+                runWithSynchronizedNative(() -> {
+                    assertTrue(Thread.holdsLock(lock));
+                });
+                assertTrue(Thread.holdsLock(lock));
+            });
+            assertFalse(Thread.holdsLock(lock));
+        });
+    }
+
+    /**
+     * Test entering a monitor with a synchronized native method and with contention.
+     */
+    @Test
+    void testEnterWithContention() throws Exception {
+        var lock = this;
+        var started = new CountDownLatch(1);
+        var entered = new AtomicBoolean();
+        var vthread = Thread.ofVirtual().unstarted(() -> {
+            started.countDown();
+            runWithSynchronizedNative(() -> {
+                assertTrue(Thread.holdsLock(lock));
+                entered.set(true);
+            });
+        });
+        try {
+            synchronized (lock) {
+                vthread.start();
+
+                // wait for thread to start and block
+                started.await();
+                await(vthread, Thread.State.BLOCKED);
+
+                assertFalse(entered.get());
+            }
+        } finally {
+            vthread.join();
+        }
+        assertTrue(entered.get());
+    }
+
+    /**
+     * Returns a stream of elements that are ordered pairs of platform and virtual thread
+     * counts. 0,2,4 platform threads. 2,4,6,8 virtual threads.
+     */
+    static Stream<Arguments> threadCounts() {
+        return IntStream.range(0, 5)
+                .filter(i -> i % 2 == 0)
+                .mapToObj(i -> i)
+                .flatMap(np -> IntStream.range(2, 9)
+                        .filter(i -> i % 2 == 0)
+                        .mapToObj(vp -> Arguments.of(np, vp)));
+    }
+
+    /**
+     * Execute a task concurrently from both platform and virtual threads.
+     */
+    private void executeConcurrently(int nPlatformThreads,
+                                     int nVirtualThreads,
+                                     Runnable task) throws Exception {
+        int parallism = nVirtualThreads;
+        if (Thread.currentThread().isVirtual()) {
+            parallism++;
+        }
+        int previousParallelism = VThreadRunner.ensureParallelism(parallism);
+        try {
+            int nthreads = nPlatformThreads + nVirtualThreads;
+            var phaser = new Phaser(nthreads + 1);
+
+            // start all threads
+            var threads = new Thread[nthreads];
+            int index = 0;
+            for (int i = 0; i < nPlatformThreads; i++) {
+                threads[index++] = Thread.ofPlatform().start(() -> {
+                    phaser.arriveAndAwaitAdvance();
+                    task.run();
+                });
+            }
+            for (int i = 0; i < nVirtualThreads; i++) {
+                threads[index++] = Thread.ofVirtual().start(() -> {
+                    phaser.arriveAndAwaitAdvance();
+                    task.run();
+                });
+            }
+
+            // wait for all threads to start
+            phaser.arriveAndAwaitAdvance();
+            System.err.printf("  %d threads started%n", nthreads);
+
+            // wait for all threads to terminate
+            for (Thread thread : threads) {
+                if (thread != null) {
+                    System.err.printf("  join %s ...%n", thread);
+                    thread.join();
+                }
+            }
+        } finally {
+            // reset parallelism
+            VThreadRunner.setParallelism(previousParallelism);
+        }
+    }
+
+
+    /**
+     * Test entering a monitor with a synchronized native method from many threads
+     * at the same time.
+     */
+    @ParameterizedTest
+    @MethodSource("threadCounts")
+    void testEnterConcurrently(int nPlatformThreads, int nVirtualThreads) throws Exception {
+        var counter = new Object() {
+            int value;
+            int value() { return value; }
+            void increment() { value++; }
+        };
+        var lock = this;
+        executeConcurrently(nPlatformThreads, nVirtualThreads, () -> {
+            runWithSynchronizedNative(() -> {
+                assertTrue(Thread.holdsLock(lock));
+                counter.increment();
+                LockSupport.parkNanos(100_000_000);  // 100ms
+            });
+        });
+        synchronized (lock) {
+            assertEquals(nPlatformThreads + nVirtualThreads, counter.value());
+        }
+    }
+
+    /**
+     * Test entering a monitor with JNI MonitorEnter.
+     */
+    @Test
+    void testEnterInNative() throws Exception {
+        Object lock = new Object();
+        VThreadRunner.run(() -> {
+            runWithMonitorEnteredInNative(lock, () -> {
+                assertTrue(Thread.holdsLock(lock));
+            });
+            assertFalse(Thread.holdsLock(lock));
+        });
+    }
+
+    /**
+     * Test reentering a monitor with JNI MonitorEnter.
+     */
+    @Test
+    void testReenterInNative() throws Exception {
+        Object lock = new Object();
+        VThreadRunner.run(() -> {
+
+            // enter, reenter with JNI MonitorEnter
+            synchronized (lock) {
+                runWithMonitorEnteredInNative(lock, () -> {
+                    assertTrue(Thread.holdsLock(lock));
+                });
+                assertTrue(Thread.holdsLock(lock));
+            }
+            assertFalse(Thread.holdsLock(lock));
+
+            // enter with JNI MonitorEnter, renter with synchronized statement
+            runWithMonitorEnteredInNative(lock, () -> {
+                assertTrue(Thread.holdsLock(lock));
+                synchronized (lock) {
+                    assertTrue(Thread.holdsLock(lock));
+                }
+                assertTrue(Thread.holdsLock(lock));
+            });
+            assertFalse(Thread.holdsLock(lock));
+
+            // enter with JNI MonitorEnter, renter with JNI MonitorEnter
+            runWithMonitorEnteredInNative(lock, () -> {
+                assertTrue(Thread.holdsLock(lock));
+                runWithMonitorEnteredInNative(lock, () -> {
+                    assertTrue(Thread.holdsLock(lock));
+                });
+                assertTrue(Thread.holdsLock(lock));
+            });
+            assertFalse(Thread.holdsLock(lock));
+        });
+    }
+
+    /**
+     * Test entering a monitor with JNI MonitorEnter and with contention.
+     */
+    @Test
+    void testEnterInNativeWithContention() throws Exception {
+        var lock = new Object();
+        var started = new CountDownLatch(1);
+        var entered = new AtomicBoolean();
+        var vthread = Thread.ofVirtual().unstarted(() -> {
+            started.countDown();
+            runWithMonitorEnteredInNative(lock, () -> {
+                assertTrue(Thread.holdsLock(lock));
+                entered.set(true);
+            });
+        });
+        try {
+            synchronized (lock) {
+                vthread.start();
+
+                // wait for thread to start and block
+                started.await();
+                await(vthread, Thread.State.BLOCKED);
+
+                assertFalse(entered.get());
+            }
+        } finally {
+            vthread.join();
+        }
+        assertTrue(entered.get());
+    }
+
+    /**
+     * Test entering a monitor with JNI MonitorEnter from many threads at the same time.
+     */
+    @ParameterizedTest
+    @MethodSource("threadCounts")
+    void testEnterInNativeConcurrently(int nPlatformThreads, int nVirtualThreads) throws Exception {
+        var counter = new Object() {
+            int value;
+            int value() { return value; }
+            void increment() { value++; }
+        };
+        var lock = counter;
+        executeConcurrently(nPlatformThreads, nVirtualThreads, () -> {
+            runWithMonitorEnteredInNative(lock, () -> {
+                assertTrue(Thread.holdsLock(lock));
+                counter.increment();
+                LockSupport.parkNanos(100_000_000);  // 100ms
+            });
+        });
+        synchronized (lock) {
+            assertEquals(nPlatformThreads + nVirtualThreads, counter.value());
+        }
+    }
+
+    /**
+     * Test parking with synchronized native method on stack.
+     */
+    @Test
+    void testParkingWhenPinned() throws Exception {
+        var lock = this;
+        var started = new CountDownLatch(1);
+        var entered = new AtomicBoolean();
+        var done = new AtomicBoolean();
+        var vthread = Thread.ofVirtual().start(() -> {
+            started.countDown();
+            runWithSynchronizedNative(() -> {
+                assertTrue(Thread.holdsLock(lock));
+                entered.set(true);
+                while (!done.get()) {
+                    LockSupport.park();
+                }
+            });
+        });
+        try {
+            // wait for thread to start and block
+            started.await();
+            await(vthread, Thread.State.WAITING);
+        } finally {
+            done.set(true);
+            LockSupport.unpark(vthread);
+            vthread.join();
+        }
+        assertTrue(entered.get());
+    }
+
+    /**
+     * Test blocking with synchronized native method on stack.
+     */
+    @Test
+    void testBlockingWhenPinned() throws Exception {
+        var lock1 = this;
+        var lock2 = new Object();
+
+        var started = new CountDownLatch(1);
+        var entered1 = new AtomicBoolean();   // set to true when vthread enters lock1
+        var entered2 = new AtomicBoolean();   // set to true when vthread enters lock2
+
+        var vthread = Thread.ofVirtual().unstarted(() -> {
+            started.countDown();
+            runWithSynchronizedNative(() -> {
+                assertTrue(Thread.holdsLock(lock1));
+                entered1.set(true);
+                synchronized (lock2) {   // should block
+                    assertTrue(Thread.holdsLock(lock2));
+                    entered2.set(true);
+                }
+            });
+        });
+        try {
+            synchronized (lock2) {
+                // start thread and wait for it to block trying to enter lock2
+                vthread.start();
+                started.await();
+                await(vthread, Thread.State.BLOCKED);
+
+                assertTrue(entered1.get());
+                assertFalse(entered2.get());
+            }
+        } finally {
+            vthread.join();
+        }
+        assertTrue(entered2.get());
+    }
+
+    /**
+     * Invokes the given task's run method while holding the monitor for "this".
+     */
+    private synchronized native void runWithSynchronizedNative(Runnable task);
+
+    /**
+     * Invokes the given task's run method while holding the monitor for the given
+     * object. The monitor is entered with JNI MonitorEnter, and exited with JNI MonitorExit.
+     */
+    private native void runWithMonitorEnteredInNative(Object lock, Runnable task);
+
+    /**
+     * Called from native methods to run the given task.
+     */
+    private void run(Runnable task) {
+        task.run();
+    }
+
+    /**
+     * Waits for the given thread to reach a given state.
+     */
+    private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
+        Thread.State state = thread.getState();
+        while (state != expectedState) {
+            assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
+            Thread.sleep(10);
+            state = thread.getState();
+        }
+    }
+}
diff a/test/jdk/java/lang/Thread/virtual/ThreadAPI.java b/test/jdk/java/lang/Thread/virtual/ThreadAPI.java
--- a/test/jdk/java/lang/Thread/virtual/ThreadAPI.java
+++ b/test/jdk/java/lang/Thread/virtual/ThreadAPI.java
@@ -1106,14 +1106,45 @@
         }
         assertEquals(List.of("A", "B", "A", "B"), list);
     }
 
     /**
-     * Test Thread.yield when thread is pinned by native frame.
+     * Test Thread.yield releases carrier when virtual thread holds a monitor.
      */
     @Test
     void testYield2() throws Exception {
+        assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
+        var list = new CopyOnWriteArrayList<String>();
+        try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
+            Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
+            ThreadFactory factory = builder.factory();
+            var lock = new Object();
+            var thread = factory.newThread(() -> {
+                list.add("A");
+                var child = factory.newThread(() -> {
+                    list.add("B");
+                    synchronized (lock) {
+                        Thread.yield();
+                    }
+                    list.add("B");
+                });
+                child.start();
+                Thread.yield();
+                list.add("A");
+                try { child.join(); } catch (InterruptedException e) { }
+            });
+            thread.start();
+            thread.join();
+        }
+        assertEquals(List.of("A", "B", "A", "B"), list);
+    }
+
+    /**
+     * Test Thread.yield when thread is pinned by native frame.
+     */
+    @Test
+    void testYield3() throws Exception {
         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
         var list = new CopyOnWriteArrayList<String>();
         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
             ThreadFactory factory = builder.factory();
@@ -1137,11 +1168,11 @@
 
     /**
      * Test Thread.yield does not consume the thread's parking permit.
      */
     @Test
-    void testYield3() throws Exception {
+    void testYield4() throws Exception {
         var thread = Thread.ofVirtual().start(() -> {
             LockSupport.unpark(Thread.currentThread());
             Thread.yield();
             LockSupport.park();  // should not park
         });
@@ -1150,11 +1181,11 @@
 
     /**
      * Test Thread.yield does not make available the thread's parking permit.
      */
     @Test
-    void testYield4() throws Exception {
+    void testYield5() throws Exception {
         var thread = Thread.ofVirtual().start(() -> {
             Thread.yield();
             LockSupport.park();  // should park
         });
         try {
diff a/test/jdk/java/lang/Thread/virtual/TracePinnedThreads.java b/test/jdk/java/lang/Thread/virtual/TracePinnedThreads.java
--- a/test/jdk/java/lang/Thread/virtual/TracePinnedThreads.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * @test
- * @bug 8284161 8289284 8322846
- * @summary Basic test of debugging option to trace pinned threads
- * @requires vm.continuations
- * @library /test/lib
- * @run junit/othervm -Djdk.tracePinnedThreads=full TracePinnedThreads
- * @run junit/othervm -Djdk.tracePinnedThreads=short TracePinnedThreads
- */
-
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
-import java.time.Duration;
-import java.util.concurrent.Executors;
-import java.util.concurrent.locks.LockSupport;
-
-import jdk.test.lib.thread.VThreadRunner;
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-
-class TracePinnedThreads {
-    static final Object lock = new Object();
-
-    /**
-     * Parks current thread for 1 second.
-     */
-    private static void park() {
-        long nanos = Duration.ofSeconds(1).toNanos();
-        LockSupport.parkNanos(nanos);
-    }
-
-    /**
-     * Invokes the park method through a native frame to park the current
-     * thread for 1 second.
-     */
-    private static native void invokePark();
-
-    /**
-     * Test parking inside synchronized block.
-     */
-    @Test
-    void testPinnedCausedBySynchronizedBlock() throws Exception {
-        String output = run(() -> {
-            synchronized (lock) {
-                park();
-            }
-        });
-        assertContains(output, "reason:MONITOR");
-        assertContains(output, "<== monitors:1");
-    }
-
-    /**
-     * Test parking with native frame on stack.
-     */
-    @Test
-    void testPinnedCausedByNativeMethod() throws Exception {
-        System.loadLibrary("TracePinnedThreads");
-        String output = run(() -> invokePark());
-        assertContains(output, "reason:NATIVE");
-        assertContains(output, "(Native Method)");
-    }
-
-    /**
-     * Test parking in class initializer.
-     */
-    @Test
-    void testPinnedCausedByClassInitializer() throws Exception {
-        class C {
-            static {
-                park();
-            }
-        }
-        String output = run(C::new);
-        assertContains(output, "reason:NATIVE");
-        assertContains(output, "<clinit>");
-    }
-
-    /**
-     * Test contention writing to System.out when pinned. The test creates four threads
-     * that write to System.out when pinned, this is enough to potentially deadlock
-     * without the changes in JDK-8322846.
-     */
-    @Test
-    void testContention() throws Exception {
-        // use several classes to avoid duplicate stack traces
-        class C1 {
-            synchronized void print() {
-                System.out.println("hello");
-            }
-        }
-        class C2 {
-            synchronized void print() {
-                System.out.println("hello");
-            }
-        }
-        class C3 {
-            synchronized void print() {
-                System.out.println("hello");
-            }
-        }
-        class C4 {
-            synchronized void print() {
-                System.out.println("hello");
-            }
-        }
-
-        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
-            executor.submit(() -> {
-                new C1().print();
-            });
-            executor.submit(() -> {
-                new C2().print();
-            });
-            executor.submit(() -> {
-                new C3().print();
-            });
-            executor.submit(() -> {
-                new C4().print();
-            });
-        }
-    }
-
-    /**
-     * Run a task in a virtual thread, returning a String with any output printed
-     * to standard output.
-     */
-    private static String run(Runnable task) throws Exception {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        PrintStream original = System.out;
-        System.setOut(new PrintStream(baos, true));
-        try {
-            VThreadRunner.run(task::run);
-        } finally {
-            System.setOut(original);
-        }
-        String output = new String(baos.toByteArray());
-        System.out.println(output);
-        return output;
-    }
-
-    /**
-     * Tests that s1 contains s2.
-     */
-    private static void assertContains(String s1, String s2) {
-        assertTrue(s1.contains(s2), s2 + " not found!!!");
-    }
-
-    /**
-     * Tests that s1 does not contain s2.
-     */
-    private static void assertDoesNotContain(String s1, String s2) {
-        assertFalse(s1.contains(s2), s2 + " found!!");
-    }
-}
diff a/test/jdk/java/lang/Thread/virtual/TrackAllThreads.java b/test/jdk/java/lang/Thread/virtual/TrackAllThreads.java
--- /dev/null
+++ b/test/jdk/java/lang/Thread/virtual/TrackAllThreads.java
@@ -0,0 +1,135 @@
+/*
+ * 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
+ * @library /test/lib
+ * @run junit/othervm TrackAllThreads
+ */
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedTransferQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.LockSupport;
+
+import jdk.test.lib.util.ForceGC;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class TrackAllThreads {
+
+    /**
+     * Test that a virtual created directly with the Thread API, then parks, is not GC'ed.
+     */
+    @Test
+    void testRootContainer() {
+        Thread thread = Thread.ofVirtual().start(LockSupport::park);
+        var ref = new WeakReference<>(thread);
+        thread = null;
+        ForceGC.waitFor(() -> ref.refersTo(null), 2000);
+        thread = ref.get();
+        if (thread == null) {
+            fail("Thread has been GC'ed");
+        } else {
+            LockSupport.unpark(thread);
+        }
+    }
+
+    /**
+     * Test that a ThreadPoolExecutor using a virtual thread factory can be GC'ed after
+     * the last virtual thread terminates.
+     */
+    @Test
+    void testSharedContainer1() {
+        var queue = new LinkedTransferQueue<Runnable>();
+        ThreadFactory factory = Thread.ofVirtual().factory();
+        var executor = new ThreadPoolExecutor(0, 1, 1, TimeUnit.SECONDS, queue, factory);
+        executor.submit(() -> { });
+        var ref = new WeakReference<>(executor);
+        executor = null;
+        boolean cleared = ForceGC.waitFor(() -> ref.refersTo(null), Long.MAX_VALUE);
+        assertTrue(cleared);
+    }
+
+    /**
+     * Test that a ThreadPoolExecutor using a virtual thread factory is not GC'ed when
+     * a task (running in a virtual thread) has parked.
+     */
+    @Disabled
+    @Test
+    void testSharedContainer2() throws Exception {
+        var queue = new LinkedTransferQueue<Runnable>();
+        ThreadFactory factory = Thread.ofVirtual().factory();
+        var executor = new ThreadPoolExecutor(0, 1, 1, TimeUnit.SECONDS, queue, factory);
+        executor.execute(LockSupport::park);
+        var ref = new WeakReference<>(executor);
+        executor = null;
+        ForceGC.waitFor(() -> ref.refersTo(null), 2000);
+        executor = ref.get();
+        if (executor == null) {
+            fail("Executor has been GC'ed");
+        } else {
+            executor.shutdownNow();
+            executor.close();
+        }
+    }
+
+    /**
+     * Test that a ThreadPerTaskExecutor using a virtual thread factory can be GC'ed after
+     * the last virtual thread terminates.
+     */
+    @Test
+    void testThreadPerTaskExecutor1() {
+        var executor = Executors.newVirtualThreadPerTaskExecutor();
+        executor.submit(() -> { });
+        var ref = new WeakReference<>(executor);
+        executor = null;
+        boolean cleared = ForceGC.waitFor(() -> ref.refersTo(null), Long.MAX_VALUE);
+        assertTrue(cleared);
+    }
+
+    /**
+     * Test that a ThreadPerTaskExecutor using a virtual thread factory is not GC'ed when
+     * a task (running in a virtual thread) has parked.
+     */
+    @Disabled
+    @Test
+    void testThreadPerTaskExecutor2() throws Exception {
+        var executor = Executors.newVirtualThreadPerTaskExecutor();
+        executor.execute(LockSupport::park);
+        var ref = new WeakReference<>(executor);
+        executor = null;
+        ForceGC.waitFor(() -> ref.refersTo(null), 2000);
+        executor = ref.get();
+        if (executor == null) {
+            fail("Executor has been GC'ed");
+        } else {
+            executor.shutdownNow();
+            executor.close();
+        }
+    }
+}
diff a/test/jdk/java/lang/Thread/virtual/libSynchronizedNative.c b/test/jdk/java/lang/Thread/virtual/libSynchronizedNative.c
--- /dev/null
+++ b/test/jdk/java/lang/Thread/virtual/libSynchronizedNative.c
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#include "jni.h"
+
+JNIEXPORT void JNICALL
+Java_SynchronizedNative_runWithSynchronizedNative(JNIEnv *env, jobject obj, jobject task) {
+    jclass clazz = (*env)->GetObjectClass(env, obj);
+    jmethodID mid = (*env)->GetMethodID(env, clazz, "run", "(Ljava/lang/Runnable;)V");
+    if (mid != NULL) {
+        (*env)->CallVoidMethod(env, obj, mid, task);
+    }
+}
+
+JNIEXPORT void JNICALL
+Java_SynchronizedNative_runWithMonitorEnteredInNative(JNIEnv *env, jobject obj, jobject lock, jobject task) {
+    jclass clazz = (*env)->GetObjectClass(env, obj);
+    jmethodID mid = (*env)->GetMethodID(env, clazz, "run", "(Ljava/lang/Runnable;)V");
+    if (mid != NULL && (*env)->MonitorEnter(env, lock) == 0) {
+        (*env)->CallVoidMethod(env, obj, mid, task);
+        (*env)->MonitorExit(env, lock);  // can be called with pending exception
+    }
+}
diff a/test/jdk/java/lang/Thread/virtual/libTracePinnedThreads.c b/test/jdk/java/lang/Thread/virtual/libTracePinnedThreads.c
--- a/test/jdk/java/lang/Thread/virtual/libTracePinnedThreads.c
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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 "jni.h"
-
-JNIEXPORT void JNICALL Java_TracePinnedThreads_invokePark(JNIEnv *env, jclass clazz) {
-    jmethodID mid = (*env)->GetStaticMethodID(env, clazz, "park", "()V");
-    if (mid != NULL) {
-        (*env)->CallStaticVoidMethod(env, clazz, mid);
-    }
-}
diff a/test/jdk/java/lang/Thread/virtual/stress/GetStackTraceALotWhenBlocked.java b/test/jdk/java/lang/Thread/virtual/stress/GetStackTraceALotWhenBlocked.java
--- /dev/null
+++ b/test/jdk/java/lang/Thread/virtual/stress/GetStackTraceALotWhenBlocked.java
@@ -0,0 +1,82 @@
+/*
+ * 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
+ * @summary Stress test Thread.getStackTrace on virtual threads that are blocking or
+ *     blocked on monitorenter
+ * @run main GetStackTraceALotWhenBlocked 500000
+ */
+
+/*
+ * @test
+ * @requires vm.debug == true & vm.continuations
+ * @run main/timeout=300 GetStackTraceALotWhenBlocked 50000
+ */
+
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class GetStackTraceALotWhenBlocked {
+
+    public static void main(String[] args) throws Exception {
+        int iterations = args.length > 0 ? Integer.parseInt(args[0]) : 100_000;
+
+        var done = new AtomicBoolean();
+        var lock = new Object();
+
+        Runnable task = () -> {
+            long count = 0L;
+            while (!done.get()) {
+                synchronized (lock) {
+                    pause();
+                }
+                count++;
+            }
+            System.out.format("%s => %d%n", Thread.currentThread(), count);
+        };
+
+        var thread1 = Thread.ofVirtual().start(task);
+        var thread2 = Thread.ofVirtual().start(task);
+        try {
+            for (int i = 0; i < iterations; i++) {
+                thread1.getStackTrace();
+                pause();
+                thread2.getStackTrace();
+                pause();
+            }
+        } finally {
+            done.set(true);
+            thread1.join();
+            thread2.join();
+        }
+    }
+
+    private static void pause() {
+        if (ThreadLocalRandom.current().nextBoolean()) {
+            Thread.onSpinWait();
+        } else {
+            Thread.yield();
+        }
+    }
+}
diff a/test/jdk/java/lang/Thread/virtual/stress/GetStackTraceALotWhenPinned.java b/test/jdk/java/lang/Thread/virtual/stress/GetStackTraceALotWhenPinned.java
--- a/test/jdk/java/lang/Thread/virtual/stress/GetStackTraceALotWhenPinned.java
+++ b/test/jdk/java/lang/Thread/virtual/stress/GetStackTraceALotWhenPinned.java
@@ -26,19 +26,19 @@
  * @bug 8322818
  * @summary Stress test Thread.getStackTrace on a virtual thread that is pinned
  * @requires vm.debug != true
  * @modules java.base/java.lang:+open
  * @library /test/lib
- * @run main/othervm GetStackTraceALotWhenPinned 500000
+ * @run main/othervm --enable-native-access=ALL-UNNAMED GetStackTraceALotWhenPinned 500000
  */
 
 /*
  * @test
  * @requires vm.debug == true
  * @modules java.base/java.lang:+open
  * @library /test/lib
- * @run main/othervm/timeout=300 GetStackTraceALotWhenPinned 200000
+ * @run main/othervm/timeout=300 --enable-native-access=ALL-UNNAMED GetStackTraceALotWhenPinned 200000
  */
 
 import java.time.Instant;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.LockSupport;
diff a/test/jdk/java/lang/Thread/virtual/stress/LotsOfContendedMonitorEnter.java b/test/jdk/java/lang/Thread/virtual/stress/LotsOfContendedMonitorEnter.java
--- /dev/null
+++ b/test/jdk/java/lang/Thread/virtual/stress/LotsOfContendedMonitorEnter.java
@@ -0,0 +1,118 @@
+/*
+ * 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 id=default
+ * @summary Test virtual threads entering a lot of monitors with contention
+ * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+ * @library /test/lib
+ * @run main/othervm LotsOfContendedMonitorEnter
+ */
+
+/*
+ * @test id=LM_LEGACY
+ * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+ * @library /test/lib
+ * @run main/othervm -XX:LockingMode=1 LotsOfContendedMonitorEnter
+ */
+
+/*
+ * @test id=LM_LIGHTWEIGHT
+ * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+ * @library /test/lib
+ * @run main/othervm -XX:LockingMode=2 LotsOfContendedMonitorEnter
+ */
+
+import java.util.concurrent.CountDownLatch;
+import jdk.test.lib.thread.VThreadRunner;
+
+public class LotsOfContendedMonitorEnter {
+
+    public static void main(String[] args) throws Exception {
+        int depth;
+        if (args.length > 0) {
+            depth = Integer.parseInt(args[0]);
+        } else {
+            depth = 1024;
+        }
+        VThreadRunner.run(() -> testContendedEnter(depth));
+    }
+
+    /**
+     * Enter the monitor for a new object, racing with another virtual thread that
+     * attempts to enter around the same time, then repeat to the given depth.
+     */
+    private static void testContendedEnter(int depthRemaining) throws Exception {
+        if (depthRemaining > 0) {
+            var lock = new Object();
+
+            // start thread to enter monitor for brief period, then enters again when signalled
+            var started = new CountDownLatch(1);
+            var signal = new CountDownLatch(1);
+            var thread = Thread.ofVirtual().start(() -> {
+                started.countDown();
+
+                // enter, may be contended
+                synchronized (lock) {
+                    Thread.onSpinWait();
+                }
+
+                // wait to be signalled
+                try {
+                    signal.await();
+                } catch (InterruptedException e) { }
+
+                // enter again, this will block until the main thread releases
+                synchronized (lock) {
+                    // do nothing
+                }
+            });
+            try {
+                // wait for thread to start
+                started.await();
+
+                // enter, may be contended
+                synchronized (lock) {
+                    // signal thread to enter monitor again, it should block
+                    signal.countDown();
+                    await(thread, Thread.State.BLOCKED);
+                    testContendedEnter(depthRemaining - 1);
+                }
+            } finally {
+                thread.join();
+            }
+        }
+    }
+
+    /**
+     * Waits for the given thread to reach a given state.
+     */
+    private static void await(Thread thread, Thread.State expectedState) {
+        Thread.State state = thread.getState();
+        while (state != expectedState) {
+            assert state != Thread.State.TERMINATED : "Thread has terminated";
+            Thread.yield();
+            state = thread.getState();
+        }
+    }
+}
diff a/test/jdk/java/lang/Thread/virtual/stress/LotsOfUncontendedMonitorEnter.java b/test/jdk/java/lang/Thread/virtual/stress/LotsOfUncontendedMonitorEnter.java
--- /dev/null
+++ b/test/jdk/java/lang/Thread/virtual/stress/LotsOfUncontendedMonitorEnter.java
@@ -0,0 +1,93 @@
+/*
+ * 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 id=default
+ * @summary Test virtual thread entering (and reentering) a lot of monitors with no contention
+ * @library /test/lib
+ * @run main/othervm LotsOfUncontendedMonitorEnter
+ */
+
+/*
+ * @test id=LM_LEGACY
+ * @library /test/lib
+ * @run main/othervm -XX:LockingMode=1 LotsOfUncontendedMonitorEnter
+ */
+
+/*
+ * @test id=LM_LIGHTWEIGHT
+ * @library /test/lib
+ * @run main/othervm -XX:LockingMode=2 LotsOfUncontendedMonitorEnter
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+import jdk.test.lib.thread.VThreadRunner;
+
+public class LotsOfUncontendedMonitorEnter {
+
+    public static void main(String[] args) throws Exception {
+        int depth;
+        if (args.length > 0) {
+            depth = Integer.parseInt(args[0]);
+        } else {
+            depth = 24; // 33554430 enters
+        }
+        VThreadRunner.run(() -> {
+            testEnter(List.of(), depth);
+        });
+    }
+
+    /**
+     * Enter the monitor for a new object, reenter a monitor that is already held, and
+     * repeat to the given depth.
+     */
+    private static void testEnter(List<Object> ownedMonitors, int depthRemaining) {
+        if (depthRemaining > 0) {
+            var lock = new Object();
+            synchronized (lock) {
+                // new list of owned monitors
+                var monitors = concat(ownedMonitors, lock);
+                testEnter(monitors, depthRemaining - 1);
+
+                // reenter a monitor that is already owned
+                int index = ThreadLocalRandom.current().nextInt(monitors.size());
+                var otherLock = monitors.get(index);
+
+                synchronized (otherLock) {
+                    testEnter(monitors, depthRemaining - 1);
+                }
+            }
+        }
+    }
+
+    /**
+     * Adds an element to a list, returning a new list.
+     */
+    private static <T> List<T> concat(List<T> list, T object) {
+        var newList = new ArrayList<>(list);
+        newList.add(object);
+        return newList;
+    }
+}
diff a/test/jdk/java/nio/channels/vthread/SelectorOps.java b/test/jdk/java/nio/channels/vthread/SelectorOps.java
--- a/test/jdk/java/nio/channels/vthread/SelectorOps.java
+++ b/test/jdk/java/nio/channels/vthread/SelectorOps.java
@@ -26,10 +26,18 @@
  * @summary Test virtual threads doing selection operations
  * @library /test/lib
  * @run junit/othervm --enable-native-access=ALL-UNNAMED SelectorOps
  */
 
+/*
+ * @test id=poller-modes
+ * @requires (os.family == "linux") | (os.family == "mac")
+ * @library /test/lib
+ * @run junit/othervm -Djdk.pollerMode=1 --enable-native-access=ALL-UNNAMED SelectorOps
+ * @run junit/othervm -Djdk.pollerMode=2 --enable-native-access=ALL-UNNAMED SelectorOps
+ */
+
 /*
  * @test id=no-vmcontinuations
  * @requires vm.continuations
  * @library /test/lib
  * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations
diff a/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java b/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java
--- a/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java
+++ b/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java
@@ -210,27 +210,24 @@
     @MethodSource("factories")
     void testForkAfterJoin(ThreadFactory factory) throws Exception {
         try (var scope = new StructuredTaskScope<String>(null, factory)) {
             // round 1
             var subtask1 = scope.fork(() -> "foo");
-            assertThrows(IllegalStateException.class, subtask1::get);
             scope.join();
             assertEquals("foo", subtask1.get());
 
             // round 2
             var subtask2 = scope.fork(() -> "bar");
             assertEquals("foo", subtask1.get());
-            assertThrows(IllegalStateException.class, subtask2::get);
             scope.join();
             assertEquals("foo", subtask1.get());
             assertEquals("bar", subtask2.get());
 
             // round 3
             var subtask3 = scope.fork(() -> "baz");
             assertEquals("foo", subtask1.get());
             assertEquals("bar", subtask2.get());
-            assertThrows(IllegalStateException.class, subtask3::get);
             scope.join();
             assertEquals("foo", subtask1.get());
             assertEquals("bar", subtask2.get());
             assertEquals("baz", subtask3.get());
         }
@@ -256,12 +253,10 @@
             // allow subtask1 to finish
             latch.countDown();
 
             // continue to fork
             var subtask2 = scope.fork(() -> "bar");
-            assertThrows(IllegalStateException.class, subtask1::get);
-            assertThrows(IllegalStateException.class, subtask2::get);
             scope.join();
             assertEquals("foo", subtask1.get());
             assertEquals("bar", subtask2.get());
         }
     }
@@ -1272,16 +1267,11 @@
     @MethodSource("factories")
     void testSubtaskWhenSuccess(ThreadFactory factory) throws Exception {
         try (var scope = new StructuredTaskScope<String>(null, factory)) {
             Callable<String> task = () -> "foo";
             Subtask<String> subtask = scope.fork(task);
-
-            // before join, owner thread
             assertEquals(task, subtask.task());
-            assertThrows(IllegalStateException.class, subtask::get);
-            assertThrows(IllegalStateException.class, subtask::exception);
-
             scope.join();
 
             // after join
             assertEquals(task, subtask.task());
             assertEquals(Subtask.State.SUCCESS, subtask.state());
@@ -1297,16 +1287,11 @@
     @MethodSource("factories")
     void testSubtaskWhenFailed(ThreadFactory factory) throws Exception {
         try (var scope = new StructuredTaskScope<String>(null, factory)) {
             Callable<String> task = () -> { throw new FooException(); };
             Subtask<String> subtask = scope.fork(task);
-
-            // before join, owner thread
             assertEquals(task, subtask.task());
-            assertThrows(IllegalStateException.class, subtask::get);
-            assertThrows(IllegalStateException.class, subtask::exception);
-
             scope.join();
 
             // after join
             assertEquals(task, subtask.task());
             assertEquals(Subtask.State.FAILED, subtask.state());
diff a/test/jdk/jdk/internal/vm/Continuation/Basic.java b/test/jdk/jdk/internal/vm/Continuation/Basic.java
--- a/test/jdk/jdk/internal/vm/Continuation/Basic.java
+++ b/test/jdk/jdk/internal/vm/Continuation/Basic.java
@@ -26,10 +26,11 @@
 /**
 * @test
 * @summary Basic tests for jdk.internal.vm.Continuation
 * @requires vm.continuations
 * @modules java.base/jdk.internal.vm
+* @library /test/lib
 * @build java.base/java.lang.StackWalkerHelper
 *
 * @run testng/othervm -XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames -Xint Basic
 *
 * @run testng/othervm -XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames -Xcomp -XX:TieredStopAtLevel=3 -XX:CompileOnly=jdk.internal.vm.Continuation::*,Basic::* Basic
@@ -42,20 +43,23 @@
 /**
 * @test
 * @requires vm.continuations
 * @requires vm.debug
 * @modules java.base/jdk.internal.vm
+* @library /test/lib
 * @build java.base/java.lang.StackWalkerHelper
 *
 * @run testng/othervm -XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames -XX:+VerifyStack -Xint Basic
 * @run testng/othervm -XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames -XX:+VerifyStack -Xcomp -XX:TieredStopAtLevel=3 -XX:CompileOnly=jdk.internal.vm.Continuation::*,Basic::* Basic
 * @run testng/othervm -XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames -XX:+VerifyStack -Xcomp -XX:-TieredCompilation -XX:CompileOnly=jdk.internal.vm.Continuation::*,Basic::* Basic
 */
 
 import jdk.internal.vm.Continuation;
 import jdk.internal.vm.ContinuationScope;
 
+import jdk.test.lib.Platform;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
@@ -274,10 +278,12 @@
         return "" + r;
     }
 
     @Test
     public void testPinnedMonitor() {
+        if (Platform.isX64() || Platform.isAArch64()) return;
+
         // Test pinning due to held monitor
         final AtomicReference<Continuation.Pinned> res = new AtomicReference<>();
 
         Continuation cont = new Continuation(FOO, ()-> {
             syncFoo(1);
diff a/test/jdk/jdk/internal/vm/Continuation/Fuzz.java b/test/jdk/jdk/internal/vm/Continuation/Fuzz.java
--- a/test/jdk/jdk/internal/vm/Continuation/Fuzz.java
+++ b/test/jdk/jdk/internal/vm/Continuation/Fuzz.java
@@ -468,11 +468,11 @@
         int computed = computeResult();
         assert result == computed : "result: " + result + " expected: " + computed;
     }
 
     boolean shouldPin() {
-        return traceHas(Op.PIN::contains);
+        return traceHas(Op.PIN::contains) && !(Platform.isX64() || Platform.isAArch64());
     }
 
     void verifyPin(boolean yieldResult) {
         if (yieldResult) yields++;
         if (!yieldResult && traceHas(op -> Op.INTERPRETED.contains(op) && Op.REFLECTED.contains(op))) return;
diff a/test/langtools/ProblemList-Virtual.txt b/test/langtools/ProblemList-Virtual.txt
--- /dev/null
+++ b/test/langtools/ProblemList-Virtual.txt
@@ -0,0 +1,28 @@
+####
+## Filed bugs
+
+####
+# Incompatible tests.
+
+####
+# Failed because jtreg wrapper can't catch exceptions in thread groups
+# or incorrently process negative tests
+tools/javac/NullQualifiedNew.java 0000000 generic-all
+tools/javac/NullQualifiedSuper2.java 0000000 generic-all
+tools/javac/generics/T5094318.java 0000000 generic-all
+tools/javac/api/T6395981.java 0000000 generic-all
+jdk/javadoc/tool/sourceOption/SourceOption.java 0000000 generic-all
+
+####
+# Tests failed because of unexpected --enable-preview
+tools/javac/launcher/GetResourceTest.java 0000000 generic-all
+tools/javac/launcher/SourceLauncherTest.java 0000000 generic-all
+
+# unstable when main thread is a virtual thread
+jdk/jshell/ToolMultilineSnippetHistoryTest.java 0000000 generic-all
+jdk/jshell/ToolShiftTabTest.java 0000000 generic-all
+
+# more unstable when main thread is a virtual thread
+jdk/jshell/ToolTabCommandTest.java 8287078 generic-all
+jdk/jshell/ToolTabSnippetTest.java 8287078 generic-all
+jdk/jshell/UndefinedClassTest.java 8287078 generic-all
diff a/test/langtools/ProblemList-vthread-Xcomp.txt b/test/langtools/ProblemList-vthread-Xcomp.txt
--- /dev/null
+++ b/test/langtools/ProblemList-vthread-Xcomp.txt
@@ -0,0 +1,34 @@
+#
+# 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.
+#
+
+#############################################################################
+#
+# List of quarantined tests for testing in Xcomp mode with wrapper.
+#
+#############################################################################
+
+####
+## Timeouts
+
+tools/javac/failover/CheckAttributedTree.java
+tools/sjavac/IdleShutdown.java
diff a/test/langtools/ProblemList.txt b/test/langtools/ProblemList.txt
--- a/test/langtools/ProblemList.txt
+++ b/test/langtools/ProblemList.txt
@@ -66,10 +66,13 @@
 tools/javac/annotations/typeAnnotations/referenceinfos/NestedTypes.java         8057687    generic-all    emit correct byte code an attributes for type annotations
 tools/javac/warnings/suppress/TypeAnnotations.java                              8057683    generic-all    improve ordering of errors with type annotations
 tools/javac/modules/SourceInSymlinkTest.java                                    8180263    windows-all    fails when run on a subst drive
 tools/javac/patterns/Exhaustiveness.java 					8326616    generic-all    intermittently timeout
 
+tools/javac/switchnull/SwitchNull.java                                          8322726    generic-all    fails with -XX:+VerifyOops
+tools/javac/lambda/T8031967.java                                                8323041    linux-aarch64  fails with -XX:TieredStopAtLevel=n (n=1,2,3)
+
 ###########################################################################
 #
 # javap
 
 tools/javap/output/RepeatingTypeAnnotations.java                                8057687    generic-all    emit correct byte code an attributes for type annotations
diff a/make/langtools/test/TEST.ROOT b/test/lib-test/ProblemList-Virtual.txt
--- a/make/langtools/test/TEST.ROOT
+++ b/test/lib-test/ProblemList-Virtual.txt
diff a/test/micro/org/openjdk/bench/java/lang/ScopedValues.java b/test/micro/org/openjdk/bench/java/lang/ScopedValues.java
--- a/test/micro/org/openjdk/bench/java/lang/ScopedValues.java
+++ b/test/micro/org/openjdk/bench/java/lang/ScopedValues.java
@@ -22,13 +22,12 @@
  */
 
 
 package org.openjdk.bench.java.lang;
 
-import java.util.concurrent.Callable;
+import java.lang.ScopedValue.CallableOp;
 import java.util.concurrent.TimeUnit;
-import java.util.function.Supplier;
 import org.openjdk.jmh.annotations.*;
 import org.openjdk.jmh.infra.Blackhole;
 
 import static org.openjdk.bench.java.lang.ScopedValuesData.*;
 
@@ -126,20 +125,20 @@
     // destroy it.
 
     @Benchmark
     @OutputTimeUnit(TimeUnit.NANOSECONDS)
     public int CreateBindThenGetThenRemove_ScopedValue() throws Exception {
-        return ScopedValue.where(sl1, THE_ANSWER).call(sl1::get);
+        return ScopedValue.callWhere(sl1, THE_ANSWER, sl1::get);
     }
 
 
     // Create a Carrier ahead of time: might be slightly faster
     private static final ScopedValue.Carrier HOLD_42 = ScopedValue.where(sl1, 42);
     @Benchmark
     @OutputTimeUnit(TimeUnit.NANOSECONDS)
     public int bindThenGetThenRemove_ScopedValue() throws Exception {
-        return HOLD_42.call(sl1::get);
+        return ScopedValue.callWhere(HOLD_42, sl1::get);
     }
 
     @Benchmark
     @OutputTimeUnit(TimeUnit.NANOSECONDS)
     public int bindThenGetThenRemove_ThreadLocal() throws Exception {
@@ -162,22 +161,13 @@
 
     // Test 4: The cost of binding, but not using any result
     @Benchmark
     @OutputTimeUnit(TimeUnit.NANOSECONDS)
     public Object bind_ScopedValue() throws Exception {
-        return HOLD_42.call(aCallable);
+        return ScopedValue.callWhere(HOLD_42, aCallableOp);
     }
-    private static final Callable<Class<?>> aCallable = () -> ScopedValues.class;
-
-    // Same, but make sure that Carrier.get(Supplier) is no slower
-    // than Carrier.call(Callable).
-    @Benchmark
-    @OutputTimeUnit(TimeUnit.NANOSECONDS)
-    public Object bindViaGet_ScopedValue() {
-        return HOLD_42.get(aSupplier);
-    }
-    private static final Supplier<Class<?>> aSupplier = () -> ScopedValues.class;
+    private static final CallableOp<Class<?>, RuntimeException> aCallableOp = () -> ScopedValues.class;
 
     @Benchmark
     @OutputTimeUnit(TimeUnit.NANOSECONDS)
     public Object bind_ThreadLocal() throws Exception {
         try {
diff a/test/micro/org/openjdk/bench/java/lang/ScopedValuesData.java b/test/micro/org/openjdk/bench/java/lang/ScopedValuesData.java
--- a/test/micro/org/openjdk/bench/java/lang/ScopedValuesData.java
+++ b/test/micro/org/openjdk/bench/java/lang/ScopedValuesData.java
@@ -57,13 +57,15 @@
     public static void run(Runnable action) {
         try {
             tl1.set(42); tl2.set(2); tl3.set(3); tl4.set(4); tl5.set(5); tl6.set(6);
             tl1.get();  // Create the ScopedValue cache as a side effect
             tl_atomicInt.set(new AtomicInteger());
-            VALUES.where(sl_atomicInt, new AtomicInteger())
-                  .where(sl_atomicRef, new AtomicReference<>())
-                  .run(action);
+
+            var carrier = VALUES.where(sl_atomicInt, new AtomicInteger())
+                  .where(sl_atomicRef, new AtomicReference<>());
+            ScopedValue.runWhere(carrier, action);
+
         } finally {
             tl1.remove(); tl2.remove(); tl3.remove(); tl4.remove(); tl5.remove(); tl6.remove();
             tl_atomicInt.remove();
         }
     }
diff a/test/micro/org/openjdk/bench/loom/Monitors.java b/test/micro/org/openjdk/bench/loom/Monitors.java
--- /dev/null
+++ b/test/micro/org/openjdk/bench/loom/Monitors.java
@@ -0,0 +1,207 @@
+/*
+ * 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.
+ */
+
+package org.openjdk.bench.loom;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+
+import jdk.internal.vm.Continuation;
+import jdk.internal.vm.ContinuationScope;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.TimeUnit;
+
+//@Fork(2)
+@Fork(1)
+@Warmup(iterations = 3, time = 5)
+@Measurement(iterations = 3, time = 5)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@State(Scope.Thread)
+@SuppressWarnings("preview")
+public class Monitors {
+    static final ContinuationScope SCOPE = new ContinuationScope() { };
+
+    static class RunSynchronized implements Runnable {
+        private final int stackDepth;
+        private final Object[] syncLockArray;
+
+        private RunSynchronized(int stackDepth, ReentrantLock[] lockArray) {
+            this.stackDepth = stackDepth;
+            this.syncLockArray = lockArray;
+        }
+
+        public void recursive(int depth) {
+            if (syncLockArray[depth] != null) {
+                synchronized (syncLockArray[depth]) {
+                    if (depth > 0) {
+                        recursive(depth - 1);
+                    } else {
+                        Continuation.yield(SCOPE);
+                    }
+                }
+            } else {
+                if (depth > 0) {
+                    recursive(depth - 1);
+                } else {
+                    Continuation.yield(SCOPE);
+                }
+            }
+        }
+
+        @Override
+        public void run() {
+            while (true) {
+                recursive(stackDepth);
+            }
+        }
+    }
+
+    static class RunReentrantLock implements Runnable {
+        private final int stackDepth;
+        private final ReentrantLock[] lockArray;
+
+        private RunReentrantLock(int stackDepth, ReentrantLock[] lockArray) {
+            this.stackDepth = stackDepth;
+            this.lockArray = lockArray;
+        }
+
+        public void recursive(int depth) {
+            if (lockArray[depth] != null) {
+                lockArray[depth].lock();
+                if (depth > 0) {
+                    recursive(depth - 1);
+                } else {
+                    Continuation.yield(SCOPE);
+                }
+                lockArray[depth].unlock();
+            } else {
+                if (depth > 0) {
+                    recursive(depth - 1);
+                } else {
+                    Continuation.yield(SCOPE);
+                }
+            }
+        }
+
+        @Override
+        public void run() {
+            while (true) {
+                recursive(stackDepth);
+            }
+        }
+    }
+
+    static void inflateAllLocks(ReentrantLock[] lockArray) {
+        for (ReentrantLock lock : lockArray) {
+            if (lock != null) {
+                synchronized (lock) {
+                    try {
+                        lock.wait(1);
+                    } catch (Exception e) {}
+                }
+            }
+        }
+    }
+
+    static ReentrantLock[] createLockArray(int stackDepth, int monitorCount) {
+        ReentrantLock[] lockArray = new ReentrantLock[stackDepth + 1];
+
+        // Always a lock on the bottom most frame
+        lockArray[stackDepth] = new ReentrantLock();
+        // Limit extra monitors to no more than one per recursive frame.
+        int remainingMonitors = Math.min(Math.max(0, monitorCount - 1), stackDepth);
+
+        if (remainingMonitors > 0) {
+            // Calculate which other frames will use a lock.
+            int stride = Math.max(2, (stackDepth / monitorCount));
+            int frameIndex = (stackDepth - stride) % (stackDepth + 1);
+            while (remainingMonitors > 0) {
+                if (lockArray[frameIndex] == null) {
+                    lockArray[frameIndex] = new ReentrantLock();
+                    frameIndex = (frameIndex - stride) % (stackDepth + 1);
+                    remainingMonitors--;
+                } else {
+                    frameIndex = (frameIndex - 1) % (stackDepth + 1);
+                }
+            }
+        }
+        System.out.println("Created lock array. synchronized/ReentrantLock will be used at following indexes:");
+        for (int i = stackDepth; i >= 0; i--) {
+            System.out.println("[" + i + "] : " + (lockArray[i] != null ? "synchronized/ReentrantLock" : "-"));
+        }
+        return lockArray;
+    }
+
+    static Continuation createContinuation(int stackDepth, ReentrantLock[] lockArray, boolean useSyncronized) {
+        Runnable task = useSyncronized ? new RunSynchronized(stackDepth, lockArray)
+                                       : new RunReentrantLock(stackDepth, lockArray);
+        //inflateAllLocks(lockArray);
+        return new Continuation(SCOPE, task);
+    }
+
+    @State(Scope.Thread)
+    public static class MyState {
+        @Param({"0", "5", "10", "20"})
+        public int stackDepth;
+
+        @Param({"1", "2", "3"})
+        public int monitorCount;
+
+        public Continuation syncCont;
+        public Continuation reentCont;
+
+        @Setup(Level.Trial)
+        public void doSetup() {
+            ReentrantLock[] lockArray = createLockArray(stackDepth, monitorCount);
+            // We don't care which object we synchronize on so just pass
+            // the same ReentrantLock array to the synchronized task.
+            syncCont = createContinuation(stackDepth, lockArray, true /* useSyncronized */);
+            reentCont = createContinuation(stackDepth, lockArray, false /* useSyncronized */);
+            System.out.println("Created continuation used for testing with stackDepth: " + stackDepth + " and monitorCount: " + monitorCount);
+        }
+    }
+
+    @Benchmark
+    public void syncLock(MyState state) {
+        state.syncCont.run();
+    }
+
+    @Benchmark
+    public void reentrantLock(MyState state) {
+        state.reentCont.run();
+    }
+}
diff a/test/micro/org/openjdk/bench/loom/Monitors2.java b/test/micro/org/openjdk/bench/loom/Monitors2.java
--- /dev/null
+++ b/test/micro/org/openjdk/bench/loom/Monitors2.java
@@ -0,0 +1,320 @@
+/*
+ * 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.
+ */
+
+package org.openjdk.bench.loom;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.*;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+//@Fork(2)
+@Fork(1)
+@Warmup(iterations = 3, time = 5)
+@Measurement(iterations = 3, time = 5)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@State(Scope.Thread)
+@SuppressWarnings("preview")
+public class Monitors2 {
+    static Object[] globalLockArray;
+    static ReentrantLock[] globalReentrantLockArray;
+    static AtomicInteger workerCount = new AtomicInteger(0);
+    static AtomicInteger dummyCounter = new AtomicInteger(0);
+    static int workIterations;
+
+    static int VT_COUNT;
+    static int CARRIER_COUNT = 8;
+    static ExecutorService scheduler = Executors.newFixedThreadPool(CARRIER_COUNT);
+
+    //@Param({"8", "32", "256", "4096", "131072"})
+    @Param({"8", "32", "256", "4096"})
+    static int VT_MULTIPLIER;
+
+    @Param({"1", "5", "12", "32"})
+    static int MONITORS_CNT;
+
+    @Param({"0", "50000", "100000", "200000"})
+    static int WORKLOAD;
+
+    @Param({"10"})
+    static int STACK_DEPTH;
+
+    void recursive4_1(int depth, int lockNumber) {
+        if (depth > 0) {
+            recursive4_1(depth - 1, lockNumber);
+        } else {
+            if (Math.random() < 0.5) {
+                Thread.yield();
+            }
+            recursive4_2(lockNumber);
+        }
+    }
+
+    void recursive4_2(int lockNumber) {
+        if (lockNumber + 2 <= MONITORS_CNT - 1) {
+            lockNumber += 2;
+            synchronized(globalLockArray[lockNumber]) {
+                Thread.yield();
+                recursive4_2(lockNumber);
+            }
+        }
+    }
+
+    final Runnable FOO4 = () -> {
+        int iterations = 10;
+        while (iterations-- > 0) {
+            int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITORS_CNT - 1);
+            synchronized(globalLockArray[lockNumber]) {
+                recursive4_1(lockNumber, lockNumber);
+            }
+        }
+        workerCount.getAndIncrement();
+    };
+
+    /**
+     *  Test contention on monitorenter with extra monitors on stack shared by all threads.
+     */
+    //@Benchmark
+    public void testContentionMultipleMonitors(MyState state) throws Exception {
+        workerCount.getAndSet(0);
+
+        Thread batch[] = new Thread[VT_COUNT];
+        for (int i = 0; i < VT_COUNT; i++) {
+            batch[i] = virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(FOO4);
+        }
+
+        for (int i = 0; i < VT_COUNT; i++) {
+            batch[i].join();
+        }
+
+        if (workerCount.get() != VT_COUNT) {
+            throw new RuntimeException("testContentionMultipleMonitors failed. Expected " + VT_COUNT + "but found " + workerCount.get());
+        }
+    }
+
+    static void recursive5_1(int depth, int lockNumber, Object[] myLockArray) {
+        if (depth > 0) {
+            recursive5_1(depth - 1, lockNumber, myLockArray);
+        } else {
+            if (Math.random() < 0.5) {
+                Thread.yield();
+            }
+            recursive5_2(lockNumber, myLockArray);
+        }
+    }
+
+    static void recursive5_2(int lockNumber, Object[] myLockArray) {
+        if (lockNumber + 2 <= MONITORS_CNT - 1) {
+            lockNumber += 2;
+            synchronized (myLockArray[lockNumber]) {
+                if (Math.random() < 0.5) {
+                    Thread.yield();
+                }
+                synchronized (globalLockArray[lockNumber]) {
+                    Thread.yield();
+                    recursive5_2(lockNumber, myLockArray);
+                }
+            }
+        }
+    }
+
+    static final Runnable FOO5 = () -> {
+        Object[] myLockArray = new Object[MONITORS_CNT];
+        for (int i = 0; i < MONITORS_CNT; i++) {
+            myLockArray[i] = new Object();
+        }
+
+        int iterations = 10;
+        while (iterations-- > 0) {
+            int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITORS_CNT - 1);
+            synchronized (myLockArray[lockNumber]) {
+                synchronized (globalLockArray[lockNumber]) {
+                    recursive5_1(lockNumber, lockNumber, myLockArray);
+                }
+            }
+        }
+        workerCount.getAndIncrement();
+    };
+
+    /**
+     *  Test contention on monitorenter with extra monitors on stack both local only and shared by all threads.
+     */
+    //@Benchmark
+    public void testContentionMultipleMonitors2(MyState state) throws Exception {
+        workerCount.getAndSet(0);
+
+        // Create batch of VT threads.
+        Thread batch[] = new Thread[VT_COUNT];
+        for (int i = 0; i < VT_COUNT; i++) {
+            //Thread.ofVirtual().name("FirstBatchVT-" + i).start(FOO);
+            batch[i] = virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(FOO5);
+        }
+
+        for (int i = 0; i < VT_COUNT; i++) {
+            batch[i].join();
+        }
+
+        if (workerCount.get() != VT_COUNT) {
+            throw new RuntimeException("testContentionMultipleMonitors2 failed. Expected " + VT_COUNT + "but found " + workerCount.get());
+        }
+    }
+
+    static void recursiveSync(int depth) {
+        if (depth > 0) {
+            recursiveSync(depth - 1);
+        } else {
+            int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITORS_CNT);
+            synchronized(globalLockArray[lockNumber]) {
+                //Thread.yield();
+                for (int i = 0; i < WORKLOAD; i++) {
+                    dummyCounter.getAndIncrement();
+                }
+                workerCount.getAndIncrement();
+            }
+        }
+    }
+
+    static final Runnable SYNC = () -> {
+        recursiveSync(STACK_DEPTH);
+    };
+
+    static void recursiveReentrant(int depth) {
+        if (depth > 0) {
+            recursiveReentrant(depth - 1);
+        } else {
+            int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITORS_CNT);
+            globalReentrantLockArray[lockNumber].lock();
+            //Thread.yield();
+            for (int i = 0; i < WORKLOAD; i++) {
+                dummyCounter.getAndIncrement();
+            }
+            workerCount.getAndIncrement();
+            globalReentrantLockArray[lockNumber].unlock();
+        }
+    }
+
+    static final Runnable REENTRANTLOCK = () -> {
+        recursiveReentrant(STACK_DEPTH);
+    };
+
+    public void runBenchmark(Runnable r) throws Exception {
+        workerCount.getAndSet(0);
+
+        // Create batch of VT threads.
+        Thread batch[] = new Thread[VT_COUNT];
+        for (int i = 0; i < VT_COUNT; i++) {
+            batch[i] = virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(r);
+        }
+
+        for (int i = 0; i < VT_COUNT; i++) {
+            batch[i].join();
+        }
+
+        if (workerCount.get() != VT_COUNT) {
+            throw new RuntimeException("testContentionMultipleMonitors2 failed. Expected " + VT_COUNT + "but found " + workerCount.get());
+        }
+    }
+
+    @Benchmark
+    public void testContentionReentrantLock(MyState state) throws Exception {
+        runBenchmark(REENTRANTLOCK);
+    }
+
+    @Benchmark
+    public void testContentionASync(MyState state) throws Exception {
+        runBenchmark(SYNC);
+    }
+
+    //@Benchmark
+    public void testExtraTime(MyState state) throws Exception {
+        dummyCounter.getAndSet(0);
+        // Takes ~120us
+        for (int i = 0; i < 50000; i++) {
+            dummyCounter.getAndIncrement();
+        }
+        if (dummyCounter.get() != 50000) {
+            throw new RuntimeException("testContentionMultipleMonitors2 failed. Expected " + 50000 + "but found " + dummyCounter.get());
+        }
+    }
+
+    @State(Scope.Thread)
+    public static class MyState {
+
+        @Setup(Level.Trial)
+        public void doSetup() {
+            // Setup up monitors/locks
+            globalLockArray = new Object[MONITORS_CNT];
+            globalReentrantLockArray = new ReentrantLock[MONITORS_CNT];
+            for (int i = 0; i < MONITORS_CNT; i++) {
+                globalLockArray[i] = new Object();
+                globalReentrantLockArray[i] = new ReentrantLock();
+            }
+            // Setup VirtualThread count
+            VT_COUNT = CARRIER_COUNT * VT_MULTIPLIER;
+
+            System.out.println("Running test with MONITORS_CNT = " + MONITORS_CNT + " VT_COUNT = " + VT_COUNT + " and WORKLOAD = " + WORKLOAD);
+            //System.out.println("Running test with VT_COUNT = " + VT_COUNT);
+        }
+
+        @TearDown(Level.Trial)
+        public void doTearDown() {
+            scheduler.shutdown();
+            System.out.println("Do TearDown");
+        }
+    }
+
+    private static Thread.Builder.OfVirtual virtualThreadBuilder(Executor scheduler) {
+        Thread.Builder.OfVirtual builder = Thread.ofVirtual();
+        try {
+            Class<?> clazz = Class.forName("java.lang.ThreadBuilders$VirtualThreadBuilder");
+            Constructor<?> ctor = clazz.getDeclaredConstructor(Executor.class);
+            ctor.setAccessible(true);
+            return (Thread.Builder.OfVirtual) ctor.newInstance(scheduler);
+        } catch (InvocationTargetException e) {
+            Throwable cause = e.getCause();
+            if (cause instanceof RuntimeException re) {
+                throw re;
+            }
+            throw new RuntimeException(e);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
