/*
 * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#include "classfile/classLoaderDataGraph.hpp"
#include "code/nmethod.hpp"
#include "gc/shared/classUnloadingContext.hpp"
#include "gc/shared/gcLocker.hpp"
#include "gc/shared/gcVMOperations.hpp"
#include "gc/shared/isGCActiveMark.hpp"
#include "gc/shared/suspendibleThreadSet.hpp"
#include "gc/z/zAllocator.inline.hpp"
#include "gc/z/zBarrierSet.hpp"
#include "gc/z/zBarrierSetAssembler.hpp"
#include "gc/z/zBarrierSetNMethod.hpp"
#include "gc/z/zBreakpoint.hpp"
#include "gc/z/zCollectedHeap.hpp"
#include "gc/z/zDriver.hpp"
#include "gc/z/zForwarding.hpp"
#include "gc/z/zForwardingTable.inline.hpp"
#include "gc/z/zGeneration.inline.hpp"
#include "gc/z/zHeap.inline.hpp"
#include "gc/z/zJNICritical.hpp"
#include "gc/z/zMark.inline.hpp"
#include "gc/z/zPageAge.inline.hpp"
#include "gc/z/zPageAllocator.hpp"
#include "gc/z/zRelocationSet.inline.hpp"
#include "gc/z/zRelocationSetSelector.inline.hpp"
#include "gc/z/zRemembered.hpp"
#include "gc/z/zRootsIterator.hpp"
#include "gc/z/zStat.hpp"
#include "gc/z/zTask.hpp"
#include "gc/z/zUncoloredRoot.inline.hpp"
#include "gc/z/zVerify.hpp"
#include "gc/z/zWorkers.hpp"
#include "interpreter/oopMapCache.hpp"
#include "logging/log.hpp"
#include "memory/universe.hpp"
#include "prims/jvmtiTagMap.hpp"
#include "runtime/atomic.hpp"
#include "runtime/continuation.hpp"
#include "runtime/handshake.hpp"
#include "runtime/safepoint.hpp"
#include "runtime/threads.hpp"
#include "runtime/vmOperations.hpp"
#include "runtime/vmThread.hpp"
#include "utilities/debug.hpp"
#include "utilities/events.hpp"

static const ZStatPhaseGeneration ZPhaseGenerationYoung[] {
  ZStatPhaseGeneration("Young Generation", ZGenerationId::young),
  ZStatPhaseGeneration("Young Generation (Promote All)", ZGenerationId::young),
  ZStatPhaseGeneration("Young Generation (Collect Roots)", ZGenerationId::young),
  ZStatPhaseGeneration("Young Generation", ZGenerationId::young)
};

static const ZStatPhaseGeneration ZPhaseGenerationOld("Old Generation", ZGenerationId::old);

static const ZStatPhasePause      ZPhasePauseMarkStartYoung("Pause Mark Start", ZGenerationId::young);
static const ZStatPhasePause      ZPhasePauseMarkStartYoungAndOld("Pause Mark Start (Major)", ZGenerationId::young);
static const ZStatPhaseConcurrent ZPhaseConcurrentMarkYoung("Concurrent Mark", ZGenerationId::young);
static const ZStatPhaseConcurrent ZPhaseConcurrentMarkContinueYoung("Concurrent Mark Continue", ZGenerationId::young);
static const ZStatPhasePause      ZPhasePauseMarkEndYoung("Pause Mark End", ZGenerationId::young);
static const ZStatPhaseConcurrent ZPhaseConcurrentMarkFreeYoung("Concurrent Mark Free", ZGenerationId::young);
static const ZStatPhaseConcurrent ZPhaseConcurrentResetRelocationSetYoung("Concurrent Reset Relocation Set", ZGenerationId::young);
static const ZStatPhaseConcurrent ZPhaseConcurrentSelectRelocationSetYoung("Concurrent Select Relocation Set", ZGenerationId::young);
static const ZStatPhasePause      ZPhasePauseRelocateStartYoung("Pause Relocate Start", ZGenerationId::young);
static const ZStatPhaseConcurrent ZPhaseConcurrentRelocatedYoung("Concurrent Relocate", ZGenerationId::young);

static const ZStatPhaseConcurrent ZPhaseConcurrentMarkOld("Concurrent Mark", ZGenerationId::old);
static const ZStatPhaseConcurrent ZPhaseConcurrentMarkContinueOld("Concurrent Mark Continue", ZGenerationId::old);
static const ZStatPhasePause      ZPhasePauseMarkEndOld("Pause Mark End", ZGenerationId::old);
static const ZStatPhaseConcurrent ZPhaseConcurrentMarkFreeOld("Concurrent Mark Free", ZGenerationId::old);
static const ZStatPhaseConcurrent ZPhaseConcurrentProcessNonStrongOld("Concurrent Process Non-Strong", ZGenerationId::old);
static const ZStatPhaseConcurrent ZPhaseConcurrentResetRelocationSetOld("Concurrent Reset Relocation Set", ZGenerationId::old);
static const ZStatPhaseConcurrent ZPhaseConcurrentSelectRelocationSetOld("Concurrent Select Relocation Set", ZGenerationId::old);
static const ZStatPhasePause      ZPhasePauseRelocateStartOld("Pause Relocate Start", ZGenerationId::old);
static const ZStatPhaseConcurrent ZPhaseConcurrentRelocatedOld("Concurrent Relocate", ZGenerationId::old);
static const ZStatPhaseConcurrent ZPhaseConcurrentRemapRootsOld("Concurrent Remap Roots", ZGenerationId::old);

static const ZStatSubPhase ZSubPhaseConcurrentMarkRootsYoung("Concurrent Mark Roots", ZGenerationId::young);
static const ZStatSubPhase ZSubPhaseConcurrentMarkFollowYoung("Concurrent Mark Follow", ZGenerationId::young);

static const ZStatSubPhase ZSubPhaseConcurrentMarkRootsOld("Concurrent Mark Roots", ZGenerationId::old);
static const ZStatSubPhase ZSubPhaseConcurrentMarkFollowOld("Concurrent Mark Follow", ZGenerationId::old);
static const ZStatSubPhase ZSubPhaseConcurrentRemapRootsColoredOld("Concurrent Remap Roots Colored", ZGenerationId::old);
static const ZStatSubPhase ZSubPhaseConcurrentRemapRootsUncoloredOld("Concurrent Remap Roots Uncolored", ZGenerationId::old);
static const ZStatSubPhase ZSubPhaseConcurrentRemapRememberedOld("Concurrent Remap Remembered", ZGenerationId::old);

static const ZStatSampler ZSamplerJavaThreads("System", "Java Threads", ZStatUnitThreads);

ZGenerationYoung* ZGeneration::_young;
ZGenerationOld*   ZGeneration::_old;

ZGeneration::ZGeneration(ZGenerationId id, ZPageTable* page_table, ZPageAllocator* page_allocator)
  : _id(id),
    _page_allocator(page_allocator),
    _page_table(page_table),
    _forwarding_table(),
    _workers(id, &_stat_workers),
    _mark(this, page_table),
    _relocate(this),
    _relocation_set(this),
    _freed(0),
    _promoted(0),
    _compacted(0),
    _phase(Phase::Relocate),
    _seqnum(1),
    _stat_heap(),
    _stat_cycle(),
    _stat_workers(),
    _stat_mark(),
    _stat_relocation(),
    _gc_timer(nullptr) {}

ZWorkers* ZGeneration::workers() {
  return &_workers;
}

uint ZGeneration::active_workers() const {
  return _workers.active_workers();
}

void ZGeneration::set_active_workers(uint nworkers) {
  _workers.set_active_workers(nworkers);
}

void ZGeneration::threads_do(ThreadClosure* tc) const {
  _workers.threads_do(tc);
}

void ZGeneration::mark_flush(Thread* thread) {
  _mark.flush(thread);
}

void ZGeneration::mark_free() {
   _mark.free();
}

void ZGeneration::free_empty_pages(ZRelocationSetSelector* selector, int bulk) {
  // Freeing empty pages in bulk is an optimization to avoid grabbing
  // the page allocator lock, and trying to satisfy stalled allocations
  // too frequently.
  if (selector->should_free_empty_pages(bulk)) {
    const size_t freed = ZHeap::heap()->free_empty_pages(_id, selector->empty_pages());
    increase_freed(freed);
    selector->clear_empty_pages();
  }
}

void ZGeneration::flip_age_pages(const ZRelocationSetSelector* selector) {
  if (is_young()) {
    _relocate.flip_age_pages(selector->not_selected_small());
    _relocate.flip_age_pages(selector->not_selected_medium());
    _relocate.flip_age_pages(selector->not_selected_large());
  }
}

static double fragmentation_limit(ZGenerationId generation) {
  if (generation == ZGenerationId::old) {
    return ZFragmentationLimit;
  } else {
    return ZYoungCompactionLimit;
  }
}

void ZGeneration::select_relocation_set(bool promote_all) {
  // Register relocatable pages with selector
  ZRelocationSetSelector selector(fragmentation_limit(_id));
  {
    ZGenerationPagesIterator pt_iter(_page_table, _id, _page_allocator);
    for (ZPage* page; pt_iter.next(&page);) {
      if (!page->is_relocatable()) {
        // Not relocatable, don't register
        continue;
      }

      if (page->is_marked()) {
        // Register live page
        selector.register_live_page(page);
      } else {
        // Register empty page
        selector.register_empty_page(page);

        // Reclaim empty pages in bulk

        // An active iterator blocks immediate deletion of pages. The intent is
        // to allow the code that iterates over pages to safely read properties
        // of the pages without them being freed/deleted. However, this
        // function both iterates over the pages AND frees them. We "yield" the
        // iterator, so that we can perform immediate deletion (as long as no
        // other thread is iterating over the pages). The contract is that the
        // pages that are about to be freed are "owned" by this thread, and no
        // other thread will change their states.
        pt_iter.yield([&]() {
          free_empty_pages(&selector, 64 /* bulk */);
        });
      }
    }

    // Reclaim remaining empty pages
    free_empty_pages(&selector, 0 /* bulk */);
  }

  // Select relocation set
  selector.select();

  // Selecting tenuring threshold must be done after select
  // which produces the liveness data, but before install,
  // which consumes the tenuring threshold.
  if (is_young()) {
    ZGeneration::young()->select_tenuring_threshold(selector.stats(), promote_all);
  }

  // Install relocation set
  _relocation_set.install(&selector);

  // Flip age young pages that were not selected
  flip_age_pages(&selector);

  // Setup forwarding table
  ZRelocationSetIterator rs_iter(&_relocation_set);
  for (ZForwarding* forwarding; rs_iter.next(&forwarding);) {
    _forwarding_table.insert(forwarding);
  }

  // Update statistics
  stat_relocation()->at_select_relocation_set(selector.stats());
  stat_heap()->at_select_relocation_set(selector.stats());
}

