階段6(Concurrent Reset Relocation Set)
//重置forwarding table
for (ZForwarding* forwarding; iter.next(&forwarding);) {
_forwarding_table.remove(forwarding);
}
// 重置relocation set
_relocation_set.reset();
這個階段做的事情比較簡單,主要是清理工作,為下一步做好準備,直接在ZDriver線程執行;
階段7(Pause Verify)
當前VerifyBeforeGC 、 VerifyDuringGC 和VerifyAfterGC參數其中任何一個配置為true時,將會執行校驗,這個階段是需要暫停業務線程的,默認不執行;
階段8(Concurrent Select Relocation Set)
該階段是根據前面階段標記的結果,選擇要進行轉移的page頁;
void ZHeap::select_relocation_set() {
ZRelocationSetSelector selector;
ZPageTableIterator pt_iter(&_page_table);
for (ZPage* page; pt_iter.next(&page);) {
//如果頁是在本次GC開始之后分配的,則認為里面的對象都是活躍的,不需要轉移
if (!page->is_relocatable()) {
continue;
}
//如果該頁的被標記過,說明存在活躍的對象,注意,此處要求一個page的垃圾超過25%才會加入到_live_pages
if (page->is_marked()) {
selector.register_live_page(page);
} else {
//沒有活躍對象,可以全部被回收
selector.register_empty_page(page);
//釋放空頁面(達到64個釋放一次,避免頻繁釋放)
free_empty_pages(&selector, 64 /* bulk */);
}
}
// 釋放空頁面
free_empty_pages(&selector, 0 /* bulk */);
// 選擇要回收的pages
selector.select();
_relocation_set.install(&selector);
ZRelocationSetIterator rs_iter(&_relocation_set);
for (ZForwarding* forwarding; rs_iter.next(&forwarding);) {
_forwarding_table.insert(forwarding);
}
}
ZGC是如何選擇需要轉移的page呢?
- 如果是large page,不會被選中;
- _live_pages根據存活對象升序排列,統計前N個page里面的垃圾占比,如果大于25%,則這個N個page需要進行轉移;
- 更新全局的_forwarding_table信息;
階段9(Pause Relocate Start)
void ZHeap::relocate_start() {
// 更新metasapce的大小
_unload.finish();
// 切換地址視圖到Remapped,在這個之后新分配的對象,地址視圖都是Remapped
flip_to_remapped();
// Enter relocate phase
ZGlobalPhase = ZPhaseRelocate;
}
階段10(Concurrent Relocate)
對象轉移是和業務線程并發執行的,具體的邏輯如下:
virtual void work() {
ZRelocateClosure<ZRelocateSmallAllocator> small(&_small_allocator);
ZRelocateClosure<ZRelocateMediumAllocator> medium(&_medium_allocator);
for (ZForwarding* forwarding; _iter.next(&forwarding);) {
if (is_small(forwarding)) {
small.do_forwarding(forwarding);
} else {
medium.do_forwarding(forwarding);
}
}
}
并發轉移的邏輯如下:
- 遍歷轉移集中的page,根據是small page還是medium page進行后續處理;
- small和medium page的處理邏輯類似,區別是對象分配器不同,一個是ZRelocateSmallAllocator,另外一個是ZRelocateMediumAllocator;
- 首先使用ZRelocateSmallAllocator或ZRelocateMediumAllocator配置一個page頁,然后遍歷ZForwardingEntry對象,將對象從原地址轉移到新地址,同時更新ZForwardingEntry的to_offset,field_populated字段也設置為true;
- 為了提高內存使用效率,ZForwardingEntry是使用一個64bit的整數來記錄這些信息,其中第0位表示populated信息,1-45表示To Object Offset,46-63表示 From Object Index;
5.此處主要,轉移完成之后,_forwarding_table信息并沒有清除,而是到下次GC時才會清理掉,原因在于對象轉移了,但是可能對象在很多地方被引用,這些引用地址并沒有被調整;那么ZGC是如何處理這種情況的呢?其實在前面已經說過,ZGC會使用讀barrier來進行地址的調整,代碼如下:
uintptr_t ZBarrier::weak_load_barrier_on_oop_slow_path(uintptr_t addr) {
return ZAddress::is_weak_good(addr) ? ZAddress::good(addr) : relocate_or_remap(addr);
}
uintptr_t ZBarrier::relocate_or_remap(uintptr_t addr) {
return during_relocate() ? relocate(addr) : remap(addr);
}
inline uintptr_t ZHeap::remap_object(uintptr_t addr) {
assert(ZGlobalPhase == ZPhaseMark ||
ZGlobalPhase == ZPhaseMarkCompleted, "Forward not allowed");
ZForwarding* const forwarding = _forwarding_table.get(addr);
if (forwarding == NULL) {
// Not forwarding
return ZAddress::good(addr);
}
// Forward object
return _relocate.forward_object(forwarding, ZAddress::good(addr));
}