Java 12 JEP 346: Promptly Return Unused Committed Memory from G1

1. 背景

目前G1垃圾回收器无法及时的将Java堆内存返回给操作系统。G1仅仅当full GC或并发周期时才会返回内存。通常的,除非在外部强制的执行,G1在很多情况下不会返回堆内存给操作系统。

在云计算大行其道的计算的今天,很多部署环境是按资源支付的,这种行为特别不利。即使是JVM由于不活跃而仅仅使用分配的内存资源的一小部分,G1也将保留所有Java堆。这导致客户始终为没有使用的资源付费,云服务商也无法充分利用其硬件。

因此,希望垃圾回收器能够检测到Java堆的利用率不足,并在此情况下,自动减少堆使用量。

目前,Shenandoah(JEP - 189)和OpenJ9(IBM J9开源版)的GenCon收集器已经提供了类似的功能。

在Bruno使用原型进行的测试表明,此解决方案可以减少85%的内存使用率。

2. 快速返回内存的机制

为了实现尽快向操作系统返回堆内存的目标,G1将在JVM不活跃期间定期的尝试触发并发周期以确定整体Java堆的使用情况,这将会导致G1自动将Java堆的未使用部分返回给操作系统。

G1通过下面两个参数来决定是否触发定期垃圾回收:

  • G1PeriodicGCInterval
    自上一次垃圾收集暂停以来已经过了超过n毫秒,并且此时没有正在进行的并发周期。值为零表示禁用快速回收内存的定期垃圾收集。

  • G1PeriodicGCSystemLoadThreshold
    调用getloadavg()返回平均一分钟JVM主机或容器的负载值小于G1PeriodicGCSystemLoadThreshold。值为0则忽略此条件。

如果不满足以上条件中的任何一个,则不执行定期垃圾收集。G1PeriodicGCInterval毫秒后,再重新判断只发执行定期垃圾收集G1PeriodicGCInterval。
如果满足以上条件,触发full GC或者concurrent GC(如果开启G1PeriodicGCInvokesConcurrent),GC之后Java heap size会被重写调整,然后多余的内存将会归还给操作系统。

3. 如何启动定期垃圾收集

-Xlog:gc:…/logs/gc.log
-XX:G1PeriodicGCInterval=5000
-XX:G1PeriodicGCSystemLoadThreshold=0

从如下的gc日志我们可以看出,G1PeriodicGCInterval=5000 ms时,G1每隔5000毫秒触发定期垃圾收集,并返回未使用的堆内存给操作系统。

[0.064s][info][gc] Using G1
[0.221s][info][gc] Periodic GC enabled with interval 5000ms
[5.087s][info][gc] GC(0) Pause Young (Concurrent Start) (G1 Periodic Collection) 1M->1M(130M) 19.502ms
[5.087s][info][gc] GC(1) Concurrent Cycle
[5.094s][info][gc] GC(1) Pause Remark 1M->1M(10M) 1.518ms
[5.095s][info][gc] GC(1) Pause Cleanup 1M->1M(10M) 0.059ms
[5.096s][info][gc] GC(1) Concurrent Cycle 8.830ms
[10.254s][info][gc] GC(2) Pause Young (Concurrent Start) (G1 Periodic Collection) 1M->1M(10M) 15.772ms
[10.254s][info][gc] GC(3) Concurrent Cycle
[10.257s][info][gc] GC(3) Pause Remark 1M->1M(10M) 0.374ms
[10.257s][info][gc] GC(3) Pause Cleanup 1M->1M(10M) 0.066ms
[10.258s][info][gc] GC(3) Concurrent Cycle 3.096ms
[15.423s][info][gc] GC(4) Pause Young (Concurrent Start) (G1 Periodic Collection) 1M->1M(10M) 17.878ms
[15.423s][info][gc] GC(5) Concurrent Cycle
[15.425s][info][gc] GC(5) Pause Remark 1M->1M(10M) 0.350ms
[15.426s][info][gc] GC(5) Pause Cleanup 1M->1M(10M) 0.068ms
[15.426s][info][gc] GC(5) Concurrent Cycle 3.321ms

4. 相关代码

由于定期垃圾收集功能大量复用现有功能,代码量并不大,大部分代码都在f94c7929a44b commit中。

4.1 g1YoungRemSetSamplingThread.cpp

bool G1YoungRemSetSamplingThread::should_start_periodic_gc() {
  // 如果当前处于并发标记阶段,则退出
  // If we are currently in a concurrent mark we are going to uncommit memory soon.
  if (G1CollectedHeap::heap()->concurrent_mark()->cm_thread()->during_cycle()) {
    log_debug(gc, periodic)("Concurrent cycle in progress. Skipping.");
    return false;
  }
  // 检查距上次GC是否有足够时间
  uintx time_since_last_gc = (uintx)Universe::heap()->millis_since_last_gc();
  if ((time_since_last_gc < G1PeriodicGCInterval)) {
    log_debug(gc, periodic)("Last GC occurred " UINTX_FORMAT "ms before which is below threshold " UINTX_FORMAT "ms. Skipping.",
                            time_since_last_gc, G1PeriodicGCInterval);
    return false;
  }
  // 检查系统负载是否低于阈值
  double recent_load;
  if ((G1PeriodicGCSystemLoadThreshold > 0.0f) &&
      (os::loadavg(&recent_load, 1) == -1 || recent_load > G1PeriodicGCSystemLoadThreshold)) {
    log_debug(gc, periodic)("Load %1.2f is higher than threshold %1.2f. Skipping.",
                            recent_load, G1PeriodicGCSystemLoadThreshold);
    return false;
  }
  return true;
}

5. 引用

JEP 346: Promptly Return Unused Committed Memory from G1
https://openjdk.java.net/jeps/346

猜你喜欢

转载自blog.csdn.net/a860MHz/article/details/88841446