ZRelocationSetParallelIterator ZGeneration::relocation_set_parallel_iterator() {
  return ZRelocationSetParallelIterator(&_relocation_set);
}

void ZGeneration::reset_relocation_set() {
  // Reset forwarding table
  ZRelocationSetIterator iter(&_relocation_set);
  for (ZForwarding* forwarding; iter.next(&forwarding);) {
    _forwarding_table.remove(forwarding);
  }

  // Reset relocation set
  _relocation_set.reset(_page_allocator);
}

void ZGeneration::synchronize_relocation() {
  _relocate.synchronize();
}

void ZGeneration::desynchronize_relocation() {
  _relocate.desynchronize();
}

bool ZGeneration::is_relocate_queue_active() const {
  return _relocate.is_queue_active();
}

void ZGeneration::reset_statistics() {
  assert(SafepointSynchronize::is_at_safepoint(), "Should be at safepoint");
  _freed = 0;
  _promoted = 0;
  _compacted = 0;
}

size_t ZGeneration::freed() const {
  return _freed;
}

void ZGeneration::increase_freed(size_t size) {
  Atomic::add(&_freed, size, memory_order_relaxed);
}

size_t ZGeneration::promoted() const {
  return _promoted;
}

void ZGeneration::increase_promoted(size_t size) {
  Atomic::add(&_promoted, size, memory_order_relaxed);
}

size_t ZGeneration::compacted() const {
  return _compacted;
}

void ZGeneration::increase_compacted(size_t size) {
  Atomic::add(&_compacted, size, memory_order_relaxed);
}

ConcurrentGCTimer* ZGeneration::gc_timer() const {
  return _gc_timer;
}

void ZGeneration::set_gc_timer(ConcurrentGCTimer* gc_timer) {
  assert(_gc_timer == nullptr, "Incorrect scoping");
  _gc_timer = gc_timer;
}

