OpenJDK ZGC 源碼分析(六)GC回收

1. 簡介

GC回收週期大體如下圖所示:
image.png

GC回收週期包括如下11個子階段:

  • phase 1:初始標記,需要STW
  • phase 2:併發標記
  • phase 3:標記結束,需要STW
  • phase 4:併發處理軟引用、弱引用
  • phase 5:併發重置Relocation Set
  • phase 6:併發銷燬可回收頁
  • phase 7:內存驗證
  • phase 8:併發選擇Relocation Set
  • phase 9:併發準備Relocation Set
  • phase 10:開始Relocate,STW
  • phase 11:併發Relocate

出於回收效率的考慮,remap過程放在下一個回收週期的併發標記子階段進行。
image.png

2. 代碼分析

2.1 入口

ZGC的入口在ZCollectedHeap collect方法

  • 調用ZDriver的collect方法

zCollectedHeap.cpp

void ZCollectedHeap::collect(GCCause::Cause cause) {
  _driver->collect(cause);
}

根據傳入的GCCause,判斷使用同步消息還是異步消息。

  • ZGC自身的觸發策略都使用異步消息,包括rule_timer、rule_warmup、rule_allocation_rate、rule_proactive
  • metaspace GC使用異步消息
  • 其他情況使用同步消息

zDriver.cpp

void ZDriver::collect(GCCause::Cause cause) {
  switch (cause) {
  case GCCause::_wb_young_gc:
  case GCCause::_wb_conc_mark:
  case GCCause::_wb_full_gc:
  case GCCause::_dcmd_gc_run:
  case GCCause::_java_lang_system_gc:
  case GCCause::_full_gc_alot:
  case GCCause::_scavenge_alot:
  case GCCause::_jvmti_force_gc:
  case GCCause::_metadata_GC_clear_soft_refs:
    // Start synchronous GC
    _gc_cycle_port.send_sync(cause);
    break;

  case GCCause::_z_timer:
  case GCCause::_z_warmup:
  case GCCause::_z_allocation_rate:
  case GCCause::_z_allocation_stall:
  case GCCause::_z_proactive:
  case GCCause::_metadata_GC_threshold:
    // Start asynchronous GC
    _gc_cycle_port.send_async(cause);
    break;

  case GCCause::_gc_locker:
    // Restart VM operation previously blocked by the GC locker
    _gc_locker_port.signal();
    break;

  default:
    // Other causes not supported
    fatal("Unsupported GC cause (%s)", GCCause::to_string(cause));
    break;
  }
}

2.2 消息機制

ZGC使用ZMessagePort類傳遞消息,ZMessagePort內部使用了ZList隊列。

zMessagePort.hpp

class ZMessagePort {
private:
  typedef ZMessageRequest<T> Request;

  Monitor        _monitor;
  bool           _has_message;
  T              _message;
  uint64_t       _seqnum;
  ZList<Request> _queue;

public:
  ZMessagePort();

  void send_sync(T message);
  void send_async(T message);

  T receive();
  void ack();
};

同步消息邏輯如下:

  • 首先構造request
  • request入隊
  • 通知消費者
  • 如果是同步消息,需要等待request處理完

zMessagePort.inline.hpp

template <typename T>
inline void ZMessagePort<T>::send_sync(T message) {
  Request request;

  {
    // Enqueue message
    MonitorLockerEx ml(&_monitor, Monitor::_no_safepoint_check_flag);
    request.initialize(message, _seqnum);
    _queue.insert_last(&request);
    ml.notify();
  }

  // Wait for completion
  request.wait();

  {
    // Guard deletion of underlying semaphore. This is a workaround for a
    // bug in sem_post() in glibc < 2.21, where it's not safe to destroy
    // the semaphore immediately after returning from sem_wait(). The
    // reason is that sem_post() can touch the semaphore after a waiting
    // thread have returned from sem_wait(). To avoid this race we are
    // forcing the waiting thread to acquire/release the lock held by the
    // posting thread. https://sourceware.org/bugzilla/show_bug.cgi?id=12674
    MonitorLockerEx ml(&_monitor, Monitor::_no_safepoint_check_flag);
  }
}

