JEP-351 ZGC: Uncommit Unused Memory(JDK 13)

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仅仅做了很小的代码改造就实现了。

4. 引用

OpenJDK 13 源代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值