void ZGeneration::clear_gc_timer() {
  assert(_gc_timer != nullptr, "Incorrect scoping");
  _gc_timer = nullptr;
}

void ZGeneration::log_phase_switch(Phase from, Phase to) {
  const char* const str[] = {
    "Young Mark Start",
    "Young Mark End",
    "Young Relocate Start",
    "Old Mark Start",
    "Old Mark End",
    "Old Relocate Start"
  };

  size_t index = 0;

  if (is_old()) {
    index += 3;
  }

  if (to == Phase::Relocate) {
    index += 2;
  }

  if (from == Phase::Mark && to == Phase::MarkComplete) {
    index += 1;
  }

  assert(index < ARRAY_SIZE(str), "OOB: %zu < %zu", index, ARRAY_SIZE(str));

  Events::log_zgc_phase_switch("%-21s %4u", str[index], seqnum());
}

void ZGeneration::set_phase(Phase new_phase) {
  log_phase_switch(_phase, new_phase);

  _phase = new_phase;
}

void ZGeneration::at_collection_start(ConcurrentGCTimer* gc_timer) {
  set_gc_timer(gc_timer);
  stat_cycle()->at_start();
  stat_heap()->at_collection_start(_page_allocator->stats(this));
  workers()->set_active();
}

void ZGeneration::at_collection_end() {
  workers()->set_inactive();
  stat_cycle()->at_end(stat_workers(), should_record_stats());
  // The heap at collection end data is gathered at relocate end
  clear_gc_timer();
}

const char* ZGeneration::phase_to_string() const {
  switch (_phase) {
  case Phase::Mark:
    return "Mark";

  case Phase::MarkComplete:
    return "MarkComplete";

  case Phase::Relocate:
    return "Relocate";

  default:
    return "Unknown";
  }
}

class VM_ZOperation : public VM_Operation {
private:
  const uint           _gc_id;
  const GCCause::Cause _gc_cause;
  bool                 _success;

public:
  VM_ZOperation(GCCause::Cause gc_cause)
    : _gc_id(GCId::current()),
      _gc_cause(gc_cause),
      _success(false) {}

  virtual const char* cause() const {
    return GCCause::to_string(_gc_cause);
  }

  virtual bool block_jni_critical() const {
    // Blocking JNI critical regions is needed in operations where we change
    // the bad mask or move objects. Changing the bad mask will invalidate all
    // oops, which makes it conceptually the same thing as moving all objects.
    return false;
  }

  virtual bool skip_thread_oop_barriers() const {
    return true;
  }

  virtual bool do_operation() = 0;

  virtual bool doit_prologue() {
    Heap_lock->lock();
    return true;
  }

  virtual void doit() {
    // Setup GC id and active marker
    GCIdMark gc_id_mark(_gc_id);
    IsSTWGCActiveMark gc_active_mark;

    // Verify before operation
    ZVerify::before_zoperation();

    // Execute operation
    _success = do_operation();

    // Update statistics
    ZStatSample(ZSamplerJavaThreads, (uint64_t)Threads::number_of_threads());
  }

  virtual void doit_epilogue() {
    Heap_lock->unlock();

    // GC thread root traversal likely used OopMapCache a lot, which
    // might have created lots of old entries. Trigger the cleanup now.
    OopMapCache::try_trigger_cleanup();
  }

  virtual bool is_gc_operation() const {
    return true;
  }

  bool success() const {
    return _success;
  }

  bool pause() {
    if (block_jni_critical()) {
      ZJNICritical::block();
    }

    VMThread::execute(this);

    if (block_jni_critical()) {
      ZJNICritical::unblock();
    }

    return _success;
  }
};

ZYoungTypeSetter::ZYoungTypeSetter(ZYoungType type) {
  assert(ZGeneration::young()->_active_type == ZYoungType::none, "Invalid type");
  ZGeneration::young()->_active_type = type;
}

ZYoungTypeSetter::~ZYoungTypeSetter() {
  assert(ZGeneration::young()->_active_type != ZYoungType::none, "Invalid type");
  ZGeneration::young()->_active_type = ZYoungType::none;
}

ZGenerationYoung::ZGenerationYoung(ZPageTable* page_table,
                                   const ZForwardingTable* old_forwarding_table,
                                   ZPageAllocator* page_allocator)
  : ZGeneration(ZGenerationId::young, page_table, page_allocator),
    _active_type(ZYoungType::none),
    _tenuring_threshold(0),
    _remembered(page_table, old_forwarding_table, page_allocator),
    _jfr_tracer() {
  ZGeneration::_young = this;
}

uint ZGenerationYoung::tenuring_threshold() {
  return _tenuring_threshold;
}

class ZGenerationCollectionScopeYoung : public StackObj {
private:
  ZYoungTypeSetter _type_setter;
  ZStatTimer       _stat_timer;

public:
  ZGenerationCollectionScopeYoung(ZYoungType type, ConcurrentGCTimer* gc_timer)
    : _type_setter(type),
      _stat_timer(ZPhaseGenerationYoung[(int)type], gc_timer) {
    // Update statistics and set the GC timer
    ZGeneration::young()->at_collection_start(gc_timer);
  }

  ~ZGenerationCollectionScopeYoung() {
    // Update statistics and clear the GC timer
    ZGeneration::young()->at_collection_end();
  }
};

bool ZGenerationYoung::should_record_stats() {
  return type() == ZYoungType::minor ||
         type() == ZYoungType::major_partial_roots;
}

void ZGenerationYoung::collect(ZYoungType type, ConcurrentGCTimer* timer) {
  ZGenerationCollectionScopeYoung scope(type, timer);

  // Phase 1: Pause Mark Start
  pause_mark_start();

  // Phase 2: Concurrent Mark
  concurrent_mark();

  abortpoint();

  // Phase 3: Pause Mark End
  while (!pause_mark_end()) {
    // Phase 3.5: Concurrent Mark Continue
    concurrent_mark_continue();

    abortpoint();
  }

  // Phase 4: Concurrent Mark Free
  concurrent_mark_free();

  abortpoint();

  // Phase 5: Concurrent Reset Relocation Set
  concurrent_reset_relocation_set();

  abortpoint();

  // Phase 6: Concurrent Select Relocation Set
  concurrent_select_relocation_set();

  abortpoint();

  // Phase 7: Pause Relocate Start
  pause_relocate_start();

  // Note that we can't have an abortpoint here. We need
  // to let concurrent_relocate() call abort_page()
  // on the remaining entries in the relocation set.

  // Phase 8: Concurrent Relocate
  concurrent_relocate();
}

