OpenJDK JVM 会把堆内存还给 Linux 吗?
Posted
技术标签:
【中文标题】OpenJDK JVM 会把堆内存还给 Linux 吗?【英文标题】:Will OpenJDK JVM ever give heap memory back to Linux? 【发布时间】:2016-01-23 22:55:17 【问题描述】:我们有一个长期存在的服务器进程,它在短时间内很少需要大量 RAM。我们看到,一旦 JVM 从操作系统获取了内存,它 永远不会将其返回给操作系统。我们如何要求 JVM 将堆内存返回给操作系统?
通常,此类问题的公认答案是使用
-XX:MaxHeapFreeRatio
和 -XX:MinHeapFreeRatio
。 (参见例如
1,2,3,4)。但是我们是这样运行 java 的:
java -Xmx4G -XX:MaxHeapFreeRatio=50 -XX:MinHeapFreeRatio=30 MemoryUsage
在 VisualVM 中仍然可以看到:
很明显,JVM 不支持-XX:MaxHeapFreeRatio=50
,因为 heapFreeRatio 非常接近 100% 而远未接近 50%。再多的点击“Perform GC”也不会将内存返回给操作系统。
MemoryUsage.java:
import java.util.ArrayList;
import java.util.List;
public class MemoryUsage
public static void main(String[] args) throws InterruptedException
System.out.println("Sleeping before allocating memory");
Thread.sleep(10*1000);
System.out.println("Allocating/growing memory");
List<Long> list = new ArrayList<>();
// Experimentally determined factor. This gives approximately 1750 MB
// memory in our installation.
long realGrowN = 166608000; //
for (int i = 0 ; i < realGrowN ; i++)
list.add(23L);
System.out.println("Memory allocated/grown - sleeping before Garbage collecting");
Thread.sleep(10*1000);
list = null;
System.gc();
System.out.println("Garbage collected - sleeping forever");
while (true)
Thread.sleep(1*1000);
版本:
> java -version
openjdk version "1.8.0_66-internal"
OpenJDK Runtime Environment (build 1.8.0_66-internal-b01)
OpenJDK 64-Bit Server VM (build 25.66-b01, mixed mode)
> uname -a
Linux londo 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt11-1+deb8u5 (2015-10-09) x86_64 GNU/Linux
> lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux 8.2 (jessie)
Release: 8.2
Codename: jessie
我也尝试过 OpenJDK 1.7 和 Sun Java 的 1.8。所有行为都相似,没有 将内存还给操作系统。
我确实认为我需要这个,而交换和分页不会“解决”这个问题,因为将磁盘 IO 用于分页接近 2GB 的垃圾进出只是浪费资源。如果不同意,请赐教。
我还用malloc()
/free()
编写了一点memoryUsage.c,它确实将内存返回给操作系统。所以它在 C 中是可能的。也许不是在 Java 中?
编辑:Augusto 指出搜索会导致我找到 -XX:MaxHeapFreeRatio
和 -XX:MinHeapFreeRatio
仅适用于 -XX:+UseSerialGC
。我欣喜若狂地尝试了一下,我很困惑我自己没有找到这个。是的,它确实适用于我的 MemoryUsage.java:
但是,当我用我们的真实应用程序尝试-XX:+UseSerialGC
时,并没有那么多:
一段时间后我发现 gc() 确实有帮助,所以我创建了一个或多或少的线程:
while (idle() && memoryTooLarge() && ! tooManyAttemptsYet())
Thread.sleep(10*1000);
System.gc();
这就成功了:
实际上,在我的许多实验中,我实际上已经看到了 -XX:+UseSerialGC
和多个 System.gc()
调用的行为,但我不喜欢需要 GC 线程。谁知道随着我们的应用程序和 Java 的发展,这是否会继续有效。一定有更好的办法。
迫使我四次(但不是立即)调用System.gc()
的逻辑是什么,这些东西记录在哪里?
在搜索 -XX:MaxHeapFreeRatio
和 -XX:MinHeapFreeRatio
仅与 -XX:+UseSerialGC
一起使用的文档时,我阅读了 the documentation for the java tool/executable 并且在任何地方都没有提到 -XX:MaxHeapFreeRatio
和 -XX:MinHeapFreeRatio
仅适用于 -XX:+UseSerialGC
。事实上,固定问题[JDK-8028391] Make the Min/MaxHeapFreeRatio flags manageable 说:
为了使应用程序能够控制允许更多或更少 GC 的方式和时间, 应该制作标志 -XX:MinHeapFreeRatio 和 -XX:MaxHeapFreeRatio 可管理。对这些标志的支持也应该在 默认并行收集器。
已解决问题的comment 说:
对这些标志的支持也被添加到 ParallelGC 作为 自适应大小政策的一部分。
我已经检查过了,fixed issue backported to openjdk-8 中引用的patch 确实包含在我正在使用的 openjdk-8 版本的source package tarball 中。所以它显然应该在“默认并行收集器”中工作,但不像我在这篇文章中展示的那样。我还没有找到任何说明它只能与-XX:+UseSerialGC
一起使用的文档。正如我在这里记录的那样,即使这是不可靠的/冒险的。
我不能让-XX:MaxHeapFreeRatio
和-XX:MinHeapFreeRatio
履行他们的承诺,而不必经历所有这些麻烦吗?
【问题讨论】:
搜索是您的朋友 - 答案是“取决于您选择的 GC 算法”:Release java memory for OS at runtime. 只是提醒...调用 gc() 不会执行 JVM 垃圾收集器,它只会建议 JVM 执行它。它可能执行,也可能不执行... 另一件事是:要让JVM GC 正常工作,您必须正确编码。我的意思是:注意重Objects的使用,如StringBuffer、Cloneable Objects、不必要的实例、循环内的concat String、Singleton模式等……所有这些东西都会增加堆空间。 垃圾收集和向操作系统释放堆是两个不同的操作。堆是线性的;释放堆空间将需要一个专用堆完全为空,或者在堆上进行非常昂贵的碎片整理操作。警告:我不是 Java 专家,但我知道非托管代码中的堆使用情况。 虽然这对于一般情况来说可能是正确的,但在我们的例子中,我们会在短时间内分配大量内存并在不久之后释放(大部分)它们。可以看出,使用正确的咒语(例如-XX:+UseSerialGC
和线程)是可能的。我看到了四种可能性: 1) 承认-XX:MaxHeapFreeRatio
和-XX:MinHeapFreeRatio
有问题,不能像宣传的那样工作。 2) 修复它们的实现 3) 请记住,无论出于何种原因,它们都无法正确实现,并弃用/删除它们。 4) 更改文档以准确反映我的预期。
@ErickG.Hagstrom:我的问题从一开始就是“我们如何要求 JVM 将堆内存返回给操作系统?”。我猜答案是“你不能 - -XX:MaxHeapFreeRatio
和 -XX:MinHeapFreeRatio
不能像宣传的那样工作”。
【参考方案1】:
G1 (-XX:+UseG1GC)、并行清除 (-XX:+UseParallelGC) 和 ParallelOld (-XX:+UseParallelOldGC) 确实在堆时返回内存 缩小。我不太确定 Serial 和 CMS,它们并没有缩小 他们在我的实验中堆积如山。
两个并行收集器在收缩之前确实需要多次 GC 堆减小到“可接受”的大小。这是每个设计。他们是 故意持有堆,假设它将被需要 未来。设置标志 -XX:GCTimeRatio=1 将改善 情况有所不同,但仍然需要几次 GC 才能大幅缩小。
G1 非常擅长快速收缩堆,所以对于用例 如上所述,我会说它可以通过使用 G1 并运行来解决
System.gc()
释放所有缓存和类加载器等之后。
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6498735
【讨论】:
感谢@Sammy,提取您提供的链接。我已经尝试过了,确实,-XX:+UseG1GC
确实适用于我上面的示例MemoryUsage.java
。但是,-XX:+UseParallelGC
和 -XX:+UseParallelOldGC
都不会导致堆收缩。我不太相信-XX:+UseG1GC
将可靠地为我们的真实应用程序工作。虽然您在技术上确实回答了我的问题,但我会接受。然而,我确实认为真正的答案是“好吧,-XX:MaxHeapFreeRatio
和 -XX:MinHeapFreeRatio
的实现或文档都被彻底破坏了”+ 指向新错误报告的链接。
我清楚地记得这个问题被问到 javaone 的 GC 团队并且答案始终相同,即 Max/MinHeapFreeRatio 标志只是对 jvm 的提示,不多不少。大多数 GC 操作只是尽力而为,不提供任何保证。这是这些标志在 -XX 下暴露的原因之一(相对于 -X 或只是 -)。众所周知,-XX 确实是内部不受支持的标志,Sun/Oracle jvm 开发人员创建这些标志是为了暴露某些行为,这些行为仅在特定条件下有效,有时仅在特定平台和实现上有效。
CMS 应该在它进行完整的 STW 收集时进行,但由于它的设计目标主要是避免 STW 收集,因此它不能可靠地将未使用的内存及时返回给操作系统。
我在 OpenJDK 中运行的应用程序遇到了类似的问题。运行一项大任务后,操作系统内存(win)最终会达到 9GB - 并且永远不会缩小。我添加了 useG1GC 参数,操作系统内存从未超过 700M,并且进程运行速度快了几个 %。以上是关于OpenJDK JVM 会把堆内存还给 Linux 吗?的主要内容,如果未能解决你的问题,请参考以下文章