OpenJDK16 ZGC 源码分析(七)GC阶段之转移集

1. 简介

在并发处理完强引用和非强引用后,ZGC就进入了转移阶段。
本文将介绍转移阶段开始的两个小步骤,重置转移集和选择转移集。

2. 源码分析

2.1 重置转移集

在标记阶段介绍后,所有的引用都已经指向对象迁移后的新地址,ZForwardingTable中的数据已经全部失效。此时需要重置转移集,为下一轮GC做准备。
hotspot/share/gc/z/zHeap.cpp

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

  // 重置转移集
  _relocation_set.reset();
}

重置forwarding table逻辑如下:
hotspot/share/gc/z/zForwardingTable.inline.hpp

inline void ZForwardingTable::remove(ZForwarding* forwarding) {
  const uintptr_t offset = forwarding->start();
  const size_t size = forwarding->size();

  assert(_map.get(offset) == forwarding, "Invalid entry");
  // 将offset后size个元素置为null
  _map.put(offset, size, NULL);
}

转移集的reset逻辑也比较简单,就是迭代器调用forwarding的析构函数:
hotspot/share/gc/z/zRelocationSet.cpp

void ZRelocationSet::reset() {
  // 析构forwardings
  ZRelocationSetIterator iter(this);
  for (ZForwarding* forwarding; iter.next(&forwarding);) {
    forwarding->~ZForwarding();
  }

  _nforwardings = 0;
}

2.2 选择转移集

这一步主要是选择出待转移的页面,入口如下,可以看出这一步直接在ZDriver线程执行,没有并发操作。
hotspot/share/gc/z/zHeap.cpp

void ZHeap::select_relocation_set() {
  // 组织其他线程执行页面删除操作
  _page_allocator.enable_deferred_delete();

  // 注册page到转移集选择器
  ZRelocationSetSelector selector;
  ZPageTableIterator pt_iter(&_page_table);
  for (ZPage* page; pt_iter.next(&page);) {
    if (!page->is_relocatable()) {
      // 本次GC开始后分配的page,无需转移
      continue;
    }

    if (page->is_marked()) {
      // 添加存活page
      selector.register_live_page(page);
    } else {
      // 添加空page
      selector.register_empty_page(page);

      // 回收空page
      free_empty_pages(&selector, 64 /* bulk */);
    }
  }

  // 回收空page
  free_empty_pages(&selector, 0 /* bulk */);

  // page注册完毕,其他线程可以删除page了
  _page_allocator.disable_deferred_delete();

  // 选择转移集
  selector.select();
  _relocation_set.install(&selector);

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

  // Update statistics
  ZStatRelocation::set_at_select_relocation_set(selector.stats());
  ZStatHeap::set_at_select_relocation_set(selector.stats());
}

具体的选择逻辑如下:
hotspot/share/gc/z/zRelocationSetSelector.cpp

void ZRelocationSetSelectorGroup::select() {
  if (is_disabled()) {
    return;
  }

  EventZRelocationSetGroup event;

  // 只有中小页面可以转移,因为large page只包含一个对象
  if (is_selectable()) {
    // 实际的选择函数
    select_inner();
  }

  // Send event
  event.commit(_page_type, _stats.npages(), _stats.total(), _stats.empty(), _stats.relocate());
}

// 计算出可回收页面,并计算出转移回收页面上的存活对象所需要的空间
void ZRelocationSetSelectorGroup::select_inner() {
  // 存活页面的数量
  const int npages = _live_pages.length();
  int selected_from = 0;
  int selected_to = 0;
  size_t selected_live_bytes = 0;
  size_t selected_forwarding_entries = 0;
  size_t from_live_bytes = 0;
  size_t from_forwarding_entries = 0;

  // 按页面的活跃对象字节数排好序
  // 升序排序
  semi_sort();

  // 添加活跃页面到候选转移集
  for (int from = 1; from <= npages; from++) {
    ZPage* const page = _live_pages.at(from - 1);
    // 需要转移的字节数
    from_live_bytes += page->live_bytes();
    from_forwarding_entries += ZForwarding::nentries(page);

    // 计算转移后对象需要的页面数
    const int to = ceil((double)(from_live_bytes) / (double)(_page_size - _object_size_limit));

    // 只有页面垃圾大于25%的页面,才会添加到转移集
    const int diff_from = from - selected_from;
    const int diff_to = to - selected_to;
    const double diff_reclaimable = 100 - percent_of(diff_to, diff_from);
    // ZFragmentationLimit默认为25%
    if (diff_reclaimable > ZFragmentationLimit) {
      selected_from = from;
      selected_to = to;
      selected_live_bytes = from_live_bytes;
      selected_forwarding_entries = from_forwarding_entries;
    }

    log_trace(gc, reloc)("Candidate Relocation Set (%s Pages): %d->%d, "
                         "%.1f%% relative defragmentation, " SIZE_FORMAT " forwarding entries, %s",
                         _name, from, to, diff_reclaimable, from_forwarding_entries,
                         (selected_from == from) ? "Selected" : "Rejected");
  }
}

bool ZRelocationSetSelectorGroup::is_selectable() {
  // 只有中小页面可以转移,因为large page只包含一个对象
  return _page_type != ZPageTypeLarge;
}

3. 总结

本文介绍了转移阶段开始的两个小步骤,重置转移集和选择转移集,后文将继续介绍并发转移的过程。

上一篇:TSN的来源和应用


下一篇:实验5:开源控制器实践——POX