首页 > 解决方案 > GC1 不释放 OS-Memory

问题描述

我使用默认垃圾收集器 (G1GC) 运行 java 应用程序。我不明白 G1CC 何时准确释放内存。在htop我看到700M应用程序使用的地方。在我这样做之后,jcmd <pid> GC.run它下降到大约250M. 这是否意味着 GC 自己不会这样做?

标签: javagarbageg1gc

解决方案


在优化应用程序的性能和可扩展性时,资源效率是一个主要问题。在 Java 世界中,通常支配资源问题的一个方面是内存使用——更具体地说,是 Java 堆的大小。

与 .NET 和 CLR 相比,JVM 以需要在启动时调整堆大小而闻名,而没有任何空间回收的机会,这将使资源可用于其他进程。这在今天仍然适用吗?让我们来了解一下。

垃圾收集器负责管理堆和相关的操作系统分配。从 JDK 9 开始,G1 垃圾收集器一直是默认的 GC。一般来说,GC 会尽量避免释放堆空间(以将内存还给操作系统),因为如果稍后需要该空间,这可能会导致性能下降。

堆收缩取决于回收周期,回收周期由 Young-only 和空间回收阶段组成。

如果相关的伊甸园空间(最初分配新对象的地方)用尽了,就会发生仅限年轻阶段。可达对象被疏散(移动)到幸存者空间。如果幸存者空间耗尽,可达对象将被疏散到另一个幸存者空间或老年代(如果对象是可达的或“存活”给定数量的集合)。

空间回收取决于老年代的大小,由初始堆占用百分比 (IHOP) 控制。默认 ( -XX:InitiatingHeapOccupancyPercent) 为 45%,但 IHOP 由 GC 调整以优化收集间隔(称为“自适应 IHOP”)。这个阶段也处理年轻代,因此它被称为“混合收集”。

根据JDK 9 中的 G1 源,收缩仅在完全收集(显式收集与否)之后进行,并且仅在满足特定阈值的情况下进行。G1CollectedHeap::do_full_collection调用G1CollectedHeap::resize_if_necessary_after_full_collection,它处理收缩过程并生成以下 GC 日志输出:

log_debug(gc, ergo, heap)(
  "Shrink the heap. requested shrinking amount: "
  SIZE_FORMAT "B aligned shrinking amount: "
  SIZE_FORMAT "B attempted shrinking amount: "
  SIZE_FORMAT "B",
  shrink_bytes, aligned_shrink_bytes, shrunk_bytes);

G1 GC 旨在

[...] 提供延迟和吞吐量之间的最佳平衡 [...]

G1 尝试尽可能长时间地推迟垃圾收集,并尝试避免完全垃圾收集。有关详细信息,请参阅垃圾回收周期

[...] 如果应用程序在收集活跃度信息时内存不足,G1 会像其他收集器一样执行就地停止世界全堆压缩 (Full GC) [...]

除非手动触发(例如使用java.lang.System.gc()),否则收集器仅在空间耗尽的情况下执行完整收集。这就是堆收缩可能发生的时候。

根据JDK 15 中的 G1 源代码,对完整集合以及并发标记和清除集合进行了收缩。依次G1FullCollector::complete_collection调用. 实际收缩过程和日志消息与 JDK 9 相同。与 JDK 9 相比,JDK 12 及更高版本还在所谓的重新标记阶段(调用)期间,在正常收集周期的上下文中执行收缩。G1CollectedHeap::prepare_heap_for_mutatorsG1CollectedHeap::resize_heap_if_necessaryG1ConcurrentMark::remarkG1CollectedHeap::resize_heap_if_necessary

更改是通过08041b0d7c08引入的。

一种直接的监控方法是-verbose:gc。用于-Xlog:gc=debug查看上面提到的日志输出,以防缩水。

因此,尽管普遍认为,JVM 确实会将内存返回给操作系统。在 JDK 11 之前,堆收缩仅在完全收集后才会触发,因为更改的重要性足以保证该操作不会影响性能。使用 JDK 12+(检查到 15),堆收缩也会在正常收集周期内触发,这使得通常不涉及完整收集的应用程序更有可能看到堆收缩。


推荐阅读