class VM_ZMarkStartYoungAndOld : public VM_ZOperation {
public:
  VM_ZMarkStartYoungAndOld()
    : VM_ZOperation(ZDriver::major()->gc_cause()) {}

  virtual VMOp_Type type() const {
    return VMOp_ZMarkStartYoungAndOld;
  }

  virtual bool block_jni_critical() const {
    return true;
  }

  virtual bool do_operation() {
    ZStatTimerYoung timer(ZPhasePauseMarkStartYoungAndOld);
    ZServiceabilityPauseTracer tracer;

    ZCollectedHeap::heap()->increment_total_collections(true /* full */);
    ZGeneration::young()->mark_start();
    ZGeneration::old()->mark_start();

    return true;
  }
};

class VM_ZYoungOperation : public VM_ZOperation {
private:
  static ZDriver* driver() {
    if (ZGeneration::young()->type() == ZYoungType::minor) {
      return ZDriver::minor();
    } else {
      return ZDriver::major();
    }
  }

public:
  VM_ZYoungOperation()
    : VM_ZOperation(driver()->gc_cause()) {}
};

class VM_ZMarkStartYoung : public VM_ZYoungOperation {
public:
  virtual VMOp_Type type() const {
    return VMOp_ZMarkStartYoung;
  }

  virtual bool block_jni_critical() const {
    return true;
  }

  virtual bool do_operation() {
    ZStatTimerYoung timer(ZPhasePauseMarkStartYoung);
    ZServiceabilityPauseTracer tracer;

    ZCollectedHeap::heap()->increment_total_collections(false /* full */);
    ZGeneration::young()->mark_start();

    return true;
  }
};

void ZGenerationYoung::flip_mark_start() {
  ZGlobalsPointers::flip_young_mark_start();
  ZBarrierSet::assembler()->patch_barriers();
  ZVerify::on_color_flip();
}

void ZGenerationYoung::flip_relocate_start() {
  ZGlobalsPointers::flip_young_relocate_start();
  ZBarrierSet::assembler()->patch_barriers();
  ZVerify::on_color_flip();
}

void ZGenerationYoung::pause_mark_start() {
  if (type() == ZYoungType::major_full_roots ||
      type() == ZYoungType::major_partial_roots) {
    VM_ZMarkStartYoungAndOld().pause();
  } else {
    VM_ZMarkStartYoung().pause();
  }
}

void ZGenerationYoung::concurrent_mark() {
  ZStatTimerYoung timer(ZPhaseConcurrentMarkYoung);
  mark_roots();
  mark_follow();
}

class VM_ZMarkEndYoung : public VM_ZYoungOperation {
public:
  virtual VMOp_Type type() const {
    return VMOp_ZMarkEndYoung;
  }

  virtual bool do_operation() {
    ZStatTimerYoung timer(ZPhasePauseMarkEndYoung);
    ZServiceabilityPauseTracer tracer;

    return ZGeneration::young()->mark_end();
  }
};


bool ZGenerationYoung::pause_mark_end() {
  return VM_ZMarkEndYoung().pause();
}

void ZGenerationYoung::concurrent_mark_continue() {
  ZStatTimerYoung timer(ZPhaseConcurrentMarkContinueYoung);
  mark_follow();
}

void ZGenerationYoung::concurrent_mark_free() {
  ZStatTimerYoung timer(ZPhaseConcurrentMarkFreeYoung);
  mark_free();
}

void ZGenerationYoung::concurrent_reset_relocation_set() {
  ZStatTimerYoung timer(ZPhaseConcurrentResetRelocationSetYoung);
  reset_relocation_set();
}

void ZGenerationYoung::select_tenuring_threshold(ZRelocationSetSelectorStats stats, bool promote_all) {
  const char* reason = "";
  if (promote_all) {
    _tenuring_threshold = 0;
    reason = "Promote All";
  } else if (ZTenuringThreshold != -1) {
    _tenuring_threshold = static_cast<uint>(ZTenuringThreshold);
    reason = "ZTenuringThreshold";
  } else {
    _tenuring_threshold = compute_tenuring_threshold(stats);
    reason = "Computed";
  }
  log_info(gc, reloc)("Using tenuring threshold: %d (%s)", _tenuring_threshold, reason);
}