消息消費者負責消費隊列中的消息,如果是異步消息,則直接讀取類變量_message。

zMessagePort.inline.hpp

template <typename T>
inline T ZMessagePort<T>::receive() {
  MonitorLockerEx ml(&_monitor, Monitor::_no_safepoint_check_flag);

  // Wait for message
  while (!_has_message && _queue.is_empty()) {
    ml.wait(Monitor::_no_safepoint_check_flag);
  }

  // Increment request sequence number
  _seqnum++;

  if (!_has_message) {
    // Message available in the queue
    _message = _queue.first()->message();
    _has_message = true;
  }

  return _message;
}

2.3 開始gc cycle

ZDriver啓動一個線程,死循環判斷是否應該啓動gc cycle

  • start_gc_cycle調用ZMessagePort的receive方法等待啓動請求
  • 調用run_gc_cycle方法,執行GC
  • 執行GC後,調用end_gc_cycle,ACK啓動請求

zDriver.cpp

void ZDriver::run_service() {
  // Main loop
  while (!should_terminate()) {
    const GCCause::Cause cause = start_gc_cycle();
    if (cause != GCCause::_no_gc) {
      run_gc_cycle(cause);
      end_gc_cycle();
    }
  }
}

GCCause::Cause ZDriver::start_gc_cycle() {
  // Wait for GC request
  return _gc_cycle_port.receive();
}

void ZDriver::end_gc_cycle() {
  // Notify GC cycle completed
  _gc_cycle_port.ack();

  // Check for out of memory condition
  ZHeap::heap()->check_out_of_memory();
}

run_gc_cycle中,順序執行GC的11個子階段。

zDriver.cpp

void ZDriver::run_gc_cycle(GCCause::Cause cause) {
  ZDriverCycleScope scope(cause);

  // Phase 1: Pause Mark Start
  {
    ZMarkStartClosure cl;
    vm_operation(&cl);
  }

  // Phase 2: Concurrent Mark
  {
    ZStatTimer timer(ZPhaseConcurrentMark);
    ZHeap::heap()->mark(true /* initial */);
  }

  // Phase 3: Pause Mark End
  {
    ZMarkEndClosure cl;
    while (!vm_operation(&cl)) {
      // Phase 3.5: Concurrent Mark Continue
      ZStatTimer timer(ZPhaseConcurrentMarkContinue);
      ZHeap::heap()->mark(false /* initial */);
    }
  }

  // Phase 4: Concurrent Process Non-Strong References
  {
    ZStatTimer timer(ZPhaseConcurrentProcessNonStrongReferences);
    ZHeap::heap()->process_non_strong_references();
  }

  // Phase 5: Concurrent Reset Relocation Set
  {
    ZStatTimer timer(ZPhaseConcurrentResetRelocationSet);
    ZHeap::heap()->reset_relocation_set();
  }

  // Phase 6: Concurrent Destroy Detached Pages
  {
    ZStatTimer timer(ZPhaseConcurrentDestroyDetachedPages);
    ZHeap::heap()->destroy_detached_pages();
  }

  // Phase 7: Pause Verify
  if (VerifyBeforeGC || VerifyDuringGC || VerifyAfterGC) {
    ZVerifyClosure cl;
    vm_operation(&cl);
  }

  // Phase 8: Concurrent Select Relocation Set
  {
    ZStatTimer timer(ZPhaseConcurrentSelectRelocationSet);
    ZHeap::heap()->select_relocation_set();
  }

  // Phase 9: Concurrent Prepare Relocation Set
  {
    ZStatTimer timer(ZPhaseConcurrentPrepareRelocationSet);
    ZHeap::heap()->prepare_relocation_set();
  }

  // Phase 10: Pause Relocate Start
  {
    ZRelocateStartClosure cl;
    vm_operation(&cl);
  }

  // Phase 11: Concurrent Relocate
  {
    ZStatTimer timer(ZPhaseConcurrentRelocated);
    ZHeap::heap()->relocate();
  }
}

3. 引用

OpenJDK 12 源代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章