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. 总结
本文介绍了转移阶段开始的两个小步骤,重置转移集和选择转移集,后文将继续介绍并发转移的过程。