uint ZGenerationYoung::compute_tenuring_threshold(ZRelocationSetSelectorStats stats) {
  size_t young_live_total = 0;
  size_t young_live_last = 0;
  double young_life_expectancy_sum = 0.0;
  uint young_life_expectancy_samples = 0;
  uint last_populated_age = 0;
  size_t last_populated_live = 0;

  for (ZPageAge age : ZPageAgeRange()) {
    const size_t young_live = stats.small(age).live() + stats.medium(age).live() + stats.large(age).live();
    if (young_live > 0) {
      last_populated_age = untype(age);
      last_populated_live = young_live;
      if (young_live_last > 0) {
        young_life_expectancy_sum += double(young_live) / double(young_live_last);
        young_life_expectancy_samples++;
      }
    }
    young_live_total += young_live;
    young_live_last = young_live;
  }

  if (young_live_total == 0) {
    return 0;
  }

  const size_t young_used_at_mark_start = ZGeneration::young()->stat_heap()->used_generation_at_mark_start();
  const size_t young_garbage = ZGeneration::young()->stat_heap()->garbage_at_mark_end();
  const size_t young_allocated = ZGeneration::young()->stat_heap()->allocated_at_mark_end();
  const size_t soft_max_capacity = ZHeap::heap()->soft_max_capacity();

  // The life expectancy shows by what factor on average one age changes between
  // two ages in the age table. Values below 1 indicate generational behaviour where
  // the live bytes is shrinking from age to age. Values at or above 1 indicate
  // anti-generational patterns where the live bytes isn't going down or grows
  // from age to age.
  const double young_life_expectancy = young_life_expectancy_samples == 0 ? 1.0 : young_life_expectancy_sum / young_life_expectancy_samples;

  // The life decay factor is the reciprocal of the life expectancy. Therefore,
  // values at or below 1 indicate anti-generational behaviour where the live
  // bytes either stays the same or grows from age to age. Conversely, values
  // above 1 indicate generational behaviour where the live bytes shrinks from
  // age to age. The more it shrinks from age to age, the higher the value.
  // Therefore, the higher this value is, the higher we want the tenuring
  // threshold to be, as we exponentially avoid promotions to the old generation.
  const double young_life_decay_factor = 1.0 / young_life_expectancy;

  // The young residency reciprocal indicates the inverse of how small the
  // resident part of the young generation is compared to the entire heap. Values
  // below 1 indicate it is relatively big. Conversely, values above 1 indicate
  // it is relatively small.
  const double young_residency_reciprocal = double(soft_max_capacity) / double(young_live_total);

  // The old residency factor clamps the old residency reciprocal to
  // at least 1. That implies this factor is 1 unless the resident memory of
  // the old generation is small compared to the residency of the heap. The
  // smaller the old generation is, the higher this value is. The reasoning
  // is that the less memory that is resident in the old generation, the less
  // point there is in promoting objects to the old generation, as the amount
  // of work it removes from the young generation collections becomes less
  // and less valuable, the smaller the old generation is.
  const double young_residency_factor = MAX2(young_residency_reciprocal, 1.0);

  // The allocated to garbage ratio, compares the ratio of newly allocated
  // memory since GC started to how much garbage we are freeing up. The higher
  // the value, the harder it is for the YC to keep up with the allocation rate.
  const double allocated_garbage_ratio = double(young_allocated) / double(young_garbage + 1);

  // We slow down the young residency factor with a log. A larger log slows
  // it down faster. We select a log between 2 - 16 scaled by the allocated
  // to garbage factor. This selects a larger log when the GC has a harder
  // time keeping up, which causes more promotions to the old generation,
  // making the young collections faster so they can catch up.
  const double young_log = MAX2(MIN2(allocated_garbage_ratio, 1.0) * 16, 2.0);

  // The young log residency is essentially the young residency factor, but slowed
  // down by the log_{young_log}(X) function described above.
  const double young_log_residency = log(young_residency_factor) / log(young_log);

  // The tenuring threshold is computed as the young life decay factor times
  // the young residency factor. That takes into consideration that the
  // value should be higher the more generational the age table is, and higher
  // the more insignificant the footprint of young resident memory is, yet breaks
  // if the GC is finding it hard to keep up with the allocation rate.
  const double tenuring_threshold_raw = young_life_decay_factor * young_log_residency;

  log_trace(gc, reloc)("Young Allocated: %zuM", young_allocated / M);
  log_trace(gc, reloc)("Young Garbage: %zuM", young_garbage / M);
  log_debug(gc, reloc)("Allocated To Garbage: %.1f", allocated_garbage_ratio);
  log_trace(gc, reloc)("Young Log: %.1f", young_log);
  log_trace(gc, reloc)("Young Residency Reciprocal: %.1f", young_residency_reciprocal);
  log_trace(gc, reloc)("Young Residency Factor: %.1f", young_residency_factor);
  log_debug(gc, reloc)("Young Log Residency: %.1f", young_log_residency);
  log_debug(gc, reloc)("Life Decay Factor: %.1f", young_life_decay_factor);

  // Round to an integer as we can't have non-integral tenuring threshold.
  const uint upper_bound = MIN2(last_populated_age + 1u, (uint)MaxTenuringThreshold);
  const uint lower_bound = MIN2(1u, upper_bound);
  const uint tenuring_threshold = clamp((uint)round(tenuring_threshold_raw), lower_bound, upper_bound);

  return tenuring_threshold;
}

void ZGenerationYoung::concurrent_select_relocation_set() {
  ZStatTimerYoung timer(ZPhaseConcurrentSelectRelocationSetYoung);
  const bool promote_all = type() == ZYoungType::major_full_preclean;
  select_relocation_set(promote_all);
}

class VM_ZRelocateStartYoung : public VM_ZYoungOperation {

public:
  virtual VMOp_Type type() const {
    return VMOp_ZRelocateStartYoung;
  }

  virtual bool block_jni_critical() const {
    return true;
  }

  virtual bool do_operation() {
    ZStatTimerYoung timer(ZPhasePauseRelocateStartYoung);
    ZServiceabilityPauseTracer tracer;

    ZGeneration::young()->relocate_start();

    return true;
  }
};

void ZGenerationYoung::pause_relocate_start() {
  VM_ZRelocateStartYoung().pause();
}

void ZGenerationYoung::concurrent_relocate() {
  ZStatTimerYoung timer(ZPhaseConcurrentRelocatedYoung);
  relocate();
}

void ZGenerationYoung::mark_start() {
  assert(SafepointSynchronize::is_at_safepoint(), "Should be at safepoint");

  // Change good colors
  flip_mark_start();

  // Reset TLAB usage
  ZHeap::heap()->reset_tlab_used();

  // Retire allocating pages
  ZAllocator::eden()->retire_pages();
  for (ZPageAge age : ZPageAgeRangeSurvivor) {
    ZAllocator::relocation(age)->retire_pages();
  }

  // Reset allocated/reclaimed/used statistics
  reset_statistics();

  // Increment sequence number
  _seqnum++;

  // Enter mark phase
  set_phase(Phase::Mark);

  // Reset marking information
  _mark.start();

  // Flip remembered set bits
  _remembered.flip();

  // Update statistics
  stat_heap()->at_mark_start(_page_allocator->update_and_stats(this));
}

void ZGenerationYoung::mark_roots() {
  ZStatTimerYoung timer(ZSubPhaseConcurrentMarkRootsYoung);
  _mark.mark_young_roots();
}

void ZGenerationYoung::mark_follow() {
  // Combine following with scanning the remembered set
  ZStatTimerYoung timer(ZSubPhaseConcurrentMarkFollowYoung);
  _remembered.scan_and_follow(&_mark);
}

bool ZGenerationYoung::mark_end() {
  assert(SafepointSynchronize::is_at_safepoint(), "Should be at safepoint");

  // End marking
  if (!_mark.end()) {
    // Marking not completed, continue concurrent mark
    return false;
  }

  // Enter mark completed phase
  set_phase(Phase::MarkComplete);

  // Update statistics
  stat_heap()->at_mark_end(_page_allocator->stats(this));

  // Notify JVMTI that some tagmap entry objects may have died.
  JvmtiTagMap::set_needs_cleaning();

  return true;
}

void ZGenerationYoung::relocate_start() {
  assert(SafepointSynchronize::is_at_safepoint(), "Should be at safepoint");

  // Change good colors
  flip_relocate_start();

  // Enter relocate phase
  set_phase(Phase::Relocate);

  // Update statistics
  stat_heap()->at_relocate_start(_page_allocator->stats(this));

  _relocate.start();
}

