diff a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp
@@ -701,22 +701,87 @@
 }
 
 void ShenandoahBarrierStubC2::enter_if_gc_state(MacroAssembler& masm, const char test_state) {
   Assembler::InlineSkippedInstructionsCounter skip_counter(&masm);
 
-  int bit_to_check = ShenandoahThreadLocalData::gc_state_to_fast_bit(test_state);
-  Address gc_state_fast(rthread, in_bytes(ShenandoahThreadLocalData::gc_state_fast_offset()));
-  __ ldrb(rscratch1, gc_state_fast);
-  if (_test_and_branch_reachable) {
-    __ tbnz(rscratch1, bit_to_check, _test_and_branch_reachable_entry);
-  } else {
-    __ tbz(rscratch1, bit_to_check, *continuation());
+  if (ShenandoahGCStateCheckRemove) {
+    // Unrealistic: remove all barrier fastpath checks.
+  } else if (ShenandoahGCStateCheckHotpatch) {
+    // Emit the unconditional branch in the first version of the method.
+    // Let the rest of runtime figure out how to manage it.
+    __ relocate(barrier_Relocation::spec());
     __ b(*entry());
+
+#ifdef ASSERT
+    Address gc_state_fast(rthread, in_bytes(ShenandoahThreadLocalData::gc_state_fast_offset()));
+    __ ldrb(rscratch1, gc_state_fast);
+    __ cbz(rscratch1, *continuation());
+    __ hlt(0); // Correctness bug: barrier is NOP-ed, but heap is NOT IDLE
+#endif
+    __ bind(*continuation());
+  } else {
+    int bit_to_check = ShenandoahThreadLocalData::gc_state_to_fast_bit(test_state);
+    Address gc_state_fast(rthread, in_bytes(ShenandoahThreadLocalData::gc_state_fast_offset()));
+    __ ldrb(rscratch1, gc_state_fast);
+    if (_test_and_branch_reachable) {
+      __ tbnz(rscratch1, bit_to_check, _test_and_branch_reachable_entry);
+    } else {
+      __ tbz(rscratch1, bit_to_check, *continuation());
+      __ b(*entry());
+    }
+
+    // This is were the slowpath stub will return to or the code above will
+    // jump to if the checks are false
+    __ bind(*continuation());
+  }
+}
+
+address ShenandoahBarrierSetAssembler::parse_stub_address(address pc) {
+  NativeInstruction* ni = nativeInstruction_at(pc);
+  assert(ni->is_jump(), "Initial code version: GC barrier fastpath must be a jump");
+  NativeJump* jmp = nativeJump_at(pc);
+  return jmp->jump_destination();
+}
+
+void insert_nop(address pc) {
+  *(pc + 0) = 0x1F;
+  *(pc + 1) = 0x20;
+  *(pc + 2) = 0x03;
+  *(pc + 3) = 0xD5;
+  ICache::invalidate_range(pc, 4);
+}
+
+bool is_nop(address pc) {
+  if (*(pc + 0) != 0x1F) return false;
+  if (*(pc + 1) != 0x20) return false;
+  if (*(pc + 2) != 0x03) return false;
+  if (*(pc + 3) != 0xD5) return false;
+  return true;
+}
+
+void check_at(bool cond, address pc, const char* msg) {
+  assert(cond, "%s: at PC " PTR_FORMAT ": %02x%02x%02x%02x%02x",
+         msg, p2i(pc), *(pc + 0), *(pc + 1), *(pc + 2), *(pc + 3), *(pc + 4));
+}
+
+void ShenandoahBarrierSetAssembler::patch_branch_to_nop(address pc) {
+  NativeInstruction* ni = nativeInstruction_at(pc);
+  if (ni->is_jump()) {
+    insert_nop(pc);
+  } else {
+    check_at(is_nop(pc), pc, "Should already be nop");
+  }
+}
+
+void ShenandoahBarrierSetAssembler::patch_nop_to_branch(address pc, address stub_addr) {
+  NativeInstruction* ni = nativeInstruction_at(pc);
+  if (is_nop(pc)) {
+    NativeJump::insert(pc, stub_addr);
+  } else {
+    check_at(ni->is_jump(), pc, "Should already be jump");
+    check_at(nativeJump_at(pc)->jump_destination() == stub_addr, pc, "Jump should be to the same address");
   }
-  // This is were the slowpath stub will return to or the code above will
-  // jump to if the checks are false
-  __ bind(*continuation());
 }
 
 bool needs_acquiring_load_exclusive(const MachNode *n) {
   assert(n->is_CAS(true), "expecting a compare and swap");
   if (n->is_CAS(false)) {
@@ -875,11 +940,11 @@
     stub->enter_if_gc_state(*masm, check);
   }
 }
 
 void ShenandoahBarrierSetAssembler::card_barrier_c2(const MachNode* node, MacroAssembler* masm, Address address) {
-  if (!ShenandoahBarrierStubC2::needs_card_barrier(node)) {
+  if (ShenandoahSkipBarriers || (node->barrier_data() & ShenandoahBitCardMark) == 0) {
     return;
   }
 
   assert(CardTable::dirty_card_val() == 0, "must be");
   Assembler::InlineSkippedInstructionsCounter skip_counter(masm);
@@ -942,11 +1007,11 @@
 }
 
 void ShenandoahBarrierStubC2::emit_code(MacroAssembler& masm) {
   // If we reach here with _skip_trampoline set it means that earlier we
   // emitted a trampoline to this stub and now we need to emit the actual stub.
-  if (_skip_trampoline) {
+  if (ShenandoahGCStateCheckHotpatch || _skip_trampoline) {
     emit_code_actual(masm);
   } else {
     _skip_trampoline = true;
 
     // The fastpath executes two branch instructions to reach this stub, let's
@@ -1074,11 +1139,12 @@
   if (!_needs_keep_alive_barrier) {
     return ;
   }
 
   // If another barrier is enabled as well, do a runtime check for a specific barrier.
-  if (_needs_load_ref_barrier) {
+  // Hotpatched GC checks only care about idle/non-idle state, so needs a check anyhow.
+  if (_needs_load_ref_barrier || ShenandoahGCStateCheckHotpatch) {
     Address gcs_addr(rthread, in_bytes(ShenandoahThreadLocalData::gc_state_offset()));
     __ ldrb(tmp1, gcs_addr);
     __ tbz(tmp1, ShenandoahHeap::MARKING_BITPOS, L_done);
   }
 
@@ -1124,11 +1190,12 @@
     return ;
   }
 
   if ((_node->barrier_data() & ShenandoahBitStrong) != 0) {
     // If another barrier is enabled as well, do a runtime check for a specific barrier.
-    if (_needs_keep_alive_barrier) {
+    // Hotpatched GC checks only care about idle/non-idle state, so needs a check anyhow.
+    if (_needs_keep_alive_barrier || ShenandoahGCStateCheckHotpatch) {
       char state_to_check = ShenandoahHeap::HAS_FORWARDED | (_needs_load_ref_weak_barrier ? ShenandoahHeap::WEAK_ROOTS : 0);
       int bit_to_check = ShenandoahThreadLocalData::gc_state_to_fast_bit(state_to_check);
       Address gc_state_fast(rthread, in_bytes(ShenandoahThreadLocalData::gc_state_fast_offset()));
       __ ldrb(tmp, gc_state_fast);
       __ tbz(tmp, bit_to_check, L_done);
diff a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.hpp
--- a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.hpp
+++ b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.hpp
@@ -145,10 +145,15 @@
   void generate_c1_pre_barrier_runtime_stub(StubAssembler* sasm);
   void generate_c1_load_reference_barrier_runtime_stub(StubAssembler* sasm, DecoratorSet decorators);
 #endif
 
 #ifdef COMPILER2
+  // Barrier hotpatching
+  static address parse_stub_address(address pc);
+  static void patch_branch_to_nop(address pc);
+  static void patch_nop_to_branch(address pc, address stub_addr);
+
   // Entry points from Matcher
   void load_c2(const MachNode* node, MacroAssembler* masm, Register dst, Address addr);
   void store_c2(const MachNode* node, MacroAssembler* masm, Address dst, bool dst_narrow, Register src, bool src_narrow);
   void compare_and_set_c2(const MachNode* node, MacroAssembler* masm, Register res, Register addr, Register oldval,
       Register newval, bool exchange, bool narrow, bool weak);
diff a/src/hotspot/cpu/aarch64/nativeInst_aarch64.cpp b/src/hotspot/cpu/aarch64/nativeInst_aarch64.cpp
--- a/src/hotspot/cpu/aarch64/nativeInst_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/nativeInst_aarch64.cpp
@@ -210,10 +210,27 @@
 //--------------------------------------------------------------------------------
 
 void NativeJump::verify() { ; }
 
 
+void NativeJump::insert(address code_pos, address entry) {
+  // Dispacement is relative to the jump instruction PC
+  intptr_t disp = (intptr_t)entry - ((intptr_t)code_pos);
+
+  // The jump immediate is 26 bits and it will at execution time be scaled by 4
+  int64_t imm26 = disp >> 2;
+
+  // The farthest that we can jump is +/- 128MiB
+  guarantee(Assembler::is_simm(imm26, 26), "maximum offset is 128MiB, you asking for %ld", imm26);
+
+  // Patch with opcode | offset
+  *((int32_t*)code_pos) = 0x14000000 | imm26;
+
+  // Tell hardware to invalidate icache line containing code_pos
+  ICache::invalidate_range(code_pos, instruction_size);
+}
+
 address NativeJump::jump_destination() const          {
   address dest = MacroAssembler::target_addr_for_insn(instruction_address());
 
   // We use jump to self as the unresolved address which the inline
   // cache code (and relocs) know about
diff a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp
--- a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp
+++ b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp
@@ -800,18 +800,24 @@
 
 void ShenandoahBarrierStubC2::gc_state_check_c2(MacroAssembler* masm,
                                                 Register gcstate,
                                                 const unsigned char test_state,
                                                 ShenandoahBarrierStubC2* slow_stub) {
-  int bit_to_check = ShenandoahThreadLocalData::gc_state_to_fast_bit(test_state);
-  Address gc_state_fast(xthread, in_bytes(ShenandoahThreadLocalData::gc_state_fast_offset()));
-  __ lbu(gcstate, gc_state_fast);
-  __ test_bit(gcstate, gcstate, bit_to_check);
-  __ bnez(gcstate, *slow_stub->entry());
-
-  // Fast path falls through here when the barrier is not needed.
-  __ bind(*slow_stub->continuation());
+  if (ShenandoahGCStateCheckRemove) {
+    // Unrealistic: remove all barrier fastpath checks.
+  } else if (ShenandoahGCStateCheckHotpatch) {
+    __ nop();
+  } else {
+    int bit_to_check = ShenandoahThreadLocalData::gc_state_to_fast_bit(test_state);
+    Address gc_state_fast(xthread, in_bytes(ShenandoahThreadLocalData::gc_state_fast_offset()));
+    __ lbu(gcstate, gc_state_fast);
+    __ test_bit(gcstate, gcstate, bit_to_check);
+    __ bnez(gcstate, *slow_stub->entry());
+
+    // Fast path falls through here when the barrier is not needed.
+    __ bind(*slow_stub->continuation());
+  }
 }
 
 void ShenandoahBarrierSetAssembler::compare_and_set_c2(const MachNode* node, MacroAssembler* masm, Register res, Register addr,
     Register oldval, Register newval, Register tmp, bool exchange, bool maybe_null, bool narrow, bool weak) {
   const Assembler::Aqrl acquire = needs_acquiring_load_reserved(node) ? Assembler::aq : Assembler::relaxed;
@@ -952,11 +958,11 @@
     ShenandoahBarrierStubC2::gc_state_check_c2(masm, t0, check, stub);
   }
 }
 
 void ShenandoahBarrierSetAssembler::card_barrier_c2(const MachNode* node, MacroAssembler* masm, Address address) {
-  if (!ShenandoahBarrierStubC2::needs_card_table_barrier(node)) {
+  if (ShenandoahSkipBarriers || (node->barrier_data() & ShenandoahBitCardMark) == 0) {
     return;
   }
 
   assert(CardTable::dirty_card_val() == 0, "must be");
   Assembler::InlineSkippedInstructionsCounter skip_counter(masm);
diff a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp
--- a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp
+++ b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp
@@ -32,10 +32,11 @@
 #include "gc/shenandoah/shenandoahHeap.inline.hpp"
 #include "gc/shenandoah/shenandoahHeapRegion.hpp"
 #include "gc/shenandoah/shenandoahRuntime.hpp"
 #include "gc/shenandoah/shenandoahThreadLocalData.hpp"
 #include "interpreter/interpreter.hpp"
+#include "nativeInst_x86.hpp"
 #include "runtime/javaThread.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "utilities/macros.hpp"
 #ifdef COMPILER1
 #include "c1/c1_LIRAssembler.hpp"
@@ -1017,10 +1018,59 @@
 
 #endif // COMPILER1
 
 #ifdef COMPILER2
 
+address ShenandoahBarrierSetAssembler::parse_stub_address(address pc) {
+  NativeInstruction* ni = nativeInstruction_at(pc);
+  assert(ni->is_jump(), "Initial code version: GC barrier fastpath must be a jump");
+  NativeJump* jmp = nativeJump_at(pc);
+  return jmp->jump_destination();
+}
+
+void insert_5_byte_nop(address pc) {
+  *(pc + 0) = 0x0F;
+  *(pc + 1) = 0x1F;
+  *(pc + 2) = 0x44;
+  *(pc + 3) = 0x00;
+  *(pc + 4) = 0x00;
+  ICache::invalidate_range(pc, 5);
+}
+
+bool is_5_byte_nop(address pc) {
+  if (*(pc + 0) != 0x0F) return false;
+  if (*(pc + 1) != 0x1F) return false;
+  if (*(pc + 2) != 0x44) return false;
+  if (*(pc + 3) != 0x00) return false;
+  if (*(pc + 4) != 0x00) return false;
+  return true;
+}
+
+void check_at(bool cond, address pc, const char* msg) {
+  assert(cond, "%s: at PC " PTR_FORMAT ": %02x%02x%02x%02x%02x",
+         msg, p2i(pc), *(pc + 0), *(pc + 1), *(pc + 2), *(pc + 3), *(pc + 4));
+}
+
+void ShenandoahBarrierSetAssembler::patch_branch_to_nop(address pc) {
+  NativeInstruction* ni = nativeInstruction_at(pc);
+  if (ni->is_jump()) {
+    insert_5_byte_nop(pc);
+  } else {
+    check_at(is_5_byte_nop(pc), pc, "Should already be nop");
+  }
+}
+
+void ShenandoahBarrierSetAssembler::patch_nop_to_branch(address pc, address stub_addr) {
+  NativeInstruction* ni = nativeInstruction_at(pc);
+  if (is_5_byte_nop(pc)) {
+    NativeJump::insert(pc, stub_addr);
+  } else {
+    check_at(ni->is_jump(), pc, "Should already be jump");
+    check_at(nativeJump_at(pc)->jump_destination() == stub_addr, pc, "Jump should be to the same address");
+  }
+}
+
 #undef __
 #define __ masm->
 
 void ShenandoahBarrierSetAssembler::load_c2(const MachNode* node, MacroAssembler* masm, Register dst, Address src, bool narrow) {
   // Do the actual load. This load is the candidate for implicit null check, and MUST come first.
@@ -1184,14 +1234,39 @@
 #define __ masm.
 
 void ShenandoahBarrierStubC2::enter_if_gc_state(MacroAssembler& masm, const char test_state) {
   Assembler::InlineSkippedInstructionsCounter skip_counter(&masm);
 
-  Address gc_state_fast(r15_thread, in_bytes(ShenandoahThreadLocalData::gc_state_fast_offset()));
-  __ testb(gc_state_fast, ShenandoahThreadLocalData::gc_state_to_fast(test_state));
-  __ jcc(Assembler::notZero, *entry());
-  __ bind(*continuation());
+  if (ShenandoahGCStateCheckRemove) {
+    // Unrealistic: remove all barrier fastpath checks.
+  } else if (ShenandoahGCStateCheckHotpatch) {
+    // Emit the unconditional branch in the first version of the method.
+    // Let the rest of runtime figure out how to manage it.
+    __ relocate(barrier_Relocation::spec());
+    __ jmp(*entry(), /* maybe_short = */ false);
+
+    Label L_done;
+#ifdef ASSERT
+    Address gc_state(r15_thread, in_bytes(ShenandoahThreadLocalData::gc_state_offset()));
+    __ testb(gc_state, 0xFF);
+    __ jccb(Assembler::zero, L_done);
+    __ hlt(); // Correctness bug: barrier is NOP-ed, but heap is NOT IDLE
+#endif
+    __ bind(*continuation());
+// This is futile to assert, because barriers are turned off asynchronously.
+// #ifdef ASSERT
+//     __ testb(gc_state, 0xFF);
+//     __ jccb(Assembler::notZero, L_done);
+//     __ hlt(); // Performance bug: barrier is JMP-ed, but heap is IDLE
+// #endif
+    __ bind(L_done);
+  } else {
+    Address gc_state_fast(r15_thread, in_bytes(ShenandoahThreadLocalData::gc_state_fast_offset()));
+    __ testb(gc_state_fast, ShenandoahThreadLocalData::gc_state_to_fast(test_state));
+    __ jcc(Assembler::notZero, *entry());
+    __ bind(*continuation());
+  }
 }
 
 void ShenandoahBarrierStubC2::emit_code(MacroAssembler& masm) {
   assert(_needs_keep_alive_barrier || _needs_load_ref_barrier, "Why are you here?");
 
@@ -1266,11 +1341,12 @@
   Address buffer(r15_thread, in_bytes(ShenandoahThreadLocalData::satb_mark_queue_buffer_offset()));
 
   Label L_fast, L_done;
 
   // If another barrier is enabled as well, do a runtime check for a specific barrier.
-  if (_needs_load_ref_barrier) {
+  // Hotpatched GC checks only care about idle/non-idle state, so needs a check anyhow.
+  if (_needs_load_ref_barrier || ShenandoahGCStateCheckHotpatch) {
     Address gc_state(r15_thread, in_bytes(ShenandoahThreadLocalData::gc_state_offset()));
     __ testb(gc_state, ShenandoahHeap::MARKING);
     __ jccb(Assembler::zero, L_done);
   }
 
@@ -1313,11 +1389,12 @@
 
 void ShenandoahBarrierStubC2::lrb(MacroAssembler& masm, Register obj, Address addr, Register tmp) {
   Label L_done;
 
   // If another barrier is enabled as well, do a runtime check for a specific barrier.
-  if (_needs_keep_alive_barrier) {
+  // Hotpatched GC checks only care about idle/non-idle state, so needs a check anyhow.
+  if (_needs_keep_alive_barrier || ShenandoahGCStateCheckHotpatch) {
     Address gc_state(r15_thread, in_bytes(ShenandoahThreadLocalData::gc_state_offset()));
     __ testb(gc_state, ShenandoahHeap::HAS_FORWARDED | (_needs_load_ref_weak_barrier ? ShenandoahHeap::WEAK_ROOTS : 0));
     __ jccb(Assembler::zero, L_done);
   }
 
diff a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.hpp b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.hpp
--- a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.hpp
+++ b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.hpp
@@ -215,19 +215,20 @@
     _fastpath_branch_offset(offset),
     _test_and_branch_reachable(),
     _skip_trampoline(),
     _test_and_branch_reachable_entry(),
     _save_slots_idx(0) {
+    assert(!ShenandoahSkipBarriers, "Do not touch stubs when disabled");
     assert(!_narrow || is_heap_access(node), "Only heap accesses can be narrow");
     post_init(offset);
   }
 
   static bool is_heap_access(const MachNode* node) {
     return (node->barrier_data() & ShenandoahBitNative) == 0;
   }
   static bool needs_slow_barrier(const MachNode* node) {
-    return needs_load_ref_barrier(node) || needs_keep_alive_barrier(node);
+    return !ShenandoahSkipBarriers && (needs_load_ref_barrier(node) || needs_keep_alive_barrier(node));
   }
   static bool needs_load_ref_barrier(const MachNode* node) {
     return (node->barrier_data() & (ShenandoahBitStrong | ShenandoahBitWeak | ShenandoahBitPhantom)) != 0;
   }
   static bool needs_load_ref_barrier_weak(const MachNode* node) {
@@ -235,11 +236,11 @@
   }
   static bool needs_keep_alive_barrier(const MachNode* node) {
     return (node->barrier_data() & ShenandoahBitKeepAlive) != 0;
   }
   static bool needs_card_barrier(const MachNode* node) {
-    return (node->barrier_data() & ShenandoahBitCardMark) != 0;
+    return !ShenandoahSkipBarriers && ((node->barrier_data() & ShenandoahBitCardMark) != 0);
   }
   static bool maybe_null(const MachNode* node) {
     return (node->barrier_data() & ShenandoahBitNotNull) == 0;
   }
 
diff a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp
@@ -186,10 +186,15 @@
       && strcmp(ShenandoahGCHeuristics, "adaptive") != 0) {
     log_warning(gc)("Ignoring -XX:ShenandoahGCHeuristics input: %s, because generational shenandoah only"
       " supports adaptive heuristics", ShenandoahGCHeuristics);
   }
 
+  if (ShenandoahGCStateCheckHotpatch && NMethodRelocation) {
+    log_warning(gc)("Hot patching is not yet supported with NMethod relocation, disabling");
+    FLAG_SET_DEFAULT(ShenandoahGCStateCheckHotpatch, false);
+  }
+
   FullGCForwarding::initialize_flags(MaxHeapSize);
 }
 
 size_t ShenandoahArguments::conservative_max_heap_alignment() {
   size_t align = next_power_of_2(ShenandoahMaxRegionSize);
diff a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp
@@ -177,10 +177,13 @@
         ShenandoahKeepAliveClosure oops;
         StackWatermarkSet::finish_processing(JavaThread::cast(thread), &oops, StackWatermarkKind::gc);
       } else if (_heap->is_concurrent_weak_root_in_progress() && _heap->is_evacuation_in_progress()) {
         ShenandoahContextEvacuateUpdateRootsClosure oops;
         StackWatermarkSet::finish_processing(JavaThread::cast(thread), &oops, StackWatermarkKind::gc);
+      } else if (ShenandoahGCStateCheckHotpatch) {
+        ShenandoahNoOpClosure oops;
+        StackWatermarkSet::finish_processing(JavaThread::cast(thread), &oops, StackWatermarkKind::gc);
       }
     }
   }
 }
 
diff a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSetNMethod.cpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSetNMethod.cpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSetNMethod.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSetNMethod.cpp
@@ -70,8 +70,8 @@
 
   // CodeCache unloading support
   nm->mark_as_maybe_on_stack();
 
   // Disarm
-  ShenandoahNMethod::disarm_nmethod(nm);
+  ShenandoahNMethod::disarm_nmethod_unlocked(nm);
   return true;
 }
diff a/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp b/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp
@@ -178,13 +178,10 @@
   inline void do_oop(oop* p);
   inline void do_oop(narrowOop* p);
 };
 
 class ShenandoahNMethodAndDisarmClosure : public NMethodToOopClosure {
-private:
-  BarrierSetNMethod* const _bs;
-
 public:
   inline ShenandoahNMethodAndDisarmClosure(OopClosure* cl);
   inline void do_nmethod(nmethod* nm);
 };
 
@@ -242,10 +239,16 @@
 
 //
 // ========= Utilities
 //
 
+class ShenandoahNoOpClosure : public OopClosure {
+public:
+  inline void do_oop(oop* p)       { }
+  inline void do_oop(narrowOop* p) { }
+};
+
 #ifdef ASSERT
 class ShenandoahAssertNotForwardedClosure : public OopClosure {
 private:
   template <class T>
   inline void do_oop_work(T* p);
diff a/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp
@@ -207,19 +207,17 @@
 void ShenandoahCleanUpdateWeakOopsClosure<CONCURRENT, IsAlive, KeepAlive>::do_oop(narrowOop* p) {
   ShouldNotReachHere();
 }
 
 ShenandoahNMethodAndDisarmClosure::ShenandoahNMethodAndDisarmClosure(OopClosure* cl) :
-  NMethodToOopClosure(cl, true /* fix_relocations */),
-   _bs(BarrierSet::barrier_set()->barrier_set_nmethod()) {
-}
+  NMethodToOopClosure(cl, true /* fix_relocations */) {}
 
 void ShenandoahNMethodAndDisarmClosure::do_nmethod(nmethod* nm) {
   assert(nm != nullptr, "Sanity");
   assert(!ShenandoahNMethod::gc_data(nm)->is_unregistered(), "Should not be here");
   NMethodToOopClosure::do_nmethod(nm);
-  _bs->disarm(nm);
+  ShenandoahNMethod::disarm_nmethod(nm);
 }
 
 
 //
 // ========= Update References
diff a/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.cpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.cpp
@@ -38,24 +38,10 @@
 
 
 ShenandoahNMethodTable* ShenandoahCodeRoots::_nmethod_table;
 int ShenandoahCodeRoots::_disarmed_value = 1;
 
-bool ShenandoahCodeRoots::use_nmethod_barriers_for_mark() {
-  // Continuations need nmethod barriers for scanning stack chunk nmethods.
-  if (Continuations::enabled()) return true;
-
-  // Concurrent class unloading needs nmethod barriers.
-  // When a nmethod is about to be executed, we need to make sure that all its
-  // metadata are marked. The alternative is to remark thread roots at final mark
-  // pause, which would cause latency issues.
-  if (ShenandoahHeap::heap()->unload_classes()) return true;
-
-  // Otherwise, we can go without nmethod barriers.
-  return false;
-}
-
 void ShenandoahCodeRoots::initialize() {
   _nmethod_table = new ShenandoahNMethodTable();
 }
 
 void ShenandoahCodeRoots::register_nmethod(nmethod* nm) {
@@ -66,31 +52,31 @@
 void ShenandoahCodeRoots::unregister_nmethod(nmethod* nm) {
   assert_locked_or_safepoint(CodeCache_lock);
   _nmethod_table->unregister_nmethod(nm);
 }
 
-void ShenandoahCodeRoots::arm_nmethods_for_mark() {
-  if (use_nmethod_barriers_for_mark()) {
-    BarrierSet::barrier_set()->barrier_set_nmethod()->arm_all_nmethods();
+void ShenandoahCodeRoots::arm_nmethods() {
+  if (ShenandoahGCStateCheckHotpatch) {
+    char gc_state = ShenandoahHeap::heap()->gc_state();
+    log_info(gc)("Arming nmethods with GC state: %d [%s%s%s%s%s%s%s]",
+         gc_state,
+         ((gc_state & ShenandoahHeap::HAS_FORWARDED) > 0) ? "HAS_FORWARDED "  : "",
+         ((gc_state & ShenandoahHeap::MARKING) > 0)       ? "MARKING "        : "",
+         ((gc_state & ShenandoahHeap::EVACUATION) > 0)    ? "EVACUATION "     : "",
+         ((gc_state & ShenandoahHeap::UPDATE_REFS) > 0)   ? "UPDATE_REFS "    : "",
+         ((gc_state & ShenandoahHeap::WEAK_ROOTS) > 0)    ? "WEAK_ROOTS "     : "",
+         ((gc_state & ShenandoahHeap::YOUNG_MARKING) > 0) ? "YOUNG_MARKING "  : "",
+         ((gc_state & ShenandoahHeap::OLD_MARKING) > 0)   ? "OLD_MARKING "    : ""
+    );
   }
-}
-
-void ShenandoahCodeRoots::arm_nmethods_for_evac() {
   BarrierSet::barrier_set()->barrier_set_nmethod()->arm_all_nmethods();
 }
 
 class ShenandoahDisarmNMethodClosure : public NMethodClosure {
-private:
-  BarrierSetNMethod* const _bs;
-
 public:
-  ShenandoahDisarmNMethodClosure() :
-    _bs(BarrierSet::barrier_set()->barrier_set_nmethod()) {
-  }
-
   virtual void do_nmethod(nmethod* nm) {
-    _bs->disarm(nm);
+    ShenandoahNMethod::disarm_nmethod(nm);
   }
 };
 
 class ShenandoahDisarmNMethodsTask : public WorkerTask {
 private:
@@ -98,25 +84,21 @@
   ShenandoahConcurrentNMethodIterator _iterator;
 
 public:
   ShenandoahDisarmNMethodsTask() :
     WorkerTask("Shenandoah Disarm NMethods"),
-    _iterator(ShenandoahCodeRoots::table()) {
-    assert(SafepointSynchronize::is_at_safepoint(), "Only at a safepoint");
-  }
+    _iterator(ShenandoahCodeRoots::table()) {}
 
   virtual void work(uint worker_id) {
     ShenandoahParallelWorkerSession worker_session(worker_id);
     _iterator.nmethods_do(&_cl);
   }
 };
 
 void ShenandoahCodeRoots::disarm_nmethods() {
-  if (use_nmethod_barriers_for_mark()) {
-    ShenandoahDisarmNMethodsTask task;
-    ShenandoahHeap::heap()->workers()->run_task(&task);
-  }
+  ShenandoahDisarmNMethodsTask task;
+  ShenandoahHeap::heap()->workers()->run_task(&task);
 }
 
 class ShenandoahNMethodUnlinkClosure : public NMethodClosure {
 private:
   bool                      _unloading_occurred;
diff a/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.hpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.hpp
@@ -65,18 +65,15 @@
   }
 
   // Concurrent nmethod unloading support
   static void unlink(WorkerThreads* workers, bool unloading_occurred);
   static void purge();
-  static void arm_nmethods_for_mark();
-  static void arm_nmethods_for_evac();
+  static void arm_nmethods();
   static void disarm_nmethods();
   static int  disarmed_value()         { return _disarmed_value; }
   static int* disarmed_value_address() { return &_disarmed_value; }
 
-  static bool use_nmethod_barriers_for_mark();
-
 private:
   static ShenandoahNMethodTable* _nmethod_table;
   static int                     _disarmed_value;
 };
 
diff a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp
@@ -720,12 +720,11 @@
 
   // Make above changes visible to worker threads
   OrderAccess::fence();
 
   // Arm nmethods for concurrent mark
-  ShenandoahCodeRoots::arm_nmethods_for_mark();
-
+  ShenandoahCodeRoots::arm_nmethods();
   ShenandoahStackWatermark::change_epoch_id();
 
   {
     ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::init_propagate_gc_state);
     heap->propagate_gc_state_to_all_threads();
@@ -762,11 +761,13 @@
     _generation->prepare_regions_and_collection_set(true /*concurrent*/);
 
     // Has to be done after cset selection
     heap->prepare_concurrent_roots();
 
-    if (!heap->collection_set()->is_empty()) {
+    // ShenandoahGCStateCheckHotpatch: we need full cycle to patch barriers back to idle.
+    // final-roots is pauseless, so there is no way to arm barriers.
+    if (ShenandoahGCStateCheckHotpatch || !heap->collection_set()->is_empty()) {
       LogTarget(Debug, gc, cset) lt;
       if (lt.is_enabled()) {
         ResourceMark rm;
         LogStream ls(lt);
         heap->collection_set()->print_on(&ls);
@@ -780,11 +781,11 @@
       heap->set_evacuation_in_progress(true);
       // From here on, we need to update references.
       heap->set_has_forwarded_objects(true);
 
       // Arm nmethods/stack for concurrent processing
-      ShenandoahCodeRoots::arm_nmethods_for_evac();
+      ShenandoahCodeRoots::arm_nmethods();
       ShenandoahStackWatermark::change_epoch_id();
 
     } else {
       if (ShenandoahVerify) {
         ShenandoahTimingsTracker v(ShenandoahPhaseTimings::final_mark_verify);
@@ -1026,11 +1027,11 @@
     ShenandoahNMethodLocker locker(data->lock());
     // Setup EvacOOM scope below reentrant lock to avoid deadlock with
     // nmethod_entry_barrier
     ShenandoahEvacOOMScope oom;
     data->oops_do(&_cl, true/*fix relocation*/);
-    _bs->disarm(n);
+    ShenandoahNMethod::disarm_nmethod_unlocked(n);
   }
 };
 
 class ShenandoahConcurrentRootsEvacUpdateTask : public WorkerTask {
 private:
@@ -1215,10 +1216,16 @@
   }
 
   heap->rebuild_free_set(true /*concurrent*/);
   _generation->heuristics()->start_idle_span();
 
+  if (ShenandoahGCStateCheckHotpatch) {
+    // Final pause: update GC barriers to idle state.
+    ShenandoahCodeRoots::arm_nmethods();
+    ShenandoahStackWatermark::change_epoch_id();
+  }
+
   {
     ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::final_update_refs_propagate_gc_state);
     heap->propagate_gc_state_to_all_threads();
   }
 }
@@ -1272,10 +1279,15 @@
       heap->young_generation()->reset_mark_bitmap<false>();
     }
   } else {
     _generation->reset_mark_bitmap<false>();
   }
+
+  // Also go and disable all barriers in all current nmethods.
+  if (ShenandoahGCStateCheckHotpatch) {
+    ShenandoahCodeRoots::disarm_nmethods();
+  }
 }
 
 bool ShenandoahConcurrentGC::check_cancellation_and_abort(ShenandoahDegenPoint point) {
   if (ShenandoahHeap::heap()->cancelled_gc()) {
     _degen_point = point;
diff a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp
@@ -34,10 +34,11 @@
 #include "gc/shenandoah/shenandoahHeap.inline.hpp"
 #include "gc/shenandoah/shenandoahMetrics.hpp"
 #include "gc/shenandoah/shenandoahMonitoringSupport.hpp"
 #include "gc/shenandoah/shenandoahOldGeneration.hpp"
 #include "gc/shenandoah/shenandoahRootProcessor.inline.hpp"
+#include "gc/shenandoah/shenandoahStackWatermark.hpp"
 #include "gc/shenandoah/shenandoahSTWMark.hpp"
 #include "gc/shenandoah/shenandoahUtils.hpp"
 #include "gc/shenandoah/shenandoahVerifier.hpp"
 #include "gc/shenandoah/shenandoahVMOperations.hpp"
 #include "gc/shenandoah/shenandoahWorkerPolicy.hpp"
@@ -282,13 +283,19 @@
         op_update_refs();
         op_update_roots();
         assert(!heap->cancelled_gc(), "STW reference update can not OOM");
       }
 
-      // Disarm nmethods that armed in concurrent cycle.
-      // In above case, update roots should disarm them
-      ShenandoahCodeRoots::disarm_nmethods();
+      if (ShenandoahGCStateCheckHotpatch) {
+        // Leaving degenerated GC, we need to flip barriers back to idle.
+        ShenandoahCodeRoots::arm_nmethods();
+        ShenandoahStackWatermark::change_epoch_id();
+      } else {
+        // Disarm nmethods that armed in concurrent cycle.
+        // In above case, update roots should disarm them
+        ShenandoahCodeRoots::disarm_nmethods();
+      }
 
       op_cleanup_complete();
 
       if (heap->mode()->is_generational()) {
         ShenandoahGenerationalHeap::heap()->complete_degenerated_cycle();
diff a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp
@@ -50,10 +50,11 @@
 #include "gc/shenandoah/shenandoahMetrics.hpp"
 #include "gc/shenandoah/shenandoahMonitoringSupport.hpp"
 #include "gc/shenandoah/shenandoahPhaseTimings.hpp"
 #include "gc/shenandoah/shenandoahReferenceProcessor.hpp"
 #include "gc/shenandoah/shenandoahRootProcessor.inline.hpp"
+#include "gc/shenandoah/shenandoahStackWatermark.hpp"
 #include "gc/shenandoah/shenandoahSTWMark.hpp"
 #include "gc/shenandoah/shenandoahUtils.hpp"
 #include "gc/shenandoah/shenandoahVerifier.hpp"
 #include "gc/shenandoah/shenandoahVMOperations.hpp"
 #include "gc/shenandoah/shenandoahWorkerPolicy.hpp"
@@ -126,10 +127,16 @@
 
   // Regardless if progress was made, we record that we completed a "successful" full GC.
   _generation->heuristics()->record_success_full();
   heap->shenandoah_policy()->record_success_full();
 
+  if (ShenandoahGCStateCheckHotpatch) {
+    // Leaving full GC, we need to flip barriers back to idle.
+    ShenandoahCodeRoots::arm_nmethods();
+    ShenandoahStackWatermark::change_epoch_id();
+  }
+
   {
     ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::full_gc_propagate_gc_state);
     heap->propagate_gc_state_to_all_threads();
   }
 }
diff a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
@@ -185,11 +185,12 @@
   return false;
 }
 
 void ShenandoahGenerationalHeap::evacuate_collection_set(ShenandoahGeneration* generation, bool concurrent) {
   ShenandoahRegionIterator regions;
-  ShenandoahGenerationalEvacuationTask task(this, generation, &regions, concurrent, false /* only promote regions */);
+  ShenandoahGenerationalEvacuationTask task(this, generation, &regions, concurrent,
+                                           (ShenandoahGCStateCheckHotpatch ? collection_set()->is_empty() : false) /* only promote regions */);
   workers()->run_task(&task);
 }
 
 void ShenandoahGenerationalHeap::promote_regions_in_place(ShenandoahGeneration* generation, bool concurrent) {
   ShenandoahRegionIterator regions;
diff a/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp b/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp
@@ -22,19 +22,20 @@
  * questions.
  *
  */
 
 
+#include "gc/shenandoah/shenandoahBarrierSetAssembler.hpp"
 #include "gc/shenandoah/shenandoahClosures.inline.hpp"
 #include "gc/shenandoah/shenandoahHeap.inline.hpp"
 #include "gc/shenandoah/shenandoahNMethod.inline.hpp"
 #include "memory/resourceArea.hpp"
 #include "runtime/continuation.hpp"
 #include "runtime/safepointVerifiers.hpp"
 
-ShenandoahNMethod::ShenandoahNMethod(nmethod* nm, GrowableArray<oop*>& oops, bool non_immediate_oops) :
-  _nm(nm), _oops(nullptr), _oops_count(0), _unregistered(false), _lock(), _ic_lock() {
+ShenandoahNMethod::ShenandoahNMethod(nmethod* nm, GrowableArray<oop*>& oops, bool non_immediate_oops, GrowableArray<ShenandoahNMethodBarrier>& barriers) :
+  _nm(nm), _oops(nullptr), _oops_count(0), _barriers(nullptr), _barriers_count(0), _unregistered(false), _lock(), _ic_lock() {
 
   if (!oops.is_empty()) {
     _oops_count = oops.length();
     _oops = NEW_C_HEAP_ARRAY(oop*, _oops_count, mtGC);
     for (int c = 0; c < _oops_count; c++) {
@@ -42,24 +43,36 @@
     }
   }
   _has_non_immed_oops = non_immediate_oops;
 
   assert_same_oops();
+
+  if (!barriers.is_empty()) {
+    _barriers_count = barriers.length();
+    _barriers = NEW_C_HEAP_ARRAY(ShenandoahNMethodBarrier, _barriers_count, mtGC);
+    for (int c = 0; c < _barriers_count; c++) {
+      _barriers[c] = barriers.at(c);
+    }
+  }
 }
 
 ShenandoahNMethod::~ShenandoahNMethod() {
   if (_oops != nullptr) {
     FREE_C_HEAP_ARRAY(oop*, _oops);
   }
+  if (_barriers != nullptr) {
+    FREE_C_HEAP_ARRAY(ShenandoahNMethodBarrier, _barriers);
+  }
 }
 
 void ShenandoahNMethod::update() {
   ResourceMark rm;
   bool non_immediate_oops = false;
   GrowableArray<oop*> oops;
+  GrowableArray<ShenandoahNMethodBarrier> barriers;
 
-  detect_reloc_oops(nm(), oops, non_immediate_oops);
+  parse(nm(), oops, non_immediate_oops, barriers);
   if (oops.length() != _oops_count) {
     if (_oops != nullptr) {
       FREE_C_HEAP_ARRAY(oop*, _oops);
       _oops = nullptr;
     }
@@ -76,48 +89,65 @@
   _has_non_immed_oops = non_immediate_oops;
 
   assert_same_oops();
 }
 
-void ShenandoahNMethod::detect_reloc_oops(nmethod* nm, GrowableArray<oop*>& oops, bool& has_non_immed_oops) {
+void ShenandoahNMethod::parse(nmethod* nm, GrowableArray<oop*>& oops, bool& has_non_immed_oops, GrowableArray<ShenandoahNMethodBarrier>& barriers) {
   has_non_immed_oops = false;
-  // Find all oops relocations
   RelocIterator iter(nm);
   while (iter.next()) {
-    if (iter.type() != relocInfo::oop_type) {
-      // Not an oop
-      continue;
-    }
-
-    oop_Relocation* r = iter.oop_reloc();
-    if (!r->oop_is_immediate()) {
-      // Non-immediate oop found
-      has_non_immed_oops = true;
-      continue;
-    }
-
-    oop value = r->oop_value();
-    if (value != nullptr) {
-      oop* addr = r->oop_addr();
-      shenandoah_assert_correct(addr, value);
-      shenandoah_assert_not_in_cset_except(addr, value, ShenandoahHeap::heap()->cancelled_gc());
-      shenandoah_assert_not_forwarded(addr, value);
-      // Non-null immediate oop found. null oops can safely be
-      // ignored since the method will be re-registered if they
-      // are later patched to be non-null.
-      oops.push(addr);
+    switch (iter.type()) {
+      case relocInfo::oop_type: {
+        oop_Relocation* r = iter.oop_reloc();
+        if (!r->oop_is_immediate()) {
+          // Non-immediate oop found
+          has_non_immed_oops = true;
+          break;
+        }
+
+        oop value = r->oop_value();
+        if (value != nullptr) {
+          oop* addr = r->oop_addr();
+          shenandoah_assert_correct(addr, value);
+          shenandoah_assert_not_in_cset_except(addr, value, ShenandoahHeap::heap()->cancelled_gc());
+          shenandoah_assert_not_forwarded(addr, value);
+          // Non-null immediate oop found. null oops can safely be
+          // ignored since the method will be re-registered if they
+          // are later patched to be non-null.
+          oops.push(addr);
+        }
+        break;
+      }
+#ifdef COMPILER2
+      case relocInfo::barrier_type: {
+        assert(ShenandoahGCStateCheckHotpatch, "Who emits these?");
+        barrier_Relocation* r = iter.barrier_reloc();
+
+        ShenandoahNMethodBarrier b;
+        b._pc = r->addr();
+        b._stub_addr = ShenandoahBarrierSetAssembler::parse_stub_address(b._pc);
+        // TODO: Can technically figure out which GC state we care about in this reloc.
+        // b._gc_state_fast_bit = r->format();
+        barriers.push(b);
+        break;
+      }
+#endif
+      default:
+        // We do not care about other relocations.
+        break;
     }
   }
 }
 
 ShenandoahNMethod* ShenandoahNMethod::for_nmethod(nmethod* nm) {
   ResourceMark rm;
   bool non_immediate_oops = false;
   GrowableArray<oop*> oops;
+  GrowableArray<ShenandoahNMethodBarrier> barriers;
 
-  detect_reloc_oops(nm, oops, non_immediate_oops);
-  return new ShenandoahNMethod(nm, oops, non_immediate_oops);
+  parse(nm, oops, non_immediate_oops, barriers);
+  return new ShenandoahNMethod(nm, oops, non_immediate_oops, barriers);
 }
 
 void ShenandoahNMethod::heal_nmethod(nmethod* nm) {
   ShenandoahNMethod* data = gc_data(nm);
   assert(data != nullptr, "Sanity");
@@ -136,10 +166,30 @@
     // In this case, concurrent root phase is skipped and degenerated GC should be
     // followed, where nmethods are disarmed.
   }
 }
 
+void ShenandoahNMethod::update_barriers() {
+#ifdef COMPILER2
+  if (!ShenandoahGCStateCheckHotpatch) {
+    return;
+  }
+
+  ShenandoahHeap* heap = ShenandoahHeap::heap();
+
+  for (int c = 0; c < _barriers_count; c++) {
+    address pc = _barriers[c]._pc;
+    address stub_addr = _barriers[c]._stub_addr;
+    if (heap->is_idle()) {
+      ShenandoahBarrierSetAssembler::patch_branch_to_nop(pc);
+    } else {
+      ShenandoahBarrierSetAssembler::patch_nop_to_branch(pc, stub_addr);
+    }
+  }
+#endif
+}
+
 #ifdef ASSERT
 void ShenandoahNMethod::assert_correct() {
   ShenandoahHeap* heap = ShenandoahHeap::heap();
   for (int c = 0; c < _oops_count; c++) {
     oop *loc = _oops[c];
@@ -204,12 +254,13 @@
     debug_stream.print_cr("recorded oops: %d", _oops_count);
     for (int i = 0; i < _oops_count; i++) {
       debug_stream.print_cr("-> " PTR_FORMAT, p2i(_oops[i]));
     }
     GrowableArray<oop*> check;
+    GrowableArray<ShenandoahNMethodBarrier> barriers;
     bool non_immed;
-    detect_reloc_oops(nm(), check, non_immed);
+    parse(nm(), check, non_immed, barriers);
     debug_stream.print_cr("check oops: %d", check.length());
     for (int i = 0; i < check.length(); i++) {
       debug_stream.print_cr("-> " PTR_FORMAT, p2i(check.at(i)));
     }
     fatal("Must match #detected: %d, #recorded: %d, #total: %d, begin: " PTR_FORMAT ", end: " PTR_FORMAT "\n%s",
@@ -241,19 +292,21 @@
     assert(nm == data->nm(), "Must be same nmethod");
     // Prevent updating a nmethod while concurrent iteration is in progress.
     wait_until_concurrent_iteration_done();
     ShenandoahNMethodLocker data_locker(data->lock());
     data->update();
+    data->update_barriers();
   } else {
     // For a new nmethod, we can safely append it to the list, because
     // concurrent iteration will not touch it.
     data = ShenandoahNMethod::for_nmethod(nm);
     assert(data != nullptr, "Sanity");
     ShenandoahNMethod::attach_gc_data(nm, data);
     ShenandoahLocker locker(&_lock);
     log_register_nmethod(nm);
     append(data);
+    data->update_barriers();
   }
   // Disarm new nmethod
   ShenandoahNMethod::disarm_nmethod(nm);
 }
 
diff a/src/hotspot/share/gc/shenandoah/shenandoahNMethod.hpp b/src/hotspot/share/gc/shenandoah/shenandoahNMethod.hpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahNMethod.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahNMethod.hpp
@@ -35,53 +35,63 @@
 
 // Use ShenandoahReentrantLock as ShenandoahNMethodLock
 typedef ShenandoahReentrantLock<ShenandoahSimpleLock> ShenandoahNMethodLock;
 typedef ShenandoahLocker<ShenandoahNMethodLock>       ShenandoahNMethodLocker;
 
+struct ShenandoahNMethodBarrier {
+  address _pc;
+  address _stub_addr;
+};
+
 // ShenandoahNMethod tuple records the internal locations of oop slots within reclocation stream in
 // the nmethod. This allows us to quickly scan the oops without doing the nmethod-internal scans,
 // that sometimes involves parsing the machine code. Note it does not record the oops themselves,
 // because it would then require handling these tuples as the new class of roots.
 class ShenandoahNMethod : public CHeapObj<mtGC> {
 private:
   nmethod* const          _nm;
   oop**                   _oops;
   int                     _oops_count;
+  ShenandoahNMethodBarrier* _barriers;
+  int                     _barriers_count;
   bool                    _has_non_immed_oops;
   bool                    _unregistered;
   ShenandoahNMethodLock   _lock;
   ShenandoahNMethodLock   _ic_lock;
 
 public:
-  ShenandoahNMethod(nmethod *nm, GrowableArray<oop*>& oops, bool has_non_immed_oops);
+  ShenandoahNMethod(nmethod *nm, GrowableArray<oop*>& oops, bool has_non_immed_oops, GrowableArray<ShenandoahNMethodBarrier>& barriers);
   ~ShenandoahNMethod();
 
   inline nmethod* nm() const;
   inline ShenandoahNMethodLock* lock();
   inline ShenandoahNMethodLock* ic_lock();
   inline void oops_do(OopClosure* oops, bool fix_relocations = false);
   // Update oops when the nmethod is re-registered
   void update();
 
+  void update_barriers();
+
   inline bool is_unregistered() const;
 
   static ShenandoahNMethod* for_nmethod(nmethod* nm);
   static inline ShenandoahNMethodLock* lock_for_nmethod(nmethod* nm);
   static inline ShenandoahNMethodLock* ic_lock_for_nmethod(nmethod* nm);
 
   static void heal_nmethod(nmethod* nm);
   static inline void heal_nmethod_metadata(ShenandoahNMethod* nmethod_data);
   static inline void disarm_nmethod(nmethod* nm);
+  static inline void disarm_nmethod_unlocked(nmethod* nm);
 
   static inline ShenandoahNMethod* gc_data(nmethod* nm);
   static inline void attach_gc_data(nmethod* nm, ShenandoahNMethod* gc_data);
 
   void assert_correct() NOT_DEBUG_RETURN;
   void assert_same_oops() NOT_DEBUG_RETURN;
 
 private:
-  static void detect_reloc_oops(nmethod* nm, GrowableArray<oop*>& oops, bool& _has_non_immed_oops);
+  static void parse(nmethod* nm, GrowableArray<oop*>& oops, bool& _has_non_immed_oops, GrowableArray<ShenandoahNMethodBarrier>& barriers);
 };
 
 class ShenandoahNMethodTable;
 
 // ShenandoahNMethodList holds registered nmethod data. The list is reference counted.
diff a/src/hotspot/share/gc/shenandoah/shenandoahNMethod.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahNMethod.inline.hpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahNMethod.inline.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahNMethod.inline.hpp
@@ -69,12 +69,27 @@
   ShenandoahEvacuateUpdateMetadataClosure cl;
   nmethod_data->oops_do(&cl, true /*fix relocation*/);
 }
 
 void ShenandoahNMethod::disarm_nmethod(nmethod* nm) {
+  ShenandoahNMethod* data = gc_data(nm);
+  assert(data != nullptr, "Sanity");
+  ShenandoahNMethodLock* lock = data->lock();
+  assert(lock != nullptr, "Must be");
+  ShenandoahNMethodLocker locker(lock);
+
+  disarm_nmethod_unlocked(nm);
+}
+
+void ShenandoahNMethod::disarm_nmethod_unlocked(nmethod* nm) {
+  ShenandoahNMethod* data = gc_data(nm);
+  assert(data != nullptr, "Sanity");
+  assert(data->lock()->owned_by_self(), "Must hold the lock");
+
   BarrierSetNMethod* const bs = BarrierSet::barrier_set()->barrier_set_nmethod();
   if (bs->is_armed(nm)) {
+    data->update_barriers();
     bs->disarm(nm);
   }
 }
 
 ShenandoahNMethod* ShenandoahNMethod::gc_data(nmethod* nm) {
diff a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.cpp b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.cpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.cpp
@@ -196,24 +196,20 @@
   _code_roots(phase) {
   assert(ShenandoahHeap::heap()->is_full_gc_in_progress(), "Full GC only");
 }
 
 void ShenandoahRootAdjuster::roots_do(uint worker_id, OopClosure* oops) {
-  NMethodToOopClosure code_blob_cl(oops, NMethodToOopClosure::FixRelocations);
   ShenandoahNMethodAndDisarmClosure nmethods_and_disarm_Cl(oops);
-  NMethodToOopClosure* adjust_code_closure = ShenandoahCodeRoots::use_nmethod_barriers_for_mark() ?
-                                             static_cast<NMethodToOopClosure*>(&nmethods_and_disarm_Cl) :
-                                             static_cast<NMethodToOopClosure*>(&code_blob_cl);
   CLDToOopClosure adjust_cld_closure(oops, ClassLoaderData::_claim_strong);
 
   // Process light-weight/limited parallel roots then
   _vm_roots.oops_do(oops, worker_id);
   _weak_roots.oops_do<OopClosure>(oops, worker_id);
   _cld_roots.cld_do(&adjust_cld_closure, worker_id);
 
   // Process heavy-weight/fully parallel roots the last
-  _code_roots.nmethods_do(adjust_code_closure, worker_id);
+  _code_roots.nmethods_do(&nmethods_and_disarm_Cl, worker_id);
   _thread_roots.oops_do(oops, nullptr, worker_id);
 }
 
 ShenandoahHeapIterationRootScanner::ShenandoahHeapIterationRootScanner(uint n_workers) :
   ShenandoahRootProcessor(ShenandoahPhaseTimings::heap_iteration_roots),
diff a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp
@@ -168,15 +168,12 @@
   _vm_roots.oops_do<T>(oops, worker_id);
 }
 
 template <typename IsAlive, typename KeepAlive>
 void ShenandoahRootUpdater::roots_do(uint worker_id, IsAlive* is_alive, KeepAlive* keep_alive) {
-  NMethodToOopClosure update_nmethods(keep_alive, NMethodToOopClosure::FixRelocations);
   ShenandoahNMethodAndDisarmClosure nmethods_and_disarm_Cl(keep_alive);
-  NMethodToOopClosure* codes_cl = ShenandoahCodeRoots::use_nmethod_barriers_for_mark() ?
-                                  static_cast<NMethodToOopClosure*>(&nmethods_and_disarm_Cl) :
-                                  static_cast<NMethodToOopClosure*>(&update_nmethods);
+  NMethodToOopClosure* codes_cl = static_cast<NMethodToOopClosure*>(&nmethods_and_disarm_Cl);
 
   CLDToOopClosure clds(keep_alive, ClassLoaderData::_claim_strong);
 
   // Process light-weight/limited parallel roots then
   _vm_roots.oops_do(keep_alive, worker_id);
diff a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp
@@ -72,11 +72,11 @@
 void ShenandoahSTWMark::mark() {
   ShenandoahHeap* const heap = ShenandoahHeap::heap();
 
   // Arm all nmethods. Even though this is STW mark, some marking code
   // piggybacks on nmethod barriers for special instances.
-  ShenandoahCodeRoots::arm_nmethods_for_mark();
+  ShenandoahCodeRoots::arm_nmethods();
 
   // Weak reference processing
   ShenandoahReferenceProcessor* rp = _generation->ref_processor();
   rp->reset_thread_locals();
 
diff a/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.cpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.cpp
@@ -59,10 +59,11 @@
 
 ShenandoahStackWatermark::ShenandoahStackWatermark(JavaThread* jt) :
   StackWatermark(jt, StackWatermarkKind::gc, _epoch_id),
   _heap(ShenandoahHeap::heap()),
   _stats(),
+  _no_op_cl(),
   _keep_alive_cl(),
   _evac_update_oop_cl(),
   _nm_cl() {}
 
 OopClosure* ShenandoahStackWatermark::closure_from_context(void* context) {
@@ -71,48 +72,48 @@
            _heap->is_concurrent_mark_in_progress(),
            "Only these two phases");
     assert(Thread::current()->is_Worker_thread(), "Unexpected thread passing in context: " PTR_FORMAT, p2i(context));
     return reinterpret_cast<OopClosure*>(context);
   } else {
-    if (_heap->is_concurrent_weak_root_in_progress()) {
-      assert(_heap->is_evacuation_in_progress(), "Nothing to evacuate");
+    if (_heap->is_concurrent_weak_root_in_progress() && _heap->is_evacuation_in_progress()) {
       return &_evac_update_oop_cl;
     } else if (_heap->is_concurrent_mark_in_progress()) {
       return &_keep_alive_cl;
+    } else if (ShenandoahGCStateCheckHotpatch) {
+      return &_no_op_cl;
     } else {
       ShouldNotReachHere();
       return nullptr;
     }
   }
 }
 
 void ShenandoahStackWatermark::start_processing_impl(void* context) {
   NoSafepointVerifier nsv;
-  ShenandoahHeap* const heap = ShenandoahHeap::heap();
 
-  // Process the non-frame part of the thread
-  if (heap->is_concurrent_weak_root_in_progress()) {
-    assert(heap->is_evacuation_in_progress(), "Should not be armed");
+  ShenandoahHeap* const heap = ShenandoahHeap::heap();
+  if (_heap->is_concurrent_weak_root_in_progress() && heap->is_evacuation_in_progress()) {
     // Retire the TLABs, which will force threads to reacquire their TLABs.
     // This is needed for two reasons. Strong one: new allocations would be with new freeset,
     // which would be outside the collection set, so no cset writes would happen there.
     // Weaker one: new allocations would happen past update watermark, and so less work would
     // be needed for reference updates (would update the large filler instead).
     retire_tlab();
-
-    _jt->oops_do_no_frames(closure_from_context(context), &_nm_cl);
   } else if (heap->is_concurrent_mark_in_progress()) {
     // We need to reset all TLABs because they might be below the TAMS, and we need to mark
     // the objects in them. Do not let mutators allocate any new objects in their current TLABs.
     // It is also a good place to resize the TLAB sizes for future allocations.
     retire_tlab();
-
-    _jt->oops_do_no_frames(closure_from_context(context), &_nm_cl);
+  } else if (ShenandoahGCStateCheckHotpatch) {
+    // Can be here for updating barriers. No TLAB retirement is needed.
   } else {
     ShouldNotReachHere();
   }
 
+  // Process the non-frame part of the thread
+  _jt->oops_do_no_frames(closure_from_context(context), &_nm_cl);
+
   // Publishes the processing start to concurrent threads
   StackWatermark::start_processing_impl(context);
 }
 
 void ShenandoahStackWatermark::retire_tlab() {
@@ -129,9 +130,9 @@
 void ShenandoahStackWatermark::process(const frame& fr, RegisterMap& register_map, void* context) {
   OopClosure* oops = closure_from_context(context);
   assert(oops != nullptr, "Should not get to here");
   ShenandoahHeap* const heap = ShenandoahHeap::heap();
   assert((heap->is_concurrent_weak_root_in_progress() && heap->is_evacuation_in_progress()) ||
-         heap->is_concurrent_mark_in_progress(),
-         "Only these two phases");
+         heap->is_concurrent_mark_in_progress() || ShenandoahGCStateCheckHotpatch,
+         "Only these phases");
   fr.oops_do(oops, &_nm_cl, &register_map, DerivedPointerIterationMode::_directly);
 }
diff a/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.hpp b/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.hpp
--- a/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.hpp
@@ -52,10 +52,11 @@
   static uint32_t                      _epoch_id;
   ShenandoahHeap* const                _heap;
   ThreadLocalAllocStats                _stats;
 
   // Closures
+  ShenandoahNoOpClosure                _no_op_cl;
   ShenandoahKeepAliveClosure           _keep_alive_cl;
   ShenandoahEvacuateUpdateRootsClosure _evac_update_oop_cl;
   ShenandoahOnStackNMethodClosure      _nm_cl;
 public:
   ShenandoahStackWatermark(JavaThread* jt);
