diff a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp
--- /dev/null
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp
@@ -0,0 +1,284 @@
+/*
+ * 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/shenandoah/shenandoahAsserts.hpp"
+#include "gc/shenandoah/shenandoahFreeSet.hpp"
+#include "gc/shenandoah/shenandoahGenerationalEvacuationTask.hpp"
+#include "gc/shenandoah/shenandoahGenerationalHeap.hpp"
+#include "gc/shenandoah/shenandoahHeap.inline.hpp"
+#include "gc/shenandoah/shenandoahOldGeneration.hpp"
+#include "gc/shenandoah/shenandoahPacer.hpp"
+#include "gc/shenandoah/shenandoahScanRemembered.inline.hpp"
+#include "gc/shenandoah/shenandoahYoungGeneration.hpp"
+#include "gc/shenandoah/shenandoahUtils.hpp"
+
+class ShenandoahConcurrentEvacuator : public ObjectClosure {
+private:
+  ShenandoahGenerationalHeap* const _heap;
+  Thread* const _thread;
+public:
+  explicit ShenandoahConcurrentEvacuator(ShenandoahGenerationalHeap* heap) :
+          _heap(heap), _thread(Thread::current()) {}
+
+  void do_object(oop p) override {
+    shenandoah_assert_marked(nullptr, p);
+    if (!p->is_forwarded()) {
+      _heap->evacuate_object(p, _thread);
+    }
+  }
+};
+
+ShenandoahGenerationalEvacuationTask::ShenandoahGenerationalEvacuationTask(ShenandoahGenerationalHeap* heap,
+                                                                           ShenandoahRegionIterator* iterator,
+                                                                           bool concurrent) :
+  WorkerTask("Shenandoah Evacuation"),
+  _heap(heap),
+  _regions(iterator),
+  _concurrent(concurrent),
+  _tenuring_threshold(0)
+{
+  shenandoah_assert_generational();
+  _tenuring_threshold = _heap->age_census()->tenuring_threshold();
+}
+
+void ShenandoahGenerationalEvacuationTask::work(uint worker_id) {
+  if (_concurrent) {
+    ShenandoahConcurrentWorkerSession worker_session(worker_id);
+    ShenandoahSuspendibleThreadSetJoiner stsj;
+    ShenandoahEvacOOMScope oom_evac_scope;
+    do_work();
+  } else {
+    ShenandoahParallelWorkerSession worker_session(worker_id);
+    ShenandoahEvacOOMScope oom_evac_scope;
+    do_work();
+  }
+}
+
+void ShenandoahGenerationalEvacuationTask::do_work() {
+  ShenandoahConcurrentEvacuator cl(_heap);
+  ShenandoahHeapRegion* r;
+
+  while ((r = _regions->next()) != nullptr) {
+    log_debug(gc)("GenerationalEvacuationTask do_work(), looking at %s region " SIZE_FORMAT ", (age: %d) [%s, %s, %s]",
+            r->is_old()? "old": r->is_young()? "young": "free", r->index(), r->age(),
+            r->is_active()? "active": "inactive",
+            r->is_humongous()? (r->is_humongous_start()? "humongous_start": "humongous_continuation"): "regular",
+            r->is_cset()? "cset": "not-cset");
+
+    if (r->is_cset()) {
+      assert(r->has_live(), "Region " SIZE_FORMAT " should have been reclaimed early", r->index());
+      _heap->marked_object_iterate(r, &cl);
+      if (ShenandoahPacing) {
+        _heap->pacer()->report_evac(r->used() >> LogHeapWordSize);
+      }
+    } else if (r->is_young() && r->is_active() && (r->age() >= _tenuring_threshold)) {
+      if (r->is_humongous_start()) {
+        // We promote humongous_start regions along with their affiliated continuations during evacuation rather than
+        // doing this work during a safepoint.  We cannot put humongous regions into the collection set because that
+        // triggers the load-reference barrier (LRB) to copy on reference fetch.
+        promote_humongous(r);
+      } else if (r->is_regular() && (r->get_top_before_promote() != nullptr)) {
+        // Likewise, we cannot put promote-in-place regions into the collection set because that would also trigger
+        // the LRB to copy on reference fetch.
+        promote_in_place(r);
+      }
+      // Aged humongous continuation regions are handled with their start region.  If an aged regular region has
+      // more garbage than ShenandoahOldGarbageThreshold, we'll promote by evacuation.  If there is room for evacuation
+      // in this cycle, the region will be in the collection set.  If there is not room, the region will be promoted
+      // by evacuation in some future GC cycle.
+
+      // If an aged regular region has received allocations during the current cycle, we do not promote because the
+      // newly allocated objects do not have appropriate age; this region's age will be reset to zero at end of cycle.
+    }
+    // else, region is free, or OLD, or not in collection set, or humongous_continuation,
+    // or is young humongous_start that is too young to be promoted
+    if (_heap->check_cancelled_gc_and_yield(_concurrent)) {
+      break;
+    }
+  }
+}
+
+// When we promote a region in place, we can continue to use the established marking context to guide subsequent remembered
+// set scans of this region's content.  The region will be coalesced and filled prior to the next old-gen marking effort.
+// We identify the entirety of the region as DIRTY to force the next remembered set scan to identify the "interesting poitners"
+// contained herein.
+void ShenandoahGenerationalEvacuationTask::promote_in_place(ShenandoahHeapRegion* region) {
+  ShenandoahMarkingContext* const marking_context = _heap->marking_context();
+  HeapWord* const tams = marking_context->top_at_mark_start(region);
+
+  {
+    const size_t old_garbage_threshold = (ShenandoahHeapRegion::region_size_bytes() * ShenandoahOldGarbageThreshold) / 100;
+    assert(_heap->active_generation()->is_mark_complete(), "sanity");
+    assert(!_heap->is_concurrent_old_mark_in_progress(), "Cannot promote in place during old marking");
+    assert(region->garbage_before_padded_for_promote() < old_garbage_threshold, "Region " SIZE_FORMAT " has too much garbage for promotion", region->index());
+    assert(region->is_young(), "Only young regions can be promoted");
+    assert(region->is_regular(), "Use different service to promote humongous regions");
+    assert(region->age() >= _heap->age_census()->tenuring_threshold(), "Only promote regions that are sufficiently aged");
+    assert(region->get_top_before_promote() == tams, "Region " SIZE_FORMAT " has been used for allocations before promotion", region->index());
+  }
+
+  // Rebuild the remembered set information and mark the entire range as DIRTY.  We do NOT scan the content of this
+  // range to determine which cards need to be DIRTY.  That would force us to scan the region twice, once now, and
+  // once during the subsequent remembered set scan.  Instead, we blindly (conservatively) mark everything as DIRTY
+  // now and then sort out the CLEAN pages during the next remembered set scan.
+  //
+  // Rebuilding the remembered set consists of clearing all object registrations (reset_object_range()) here,
+  // then registering every live object and every coalesced range of free objects in the loop that follows.
+  _heap->card_scan()->reset_object_range(region->bottom(), region->end());
+  _heap->card_scan()->mark_range_as_dirty(region->bottom(), region->get_top_before_promote() - region->bottom());
+
+  // TODO: use an existing coalesce-and-fill function rather than replicating the code here.
+  HeapWord* obj_addr = region->bottom();
+  while (obj_addr < tams) {
+    oop obj = cast_to_oop(obj_addr);
+    if (marking_context->is_marked(obj)) {
+      assert(obj->klass() != nullptr, "klass should not be NULL");
+      // This thread is responsible for registering all objects in this region.  No need for lock.
+      _heap->card_scan()->register_object_without_lock(obj_addr);
+      obj_addr += obj->size();
+    } else {
+      HeapWord* next_marked_obj = marking_context->get_next_marked_addr(obj_addr, tams);
+      assert(next_marked_obj <= tams, "next marked object cannot exceed tams");
+      size_t fill_size = next_marked_obj - obj_addr;
+      assert(fill_size >= ShenandoahHeap::min_fill_size(), "previously allocated objects known to be larger than min_size");
+      ShenandoahHeap::fill_with_object(obj_addr, fill_size);
+      _heap->card_scan()->register_object_without_lock(obj_addr);
+      obj_addr = next_marked_obj;
+    }
+  }
+  // We do not need to scan above TAMS because restored top equals tams
+  assert(obj_addr == tams, "Expect loop to terminate when obj_addr equals tams");
+
+  ShenandoahOldGeneration* const old_gen = _heap->old_generation();
+  ShenandoahYoungGeneration* const young_gen = _heap->young_generation();
+
+  {
+    ShenandoahHeapLocker locker(_heap->lock());
+
+    HeapWord* update_watermark = region->get_update_watermark();
+
+    // Now that this region is affiliated with old, we can allow it to receive allocations, though it may not be in the
+    // is_collector_free range.
+    region->restore_top_before_promote();
+
+    size_t region_used = region->used();
+
+    // The update_watermark was likely established while we had the artificially high value of top.  Make it sane now.
+    assert(update_watermark >= region->top(), "original top cannot exceed preserved update_watermark");
+    region->set_update_watermark(region->top());
+
+    // Unconditionally transfer one region from young to old. This represents the newly promoted region.
+    // This expands old and shrinks new by the size of one region.  Strictly, we do not "need" to expand old
+    // if there are already enough unaffiliated regions in old to account for this newly promoted region.
+    // However, if we do not transfer the capacities, we end up reducing the amount of memory that would have
+    // otherwise been available to hold old evacuations, because old available is max_capacity - used and now
+    // we would be trading a fully empty region for a partially used region.
+    young_gen->decrease_used(region_used);
+    young_gen->decrement_affiliated_region_count();
+
+    // transfer_to_old() increases capacity of old and decreases capacity of young
+    _heap->generation_sizer()->force_transfer_to_old(1);
+    region->set_affiliation(OLD_GENERATION);
+
+    old_gen->increment_affiliated_region_count();
+    old_gen->increase_used(region_used);
+
+    // add_old_collector_free_region() increases promoted_reserve() if available space exceeds plab_min_size()
+    _heap->free_set()->add_old_collector_free_region(region);
+  }
+}
+
+void ShenandoahGenerationalEvacuationTask::promote_humongous(ShenandoahHeapRegion* region) {
+  ShenandoahMarkingContext* marking_context = _heap->marking_context();
+  oop obj = cast_to_oop(region->bottom());
+  assert(_heap->active_generation()->is_mark_complete(), "sanity");
+  assert(region->is_young(), "Only young regions can be promoted");
+  assert(region->is_humongous_start(), "Should not promote humongous continuation in isolation");
+  assert(region->age() >= _heap->age_census()->tenuring_threshold(), "Only promote regions that are sufficiently aged");
+  assert(marking_context->is_marked(obj), "promoted humongous object should be alive");
+
+  // TODO: Consider not promoting humongous objects that represent primitive arrays.  Leaving a primitive array
+  // (obj->is_typeArray()) in young-gen is harmless because these objects are never relocated and they are not
+  // scanned.  Leaving primitive arrays in young-gen memory allows their memory to be reclaimed more quickly when
+  // it becomes garbage.  Better to not make this change until sizes of young-gen and old-gen are completely
+  // adaptive, as leaving primitive arrays in young-gen might be perceived as an "astonishing result" by someone
+  // has carefully analyzed the required sizes of an application's young-gen and old-gen.
+  const size_t used_bytes = obj->size() * HeapWordSize;
+  const size_t spanned_regions = ShenandoahHeapRegion::required_regions(used_bytes);
+  const size_t humongous_waste = spanned_regions * ShenandoahHeapRegion::region_size_bytes() - obj->size() * HeapWordSize;
+  const size_t index_limit = region->index() + spanned_regions;
+
+  ShenandoahGeneration* const old_generation = _heap->old_generation();
+  ShenandoahGeneration* const young_generation = _heap->young_generation();
+  {
+    // We need to grab the heap lock in order to avoid a race when changing the affiliations of spanned_regions from
+    // young to old.
+    ShenandoahHeapLocker locker(_heap->lock());
+
+    // We promote humongous objects unconditionally, without checking for availability.  We adjust
+    // usage totals, including humongous waste, after evacuation is done.
+    log_debug(gc)("promoting humongous region " SIZE_FORMAT ", spanning " SIZE_FORMAT, region->index(), spanned_regions);
+
+    young_generation->decrease_used(used_bytes);
+    young_generation->decrease_humongous_waste(humongous_waste);
+    young_generation->decrease_affiliated_region_count(spanned_regions);
+
+    // transfer_to_old() increases capacity of old and decreases capacity of young
+    _heap->generation_sizer()->force_transfer_to_old(spanned_regions);
+
+    // For this region and each humongous continuation region spanned by this humongous object, change
+    // affiliation to OLD_GENERATION and adjust the generation-use tallies.  The remnant of memory
+    // in the last humongous region that is not spanned by obj is currently not used.
+    for (size_t i = region->index(); i < index_limit; i++) {
+      ShenandoahHeapRegion* r = _heap->get_region(i);
+      log_debug(gc)("promoting humongous region " SIZE_FORMAT ", from " PTR_FORMAT " to " PTR_FORMAT,
+              r->index(), p2i(r->bottom()), p2i(r->top()));
+      // We mark the entire humongous object's range as dirty after loop terminates, so no need to dirty the range here
+      r->set_affiliation(OLD_GENERATION);
+    }
+
+    old_generation->increase_affiliated_region_count(spanned_regions);
+    old_generation->increase_used(used_bytes);
+    old_generation->increase_humongous_waste(humongous_waste);
+  }
+
+  // Since this region may have served previously as OLD, it may hold obsolete object range info.
+  HeapWord* const humongous_bottom = region->bottom();
+  _heap->card_scan()->reset_object_range(humongous_bottom, humongous_bottom + spanned_regions * ShenandoahHeapRegion::region_size_words());
+  // Since the humongous region holds only one object, no lock is necessary for this register_object() invocation.
+  _heap->card_scan()->register_object_without_lock(humongous_bottom);
+
+  if (obj->is_typeArray()) {
+    // Primitive arrays don't need to be scanned.
+    log_debug(gc)("Clean cards for promoted humongous object (Region " SIZE_FORMAT ") from " PTR_FORMAT " to " PTR_FORMAT,
+            region->index(), p2i(humongous_bottom), p2i(humongous_bottom + obj->size()));
+    _heap->card_scan()->mark_range_as_clean(humongous_bottom, obj->size());
+  } else {
+    log_debug(gc)("Dirty cards for promoted humongous object (Region " SIZE_FORMAT ") from " PTR_FORMAT " to " PTR_FORMAT,
+            region->index(), p2i(humongous_bottom), p2i(humongous_bottom + obj->size()));
+    _heap->card_scan()->mark_range_as_dirty(humongous_bottom, obj->size());
+  }
+}
