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僅僅做了很小的代碼改造就實現了。