void ZGenerationYoung::relocate() {
  // Relocate relocation set
  _relocate.relocate(&_relocation_set);

  // Update statistics
  stat_heap()->at_relocate_end(_page_allocator->stats(this), should_record_stats());
}

void ZGenerationYoung::flip_promote(ZPage* from_page, ZPage* to_page) {
  _page_table->replace(from_page, to_page);

  // Update statistics
  _page_allocator->promote_used(from_page, to_page);
  increase_freed(from_page->size());
  increase_promoted(from_page->live_bytes());
}

void ZGenerationYoung::in_place_relocate_promote(ZPage* from_page, ZPage* to_page) {
  _page_table->replace(from_page, to_page);

  // Update statistics
  _page_allocator->promote_used(from_page, to_page);
}

void ZGenerationYoung::register_flip_promoted(const ZArray<ZPage*>& pages) {
  _relocation_set.register_flip_promoted(pages);
}

void ZGenerationYoung::register_in_place_relocate_promoted(ZPage* page) {
  _relocation_set.register_in_place_relocate_promoted(page);
}

void ZGenerationYoung::register_with_remset(ZPage* page) {
  _remembered.register_found_old(page);
}

ZRemembered* ZGenerationYoung::remembered() {
  return &_remembered;
}

void ZGenerationYoung::remap_current_remset(ZRemsetTableIterator* iter) {
  _remembered.remap_current(iter);
}

ZGenerationTracer* ZGenerationYoung::jfr_tracer() {
  return &_jfr_tracer;
}

ZGenerationOld::ZGenerationOld(ZPageTable* page_table, ZPageAllocator* page_allocator)
  : ZGeneration(ZGenerationId::old, page_table, page_allocator),
    _reference_processor(&_workers),
    _weak_roots_processor(&_workers),
    _unload(&_workers),
    _total_collections_at_start(0),
    _young_seqnum_at_reloc_start(0),
    _jfr_tracer() {
  ZGeneration::_old = this;
}

class ZGenerationCollectionScopeOld : public StackObj {
private:
  ZStatTimer      _stat_timer;
  ZDriverUnlocker _unlocker;

public:
  ZGenerationCollectionScopeOld(ConcurrentGCTimer* gc_timer)
    : _stat_timer(ZPhaseGenerationOld, gc_timer),
      _unlocker() {
    // Update statistics and set the GC timer
    ZGeneration::old()->at_collection_start(gc_timer);
  }

  ~ZGenerationCollectionScopeOld() {
    // Update statistics and clear the GC timer
    ZGeneration::old()->at_collection_end();
  }
};

bool ZGenerationOld::should_record_stats() {
  return true;
}

void ZGenerationOld::collect(ConcurrentGCTimer* timer) {
  ZGenerationCollectionScopeOld scope(timer);

  // Phase 1: Concurrent Mark
  concurrent_mark();

  abortpoint();

  // Phase 2: Pause Mark End
  while (!pause_mark_end()) {
    // Phase 2.5: Concurrent Mark Continue
    concurrent_mark_continue();

    abortpoint();
  }

  // Phase 3: Concurrent Mark Free
  concurrent_mark_free();

  abortpoint();

  // Phase 4: Concurrent Process Non-Strong References
  concurrent_process_non_strong_references();

  abortpoint();

  // Phase 5: Concurrent Reset Relocation Set
  concurrent_reset_relocation_set();

  abortpoint();

  // Phase 6: Pause Verify
  pause_verify();

  // Phase 7: Concurrent Select Relocation Set
  concurrent_select_relocation_set();

  abortpoint();

  {
    ZDriverLocker locker;

    // Phase 8: Concurrent Remap Roots
    concurrent_remap_young_roots();

    abortpoint();

    // Phase 9: Pause Relocate Start
    pause_relocate_start();
  }

  // Note that we can't have an abortpoint here. We need
  // to let concurrent_relocate() call abort_page()
  // on the remaining entries in the relocation set.

  // Phase 10: Concurrent Relocate
  concurrent_relocate();
}

void ZGenerationOld::flip_mark_start() {
  ZGlobalsPointers::flip_old_mark_start();
  ZBarrierSet::assembler()->patch_barriers();
  ZVerify::on_color_flip();
}

void ZGenerationOld::flip_relocate_start() {
  ZGlobalsPointers::flip_old_relocate_start();
  ZBarrierSet::assembler()->patch_barriers();
  ZVerify::on_color_flip();
}

void ZGenerationOld::concurrent_mark() {
  ZStatTimerOld timer(ZPhaseConcurrentMarkOld);
  ZBreakpoint::at_after_marking_started();
  mark_roots();
  mark_follow();
  ZBreakpoint::at_before_marking_completed();
}

class VM_ZMarkEndOld : public VM_ZOperation {
public:
  VM_ZMarkEndOld()
    : VM_ZOperation(ZDriver::major()->gc_cause()) {}

  virtual VMOp_Type type() const {
    return VMOp_ZMarkEndOld;
  }

  virtual bool do_operation() {
    ZStatTimerOld timer(ZPhasePauseMarkEndOld);
    ZServiceabilityPauseTracer tracer;

    return ZGeneration::old()->mark_end();
  }
};

bool ZGenerationOld::pause_mark_end() {
  return VM_ZMarkEndOld().pause();
}

void ZGenerationOld::concurrent_mark_continue() {
  ZStatTimerOld timer(ZPhaseConcurrentMarkContinueOld);
  mark_follow();
}

void ZGenerationOld::concurrent_mark_free() {
  ZStatTimerOld timer(ZPhaseConcurrentMarkFreeOld);
  mark_free();
}

void ZGenerationOld::concurrent_process_non_strong_references() {
  ZStatTimerOld timer(ZPhaseConcurrentProcessNonStrongOld);
  ZBreakpoint::at_after_reference_processing_started();
  process_non_strong_references();
}

void ZGenerationOld::concurrent_reset_relocation_set() {
  ZStatTimerOld timer(ZPhaseConcurrentResetRelocationSetOld);
  reset_relocation_set();
}

class VM_ZVerifyOld : public VM_Operation {
public:
  virtual VMOp_Type type() const {
    return VMOp_ZVerifyOld;
  }

  virtual bool skip_thread_oop_barriers() const {
    return true;
  }

