在并发处理完强引用和非强引用后,ZGC就进入了转移阶段。
本文将介绍转移阶段开始的两个小步骤,重置转移集和选择转移集。
在标记阶段介绍后,所有的引用都已经指向对象迁移后的新地址,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; }
这一步主要是选择出待转移的页面,入口如下,可以看出这一步直接在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; }
本文介绍了转移阶段开始的两个小步骤,重置转移集和选择转移集,后文将继续介绍并发转移的过程。