1. 简介
将GC后的内存返回给OS这一能力,在云计算场景中将节省用户的支出。因此G1和Shenandoah均实现了该能力,在JDK 13中,ZGC也具备了该能力。
由于ZGC的堆被划分为一个个的ZPage,返回OS将以ZPage为单位执行,实现较为简单,下文中将详细说明。
2. 分析
2.1 页缓存
ZGC中使用ZPageCache缓存ZPage,ZPageCache中维护了三个LRU链表分别缓存small medium large页面。
zPageCache.cpp
class ZPageCache {
private:
size_t _available;
ZPerNUMA<ZList<ZPage> > _small;
ZList<ZPage> _medium;
ZList<ZPage> _large;
ZPage* alloc_small_page();
ZPage* alloc_medium_page();
ZPage* alloc_large_page(size_t size);
ZPage* alloc_oversized_medium_page(size_t size);
ZPage* alloc_oversized_large_page(size_t size);
ZPage* alloc_oversized_page(size_t size);
void free_page_inner(ZPage* page);
}
2.2 页的分配与回收
ZPageAllocator分配页时,首先要从ZPageCache中查找是否有空闲的ZPage,如有则直接使用此ZPage,并将其从ZPageCache链表中摘除;ZPageAllocator释放页时,也是将ZPage加入ZPageCache链表。
zPageAllocator.cpp
ZPage* ZPageAllocator::alloc_page_common_inner(uint8_t type, size_t size, bool no_reserve) {
if (!ensure_available(size, no_reserve)) {
// Not enough free memory
return NULL;
}
// 首先尝试从ZPageCache中分配
ZPage* const page = _cache.alloc_page(type, size);
if (page != NULL) {
return page;
}
// Try flush pages from the cache
ensure_uncached_available(size);
// Create new page
return create_page(type, size);
}
void ZPageAllocator::free_page(ZPage* page, bool reclaimed) {
ZLocker<ZLock> locker(&_lock);
// Update used statistics
decrease_used(page->size(), reclaimed);
// Set time when last used
page->set_last_used();
// 添加到ZPageCache的LRU链表中
_cache.free_page(page);
// Try satisfy blocked allocations
satisfy_alloc_queue();
}
2.3 页返回给OS
JDK启动了一个后台任务,定时的调用ZHeap的uncommit函数,以返回页给OS
zUncommitter.cpp
void ZUncommitter::run_service() {
for (;;) {
// Try uncommit unused memory
const uint64_t timeout = ZHeap::heap()->uncommit(ZUncommitDelay);
log_trace(gc, heap)("Uncommit Timeout: " UINT64_FORMAT "s", timeout);
// 默认情况下idle 5*60秒
if (!idle(timeout)) {
return;
}
}
}
zPageAllocator.cpp
uint64_t ZPageAllocator::uncommit(uint64_t delay) {
// Set the default timeout, when no pages are found in the
// cache or when uncommit is disabled, equal to the delay.
uint64_t timeout = delay;
if (!_uncommit) {
// Disabled
return timeout;
}
size_t capacity_before;
size_t capacity_after;
size_t uncommitted;
{
SuspendibleThreadSetJoiner joiner;
ZLocker<ZLock> locker(&_lock);
// Don't flush more than we will uncommit. Never uncommit
// the reserve, and never uncommit below min capacity.
const size_t needed = MIN2(_used + _max_reserve, _current_max_capacity);
const size_t guarded = MAX2(needed, _min_capacity);
// 可以uncommit的容量等于总容量减去必须保留的容量
const size_t uncommittable = _capacity - guarded;
const size_t uncached_available = _capacity - _used - _cache.available();
size_t uncommit = MIN2(uncommittable, uncached_available);
// 将uncommittable与实际uncommit之间的页flush,flush页到uncommit
const size_t flush = uncommittable - uncommit;
if (flush > 0) {
// Flush pages to uncommit
ZPageCacheFlushForUncommitClosure cl(flush, delay);
uncommit += flush_cache(&cl);
timeout = cl.timeout();
}
// Uncommit
uncommitted = _physical.uncommit(uncommit);
_capacity -= uncommitted;
capacity_after = _capacity;
capacity_before = capacity_after + uncommitted;
}
if (uncommitted > 0) {
log_info(gc, heap)("Capacity: " SIZE_FORMAT "M(%.0lf%%)->" SIZE_FORMAT "M(%.0lf%%), "
"Uncommitted: " SIZE_FORMAT "M",
capacity_before / M, percent_of(capacity_before, max_capacity()),
capacity_after / M, percent_of(capacity_after, max_capacity()),
uncommitted / M);
// Update statistics
ZStatInc(ZCounterUncommit, uncommitted);
}
return timeout;
}
3. 总结
基于良好的设计,JEP-351 ZGC: Uncommit Unused Memory仅仅做了很小的代码改造就实现了。