  virtual void doit() {
    ZVerify::after_weak_processing();
  }

  void pause() {
    VMThread::execute(this);
  }
};

void ZGenerationOld::pause_verify() {
  // Note that we block out concurrent young collections when performing the
  // verification. The verification checks that store good oops in the
  // old generation have a corresponding remembered set entry, or is in
  // a store barrier buffer (hence asynchronously creating such entries).
  // That lookup would otherwise race with installation of base pointers
  // into the store barrier buffer. We dodge that race by blocking out
  // young collections during this verification.
  if (ZVerifyRoots || ZVerifyObjects) {
    // Limited verification
    ZDriverLocker locker;
    VM_ZVerifyOld().pause();
  }
}

void ZGenerationOld::concurrent_select_relocation_set() {
  ZStatTimerOld timer(ZPhaseConcurrentSelectRelocationSetOld);
  select_relocation_set(false /* promote_all */);
}

class VM_ZRelocateStartOld : public VM_ZOperation {
public:
  VM_ZRelocateStartOld()
    : VM_ZOperation(ZDriver::major()->gc_cause()) {}

  virtual VMOp_Type type() const {
    return VMOp_ZRelocateStartOld;
  }

  virtual bool block_jni_critical() const {
    return true;
  }

  virtual bool do_operation() {
    ZStatTimerOld timer(ZPhasePauseRelocateStartOld);
    ZServiceabilityPauseTracer tracer;

    ZGeneration::old()->relocate_start();

    return true;
  }
};

void ZGenerationOld::pause_relocate_start() {
  VM_ZRelocateStartOld().pause();
}

void ZGenerationOld::concurrent_relocate() {
  ZStatTimerOld timer(ZPhaseConcurrentRelocatedOld);
  relocate();
}

void ZGenerationOld::concurrent_remap_young_roots() {
  ZStatTimerOld timer(ZPhaseConcurrentRemapRootsOld);
  remap_young_roots();
}

void ZGenerationOld::mark_start() {
  assert(SafepointSynchronize::is_at_safepoint(), "Should be at safepoint");

  // Verification
  ClassLoaderDataGraph::verify_claimed_marks_cleared(ClassLoaderData::_claim_strong);

  // Change good colors
  flip_mark_start();

  // Retire allocating pages
  ZAllocator::old()->retire_pages();

  // Reset allocated/reclaimed/used statistics
  reset_statistics();

  // Reset encountered/dropped/enqueued statistics
  _reference_processor.reset_statistics();

  // Increment sequence number
  _seqnum++;

  // Enter mark phase
  set_phase(Phase::Mark);

  // Reset marking information
  _mark.start();

  // Update statistics
  stat_heap()->at_mark_start(_page_allocator->update_and_stats(this));

  // Note that we start a marking cycle.
  // Unlike other GCs, the color switch implicitly changes the nmethods
  // to be armed, and the thread-local disarm values are lazily updated
  // when JavaThreads wake up from safepoints.
  CodeCache::on_gc_marking_cycle_start();

  _total_collections_at_start = ZCollectedHeap::heap()->total_collections();
}

void ZGenerationOld::mark_roots() {
  ZStatTimerOld timer(ZSubPhaseConcurrentMarkRootsOld);
  _mark.mark_old_roots();
}

void ZGenerationOld::mark_follow() {
  ZStatTimerOld timer(ZSubPhaseConcurrentMarkFollowOld);
  _mark.mark_follow();
}

bool ZGenerationOld::mark_end() {
  assert(SafepointSynchronize::is_at_safepoint(), "Should be at safepoint");

  // Try end marking
  if (!_mark.end()) {
    // Marking not completed, continue concurrent mark
    return false;
  }

  // Enter mark completed phase
  set_phase(Phase::MarkComplete);

  // Verify after mark
  ZVerify::after_mark();

  // Update statistics
  stat_heap()->at_mark_end(_page_allocator->stats(this));

  // Block resurrection of weak/phantom references
  ZResurrection::block();

  // Prepare to unload stale metadata and nmethods
  _unload.prepare();

  // Notify JVMTI that some tagmap entry objects may have died.
  JvmtiTagMap::set_needs_cleaning();

  // Note that we finished a marking cycle.
  // Unlike other GCs, we do not arm the nmethods
  // when marking terminates.
  CodeCache::on_gc_marking_cycle_finish();

  return true;
}

void ZGenerationOld::set_soft_reference_policy(bool clear) {
  _reference_processor.set_soft_reference_policy(clear);
}

bool ZGenerationOld::uses_clear_all_soft_reference_policy() const {
  return _reference_processor.uses_clear_all_soft_reference_policy();
}

class ZRendezvousHandshakeClosure : public HandshakeClosure {
public:
  ZRendezvousHandshakeClosure()
    : HandshakeClosure("ZRendezvous") {}

  void do_thread(Thread* thread) {
    // Does nothing
  }
};

class ZRendezvousGCThreads: public VM_Operation {
 public:
  VMOp_Type type() const { return VMOp_ZRendezvousGCThreads; }

  virtual bool evaluate_at_safepoint() const {
    // We only care about synchronizing the GC threads.
    // Leave the Java threads running.
    return false;
  }

  virtual bool skip_thread_oop_barriers() const {
    fatal("Concurrent VMOps should not call this");
    return true;
  }

  virtual bool is_gc_operation() const {
    return true;
  }

  void doit() {
    // Light weight "handshake" of the GC threads
    SuspendibleThreadSet::synchronize();
    SuspendibleThreadSet::desynchronize();
  };
};


