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
@@ -29,12 +29,12 @@
 #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), _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 +42,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 +88,73 @@
   _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;
-    }
+    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;
+      }
+      case relocInfo::barrier_type: {
+        assert(ShenandoahGCStateCheckHotpatch, "Who emits these?");
+        barrier_Relocation* r = iter.barrier_reloc();
+
+        // TODO: Move to assembler?
+#ifdef AMD64
+        NativeInstruction* ni = nativeInstruction_at(r->addr());
+        assert(ni->is_jump(), "Initial code version: GC barrier fastpath must be a jump");
+        NativeJump* jmp = nativeJump_at(r->addr());
+
+        ShenandoahNMethodBarrier b;
+        b._barrier_pc = r->addr();
+        b._stub_addr = jmp->jump_destination();
+        // TODO: Can technically figure out which GC state we care about in this reloc.
+        // b._gc_state_fast_bit = r->format();
+        barriers.push(b);
+#else
+        Unimplemented();
+#endif
 
-    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;
+      }
+      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");
@@ -134,10 +171,69 @@
   } else {
     // There is possibility that GC is cancelled when it arrives final mark.
     // In this case, concurrent root phase is skipped and degenerated GC should be
     // followed, where nmethods are disarmed.
   }
+
+  // Update all barriers
+  data->update_barriers();
+}
+
+#ifdef AMD64
+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;
+}
+#endif
+
+void ShenandoahNMethod::update_barriers() {
+  if (!ShenandoahGCStateCheckHotpatch) return;
+
+  ShenandoahHeap* heap = ShenandoahHeap::heap();
+
+  for (int c = 0; c < _barriers_count; c++) {
+    ShenandoahNMethodBarrier& barrier = _barriers[c];
+
+    address pc = barrier._barrier_pc;
+    NativeInstruction* ni = nativeInstruction_at(pc);
+
+    // TODO: This should really be in assembler?
+ #ifdef AMD64
+    if (heap->is_idle()) {
+      // Heap is idle: insert nops
+      if (ni->is_jump()) {
+        insert_5_byte_nop(pc);
+      } else {
+        assert(is_5_byte_nop(pc), "Sanity: should already be nop at PC " PTR_FORMAT ": %02x%02x%02x%02x%02x",
+               p2i(pc), *(pc + 0), *(pc + 1), *(pc + 2), *(pc + 3), *(pc + 4));
+      }
+    } else {
+      // Heap is active: insert jumps to barrier stubs
+      if (is_5_byte_nop(pc)) {
+        NativeJump::insert(pc, barrier._stub_addr);
+      } else {
+        assert(ni->is_jump(), "Sanity: should already be jump at PC " PTR_FORMAT ": %02x%02x%02x%02x%02x",
+              p2i(pc), *(pc + 0), *(pc + 1), *(pc + 2), *(pc + 3), *(pc + 4));
+      }
+    }
+#else
+    Unimplemented();
+#endif
+  }
 }
 
 #ifdef ASSERT
 void ShenandoahNMethod::assert_correct() {
   ShenandoahHeap* heap = ShenandoahHeap::heap();
@@ -204,12 +300,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,14 +338,16 @@
     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);
+    data->update_barriers();
     assert(data != nullptr, "Sanity");
     ShenandoahNMethod::attach_gc_data(nm, data);
     ShenandoahLocker locker(&_lock);
     log_register_nmethod(nm);
     append(data);
