diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp
index bdf1e5b912857..4f71135084465 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp
@@ -35,25 +35,21 @@
 #include "gc/shenandoah/shenandoahPacer.inline.hpp"
 #include "gc/shenandoah/shenandoahUtils.hpp"
 #include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp"
+#include "gc/shenandoah/mode/shenandoahMode.hpp"
+#include "logging/log.hpp"
 #include "memory/metaspaceUtils.hpp"
 #include "memory/metaspaceStats.hpp"
-#include "memory/resourceArea.hpp"
-#include "runtime/atomic.hpp"
 
 ShenandoahControlThread::ShenandoahControlThread() :
-  ConcurrentGCThread(),
-  _alloc_failure_waiters_lock(Mutex::safepoint-2, "ShenandoahAllocFailureGC_lock", true),
-  _gc_waiters_lock(Mutex::safepoint-2, "ShenandoahRequestedGC_lock", true),
+  ShenandoahController(),
   _requested_gc_cause(GCCause::_no_cause_specified),
-  _degen_point(ShenandoahGC::_degenerated_outside_cycle),
-  _allocs_seen(0) {
+  _degen_point(ShenandoahGC::_degenerated_outside_cycle) {
   set_name("Shenandoah Control Thread");
-  reset_gc_id();
   create_and_start();
 }
 
 void ShenandoahControlThread::run_service() {
-  ShenandoahHeap* heap = ShenandoahHeap::heap();
+  ShenandoahHeap* const heap = ShenandoahHeap::heap();
 
   const GCMode default_mode = concurrent_normal;
   const GCCause::Cause default_cause = GCCause::_shenandoah_concurrent_gc;
@@ -77,7 +73,7 @@ void ShenandoahControlThread::run_service() {
     const GCCause::Cause requested_gc_cause = _requested_gc_cause;
 
     // This control loop iteration has seen this much allocation.
-    const size_t allocs_seen = Atomic::xchg(&_allocs_seen, (size_t)0, memory_order_relaxed);
+    const size_t allocs_seen = reset_allocs_seen();
 
     // Check if we have seen a new target for soft max heap size.
     const bool soft_max_changed = heap->check_soft_max_changed();
@@ -106,7 +102,6 @@ void ShenandoahControlThread::run_service() {
         policy->record_alloc_failure_to_full();
         mode = stw_full;
       }
-
     } else if (is_gc_requested) {
       cause = requested_gc_cause;
       log_info(gc)("Trigger: GC request (%s)", GCCause::to_string(cause));
@@ -239,7 +234,7 @@ void ShenandoahControlThread::run_service() {
         heap->pacer()->setup_for_idle();
       }
     } else {
-      // Allow allocators to know we have seen this much regions
+      // Report to pacer that we have seen this many words allocated
       if (ShenandoahPacing && (allocs_seen > 0)) {
         heap->pacer()->report_alloc(allocs_seen);
       }
@@ -407,88 +402,8 @@ void ShenandoahControlThread::handle_requested_gc(GCCause::Cause cause) {
   }
 }
 
-void ShenandoahControlThread::handle_alloc_failure(ShenandoahAllocRequest& req, bool block) {
-  ShenandoahHeap* heap = ShenandoahHeap::heap();
-
-  assert(current()->is_Java_thread(), "expect Java thread here");
-
-  if (try_set_alloc_failure_gc()) {
-    // Only report the first allocation failure
-    log_info(gc)("Failed to allocate %s, " SIZE_FORMAT "%s",
-                 req.type_string(),
-                 byte_size_in_proper_unit(req.size() * HeapWordSize), proper_unit_for_byte_size(req.size() * HeapWordSize));
-
-    // Now that alloc failure GC is scheduled, we can abort everything else
-    heap->cancel_gc(GCCause::_allocation_failure);
-  }
-
-
-  if (block) {
-    MonitorLocker ml(&_alloc_failure_waiters_lock);
-    while (is_alloc_failure_gc()) {
-      ml.wait();
-    }
-  }
-}
-
-void ShenandoahControlThread::handle_alloc_failure_evac(size_t words) {
-  ShenandoahHeap* heap = ShenandoahHeap::heap();
-
-  if (try_set_alloc_failure_gc()) {
-    // Only report the first allocation failure
-    log_info(gc)("Failed to allocate " SIZE_FORMAT "%s for evacuation",
-                 byte_size_in_proper_unit(words * HeapWordSize), proper_unit_for_byte_size(words * HeapWordSize));
-  }
-
-  // Forcefully report allocation failure
-  heap->cancel_gc(GCCause::_shenandoah_allocation_failure_evac);
-}
-
-void ShenandoahControlThread::notify_alloc_failure_waiters() {
-  _alloc_failure_gc.unset();
-  MonitorLocker ml(&_alloc_failure_waiters_lock);
-  ml.notify_all();
-}
-
-bool ShenandoahControlThread::try_set_alloc_failure_gc() {
-  return _alloc_failure_gc.try_set();
-}
-
-bool ShenandoahControlThread::is_alloc_failure_gc() {
-  return _alloc_failure_gc.is_set();
-}
-
 void ShenandoahControlThread::notify_gc_waiters() {
   _gc_requested.unset();
   MonitorLocker ml(&_gc_waiters_lock);
   ml.notify_all();
 }
-
-void ShenandoahControlThread::pacing_notify_alloc(size_t words) {
-  assert(ShenandoahPacing, "should only call when pacing is enabled");
-  Atomic::add(&_allocs_seen, words, memory_order_relaxed);
-}
-
-void ShenandoahControlThread::reset_gc_id() {
-  Atomic::store(&_gc_id, (size_t)0);
-}
-
-void ShenandoahControlThread::update_gc_id() {
-  Atomic::inc(&_gc_id);
-}
-
-size_t ShenandoahControlThread::get_gc_id() {
-  return Atomic::load(&_gc_id);
-}
-
-void ShenandoahControlThread::start() {
-  create_and_start();
-}
-
-void ShenandoahControlThread::prepare_for_graceful_shutdown() {
-  _graceful_shutdown.set();
-}
-
-bool ShenandoahControlThread::in_graceful_shutdown() {
-  return _graceful_shutdown.is_set();
-}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp
index 9da25b1a73ced..be99ef1d865fb 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp
@@ -28,10 +28,11 @@
 #include "gc/shared/gcCause.hpp"
 #include "gc/shared/concurrentGCThread.hpp"
 #include "gc/shenandoah/shenandoahGC.hpp"
+#include "gc/shenandoah/shenandoahController.hpp"
 #include "gc/shenandoah/shenandoahPadding.hpp"
 #include "gc/shenandoah/shenandoahSharedVariables.hpp"
 
-class ShenandoahControlThread: public ConcurrentGCThread {
+class ShenandoahControlThread: public ShenandoahController {
   friend class VMStructs;
 
 private:
@@ -42,68 +43,30 @@ class ShenandoahControlThread: public ConcurrentGCThread {
     stw_full
   } GCMode;
 
-  // While we could have a single lock for these, it may risk unblocking
-  // GC waiters when alloc failure GC cycle finishes. We want instead
-  // to make complete explicit cycle for for demanding customers.
-  Monitor _alloc_failure_waiters_lock;
-  Monitor _gc_waiters_lock;
-
-public:
-  void run_service();
-  void stop_service();
-
-private:
   ShenandoahSharedFlag _gc_requested;
-  ShenandoahSharedFlag _alloc_failure_gc;
-  ShenandoahSharedFlag _graceful_shutdown;
   GCCause::Cause       _requested_gc_cause;
   ShenandoahGC::ShenandoahDegenPoint _degen_point;
 
-  shenandoah_padding(0);
-  volatile size_t _allocs_seen;
-  shenandoah_padding(1);
-  volatile size_t _gc_id;
-  shenandoah_padding(2);
+public:
+  ShenandoahControlThread();
+
+  void run_service() override;
+  void stop_service() override;
+
+  void request_gc(GCCause::Cause cause) override;
+
+private:
 
   bool check_cancellation_or_degen(ShenandoahGC::ShenandoahDegenPoint point);
   void service_concurrent_normal_cycle(GCCause::Cause cause);
   void service_stw_full_cycle(GCCause::Cause cause);
   void service_stw_degenerated_cycle(GCCause::Cause cause, ShenandoahGC::ShenandoahDegenPoint point);
 
-  bool try_set_alloc_failure_gc();
-  void notify_alloc_failure_waiters();
-  bool is_alloc_failure_gc();
-
-  void reset_gc_id();
-  void update_gc_id();
-  size_t get_gc_id();
-
   void notify_gc_waiters();
 
   // Handle GC request.
   // Blocks until GC is over.
   void handle_requested_gc(GCCause::Cause cause);
-
-public:
-  // Constructor
-  ShenandoahControlThread();
-
-  // Handle allocation failure from a mutator allocation.
-  // Optionally blocks while collector is handling the failure. If the GC
-  // threshold has been exceeded, the mutator allocation will not block so
-  // that the out of memory error can be raised promptly.
-  void handle_alloc_failure(ShenandoahAllocRequest& req, bool block = true);
-
-  // Handle allocation failure from evacuation path.
-  void handle_alloc_failure_evac(size_t words);
-
-  void request_gc(GCCause::Cause cause);
-
-  void pacing_notify_alloc(size_t words);
-
-  void start();
-  void prepare_for_graceful_shutdown();
-  bool in_graceful_shutdown();
 };
 
 #endif // SHARE_GC_SHENANDOAH_SHENANDOAHCONTROLTHREAD_HPP
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahController.cpp b/src/hotspot/share/gc/shenandoah/shenandoahController.cpp
new file mode 100644
index 0000000000000..6d6d21c406623
--- /dev/null
+++ b/src/hotspot/share/gc/shenandoah/shenandoahController.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+#include "precompiled.hpp"
+
+#include "gc/shared/gc_globals.hpp"
+#include "gc/shenandoah/shenandoahController.hpp"
+#include "gc/shenandoah/shenandoahHeap.hpp"
+#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp"
+
+void ShenandoahController::pacing_notify_alloc(size_t words) {
+  assert(ShenandoahPacing, "should only call when pacing is enabled");
+  Atomic::add(&_allocs_seen, words, memory_order_relaxed);
+}
+
+size_t ShenandoahController::reset_allocs_seen() {
+  return Atomic::xchg(&_allocs_seen, (size_t)0, memory_order_relaxed);
+}
+
+void ShenandoahController::prepare_for_graceful_shutdown() {
+  _graceful_shutdown.set();
+}
+
+bool ShenandoahController::in_graceful_shutdown() {
+  return _graceful_shutdown.is_set();
+}
+
+void ShenandoahController::update_gc_id() {
+  Atomic::inc(&_gc_id);
+}
+
+size_t ShenandoahController::get_gc_id() {
+  return Atomic::load(&_gc_id);
+}
+
+void ShenandoahController::handle_alloc_failure(ShenandoahAllocRequest& req, bool block) {
+  ShenandoahHeap* heap = ShenandoahHeap::heap();
+
+  assert(current()->is_Java_thread(), "expect Java thread here");
+  bool is_humongous = req.size() > ShenandoahHeapRegion::humongous_threshold_words();
+
+  if (try_set_alloc_failure_gc(is_humongous)) {
+    // Only report the first allocation failure
+    log_info(gc)("Failed to allocate %s, " SIZE_FORMAT "%s",
+                 req.type_string(),
+                 byte_size_in_proper_unit(req.size() * HeapWordSize), proper_unit_for_byte_size(req.size() * HeapWordSize));
+
+    // Now that alloc failure GC is scheduled, we can abort everything else
+    heap->cancel_gc(GCCause::_allocation_failure);
+  }
+
+
+  if (block) {
+    MonitorLocker ml(&_alloc_failure_waiters_lock);
+    while (is_alloc_failure_gc()) {
+      ml.wait();
+    }
+  }
+}
+
+void ShenandoahController::handle_alloc_failure_evac(size_t words) {
+  ShenandoahHeap* heap = ShenandoahHeap::heap();
+  bool is_humongous = (words > ShenandoahHeapRegion::region_size_words());
+
+  if (try_set_alloc_failure_gc(is_humongous)) {
+    // Only report the first allocation failure
+    log_info(gc)("Failed to allocate " SIZE_FORMAT "%s for evacuation",
+                 byte_size_in_proper_unit(words * HeapWordSize), proper_unit_for_byte_size(words * HeapWordSize));
+  }
+
+  // Forcefully report allocation failure
+  heap->cancel_gc(GCCause::_shenandoah_allocation_failure_evac);
+}
+
+void ShenandoahController::notify_alloc_failure_waiters() {
+  _alloc_failure_gc.unset();
+  _humongous_alloc_failure_gc.unset();
+  MonitorLocker ml(&_alloc_failure_waiters_lock);
+  ml.notify_all();
+}
+
+bool ShenandoahController::try_set_alloc_failure_gc(bool is_humongous) {
+  if (is_humongous) {
+    _humongous_alloc_failure_gc.try_set();
+  }
+  return _alloc_failure_gc.try_set();
+}
+
+bool ShenandoahController::is_alloc_failure_gc() {
+  return _alloc_failure_gc.is_set();
+}
+
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahController.hpp b/src/hotspot/share/gc/shenandoah/shenandoahController.hpp
new file mode 100644
index 0000000000000..2808f30b44459
--- /dev/null
+++ b/src/hotspot/share/gc/shenandoah/shenandoahController.hpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef LINUX_X86_64_SERVER_SLOWDEBUG_SHENANDOAHCONTROLLER_HPP
+#define LINUX_X86_64_SERVER_SLOWDEBUG_SHENANDOAHCONTROLLER_HPP
+
+#include "gc/shared/gcCause.hpp"
+#include "gc/shared/concurrentGCThread.hpp"
+#include "gc/shenandoah/shenandoahAllocRequest.hpp"
+#include "gc/shenandoah/shenandoahSharedVariables.hpp"
+
+/**
+ * This interface exposes methods necessary for the heap to interact
+ * with the threads responsible for driving the collection cycle.
+ */
+class ShenandoahController: public ConcurrentGCThread {
+private:
+  ShenandoahSharedFlag _graceful_shutdown;
+
+  shenandoah_padding(0);
+  volatile size_t _allocs_seen;
+  shenandoah_padding(1);
+  volatile size_t _gc_id;
+  shenandoah_padding(2);
+
+protected:
+  ShenandoahSharedFlag _alloc_failure_gc;
+  ShenandoahSharedFlag _humongous_alloc_failure_gc;
+
+  // While we could have a single lock for these, it may risk unblocking
+  // GC waiters when alloc failure GC cycle finishes. We want instead
+  // to make complete explicit cycle for demanding customers.
+  Monitor _alloc_failure_waiters_lock;
+  Monitor _gc_waiters_lock;
+
+public:
+  ShenandoahController():
+    ConcurrentGCThread(),
+    _allocs_seen(0),
+    _gc_id(0),
+    _alloc_failure_waiters_lock(Mutex::safepoint-2, "ShenandoahAllocFailureGC_lock", true),
+    _gc_waiters_lock(Mutex::safepoint-2, "ShenandoahRequestedGC_lock", true)
+  { }
+
+  // Request a collection cycle. This handles "explicit" gc requests
+  // like System.gc and "implicit" gc requests, like metaspace oom.
+  virtual void request_gc(GCCause::Cause cause) = 0;
+
+  // This cancels the collection cycle and has an option to block
+  // until another cycle runs and clears the alloc failure gc flag.
+  void handle_alloc_failure(ShenandoahAllocRequest& req, bool block);
+
+  // Invoked for allocation failures during evacuation. This cancels
+  // the collection cycle without blocking.
+  void handle_alloc_failure_evac(size_t words);
+
+  // Return true if setting the flag which indicates allocation failure succeeds.
+  bool try_set_alloc_failure_gc(bool is_humongous);
+
+  // Notify threads waiting for GC to complete.
+  void notify_alloc_failure_waiters();
+
+  // True if allocation failure flag has been set.
+  bool is_alloc_failure_gc();
+
+  // This is called for every allocation. The control thread accumulates
+  // this value when idle. During the gc cycle, the control resets it
+  // and reports it to the pacer.
+  void pacing_notify_alloc(size_t words);
+  size_t reset_allocs_seen();
+
+  // These essentially allows to cancel a collection cycle for the
+  // purpose of shutting down the JVM, without trying to start a degenerated
+  // cycle.
+  void prepare_for_graceful_shutdown();
+  bool in_graceful_shutdown();
+
+
+  // Returns the internal gc count used by the control thread. Probably
+  // doesn't need to be exposed.
+  size_t get_gc_id();
+  void update_gc_id();
+};
+#endif //LINUX_X86_64_SERVER_SLOWDEBUG_SHENANDOAHCONTROLLER_HPP
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
index f270e16f53eb9..ee3f4e7d8eb8c 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
@@ -955,7 +955,7 @@ HeapWord* ShenandoahHeap::allocate_memory(ShenandoahAllocRequest& req) {
     size_t original_count = shenandoah_policy()->full_gc_count();
     while (result == nullptr
         && (get_gc_no_progress_count() == 0 || original_count == shenandoah_policy()->full_gc_count())) {
-      control_thread()->handle_alloc_failure(req);
+      control_thread()->handle_alloc_failure(req, true);
       result = allocate_memory_under_lock(req, in_new_region);
     }