void ZGenerationOld::process_non_strong_references() {
  // Process Soft/Weak/Final/PhantomReferences
  _reference_processor.process_references();

  // Process weak roots
  _weak_roots_processor.process_weak_roots();

  ClassUnloadingContext ctx(_workers.active_workers(),
                            true /* unregister_nmethods_during_purge */,
                            true /* lock_nmethod_free_separately */);

  // Unlink stale metadata and nmethods
  _unload.unlink();

  // Perform a handshake. This is needed 1) to make sure that stale
  // metadata and nmethods are no longer observable. And 2), to
  // prevent the race where a mutator first loads an oop, which is
  // logically null but not yet cleared. Then this oop gets cleared
  // by the reference processor and resurrection is unblocked. At
  // this point the mutator could see the unblocked state and pass
  // this invalid oop through the normal barrier path, which would
  // incorrectly try to mark the oop.
  ZRendezvousHandshakeClosure cl;
  Handshake::execute(&cl);

  // GC threads are not part of the handshake above.
  // Explicitly "handshake" them.
  ZRendezvousGCThreads op;
  VMThread::execute(&op);

  // Unblock resurrection of weak/phantom references
  ZResurrection::unblock();

  // Purge stale metadata and nmethods that were unlinked
  _unload.purge();

  // Enqueue Soft/Weak/Final/PhantomReferences. Note that this
  // must be done after unblocking resurrection. Otherwise the
  // Finalizer thread could call Reference.get() on the Finalizers
  // that were just enqueued, which would incorrectly return null
  // during the resurrection block window, since such referents
  // are only Finalizable marked.
  _reference_processor.enqueue_references();

  // Clear old markings claim bits.
  // Note: Clearing _claim_strong also clears _claim_finalizable.
  ClassLoaderDataGraph::clear_claimed_marks(ClassLoaderData::_claim_strong);
}

void ZGenerationOld::relocate_start() {
  assert(SafepointSynchronize::is_at_safepoint(), "Should be at safepoint");

  // Finish unloading stale metadata and nmethods
  _unload.finish();

  // Change good colors
  flip_relocate_start();

  // Enter relocate phase
  set_phase(Phase::Relocate);

  // Update statistics
  stat_heap()->at_relocate_start(_page_allocator->stats(this));

  // Need to know the remset parity when relocating objects
  _young_seqnum_at_reloc_start = ZGeneration::young()->seqnum();

  _relocate.start();
}

void ZGenerationOld::relocate() {
  // Relocate relocation set
  _relocate.relocate(&_relocation_set);

  // Update statistics
  stat_heap()->at_relocate_end(_page_allocator->stats(this), should_record_stats());
}

class ZRemapOopClosure : public OopClosure {
public:
  virtual void do_oop(oop* p) {
    ZBarrier::load_barrier_on_oop_field((volatile zpointer*)p);
  }

  virtual void do_oop(narrowOop* p) {
    ShouldNotReachHere();
  }
};

class ZRemapThreadClosure : public ThreadClosure {
public:
  virtual void do_thread(Thread* thread) {
    JavaThread* const jt = JavaThread::cast(thread);
    StackWatermarkSet::finish_processing(jt, nullptr, StackWatermarkKind::gc);
  }
};

class ZRemapNMethodClosure : public NMethodClosure {
private:
  ZBarrierSetNMethod* const _bs_nm;

public:
  ZRemapNMethodClosure()
    : _bs_nm(static_cast<ZBarrierSetNMethod*>(BarrierSet::barrier_set()->barrier_set_nmethod())) {}

  virtual void do_nmethod(nmethod* nm) {
    ZLocker<ZReentrantLock> locker(ZNMethod::lock_for_nmethod(nm));
    if (_bs_nm->is_armed(nm)) {
      // Heal barriers
      ZNMethod::nmethod_patch_barriers(nm);

      // Heal oops
      ZUncoloredRootProcessOopClosure cl(ZNMethod::color(nm));
      ZNMethod::nmethod_oops_do_inner(nm, &cl);

      log_trace(gc, nmethod)("nmethod: " PTR_FORMAT " visited by old remapping", p2i(nm));

      // Disarm
      _bs_nm->disarm(nm);
    }
  }
};

typedef ClaimingCLDToOopClosure<ClassLoaderData::_claim_none> ZRemapCLDClosure;

class ZRemapYoungRootsTask : public ZTask {
private:
  ZRemsetTableIterator             _remset_table_iterator;

  ZRootsIteratorAllColored         _roots_colored;
  ZRootsIteratorAllUncolored       _roots_uncolored;

  ZRemapOopClosure                 _cl_colored;
  ZRemapCLDClosure                 _cld_cl;

  ZRemapThreadClosure              _thread_cl;
  ZRemapNMethodClosure             _nm_cl;

public:
  ZRemapYoungRootsTask(ZPageTable* page_table, ZPageAllocator* page_allocator)
    : ZTask("ZRemapYoungRootsTask"),
      _remset_table_iterator(ZGeneration::young()->remembered(), false /* previous */),
      _roots_colored(ZGenerationIdOptional::old),
      _roots_uncolored(ZGenerationIdOptional::old),
      _cl_colored(),
      _cld_cl(&_cl_colored),
      _thread_cl(),
      _nm_cl() {}

  virtual void work() {
    {
      ZStatTimerWorker timer(ZSubPhaseConcurrentRemapRootsColoredOld);
      _roots_colored.apply(&_cl_colored,
                           &_cld_cl);
    }

    {
      ZStatTimerWorker timer(ZSubPhaseConcurrentRemapRootsUncoloredOld);
      _roots_uncolored.apply(&_thread_cl,
                             &_nm_cl);
    }

    {
      ZStatTimerWorker timer(ZSubPhaseConcurrentRemapRememberedOld);
      // Visit all object fields that potentially pointing into young generation
      ZGeneration::young()->remap_current_remset(&_remset_table_iterator);
    }
  }
};

// This function is used by the old generation to purge roots to the young generation from
// young remap bit errors, before the old generation performs old relocate start. By doing
// that, we can know that double remap bit errors don't need to be concerned with double
// remap bit errors, in the young generation roots. That makes it possible to figure out
// which generation table to use when remapping a pointer, without needing an extra adjust
// phase that walks the entire heap.
void ZGenerationOld::remap_young_roots() {
  // We upgrade the number of workers to the number last used by the young generation. The
  // reason is that this code is run under the driver lock, which means that a young generation
  // collection might be waiting for this code to complete.
  uint prev_nworkers = _workers.active_workers();
  uint remap_nworkers = clamp(ZGeneration::young()->workers()->active_workers() + prev_nworkers, 1u, ZOldGCThreads);
  _workers.set_active_workers(remap_nworkers);

  // TODO: The STS joiner is only needed to satisfy ZBarrier::assert_is_state_barrier_safe that doesn't
  // understand the driver locker. Consider making the assert aware of the driver locker.
  SuspendibleThreadSetJoiner sts_joiner;

  ZRemapYoungRootsTask task(_page_table, _page_allocator);
  workers()->run(&task);
  _workers.set_active_workers(prev_nworkers);
}

uint ZGenerationOld::total_collections_at_start() const {
  return _total_collections_at_start;
}

ZGenerationTracer* ZGenerationOld::jfr_tracer() {
  return &_jfr_tracer;
}
