在本文中,我们想分享 Java 内存管理和容器内部弹性的细节,这些细节乍一看并不明显。
您将在下面找到需要注意的问题列表和即将发布的 JDK 版本中的重要更新,以及针对核心痛点的现有解决方法。我们收集了前 5 个最有趣和最有用的技巧,以提高 Java 应用程序的资源使用效率。
目前,社区正在讨论有关在 Docker 容器中运行 Java 应用程序时内存限制确定不正确的问题。
问题在于,如果未明确定义 Xmx 选项,则由于默认的内部垃圾收集 (GC) 人体工程学算法,JVM 将使用主机操作系统所有可用内存的 1/4。如果 JVM 内存使用量增长超过为 Docker 容器定义的 cgroups 限制,这可能会导致内核终止 Java 进程。
为了解决这个问题,最近在 OpenJDK 9 中实现了一项改进:
OpenJDK 9 中添加了第一个实验性更改,因此 JVM 可以理解它在容器中运行并相应地调整内存限制。”来自 Java 9 将在运行 Docker 时调整内存限制 文章
新的 JVM 选项 (-XX:+UseCGroupMemoryLimitForHeap) 根据 cgroup 中定义的内存限制自动为 Java 进程设置 Xmx。
作为在 Java 9 公开发布之前解决此问题的一个很好的解决方法,可以在 JVM 的启动选项中明确指定 Xmx 限制。 官方 OpenJDK 存储库 中有一个公开的拉取请求,要求“根据 docker 内存限制设置更好的默认 Xmx 值的脚本”。
Jelastic 通过结合使用增强的系统容器虚拟化层和 Docker 镜像,设法避免了错误的内存限制确定。之前我们在文章容器中的 Java 和内存限制:LXC、Docker 和 OpenVZ 中解释了它的工作原理。
在云中运行 Java 应用程序时,注意 Java 进程的本机内存使用情况也很重要,即所谓的堆外内存。它可以用于不同的目的:
默认情况下,元空间分配仅受可用本机操作系统内存量的限制。结合 Docker 容器中不正确的内存限制确定,这会增加应用程序不稳定的风险。限制元数据的大小很重要,特别是当您遇到 OOM 问题时。使用特殊选项 -XX:MetaspaceSize 执行此操作。
由于所有对象都存储在正常的垃圾回收堆内存之外,因此它们对 Java 应用程序的内存占用量的影响并不明显。有一篇很好的文章详细解释了这个问题,并提供了一些如何分析本机内存使用情况的指南:
“几周前,我在尝试分析在 Docker 下运行的 Java 应用程序(Spring Boot + Infinispan)中的内存消耗时遇到了一个有趣的问题。 Xmx 参数设置为 256m,但 Docker 监控工具显示已用内存几乎是两倍。” –分析 Docker 容器中的 java 内存使用情况。
以及作者的有趣总结:
“我能说什么作为总结?好吧……永远不要把“java”和“micro”放在同一个句子中,我在开玩笑——请记住,在 java、linux 和 docker 的情况下处理内存比一开始看起来要棘手一些。”
为了跟踪本机内存分配,可以使用特定的 JVM 选项 (-XX:NativeMemoryTracking=summary)。请注意,如果您启用此选项,您将获得 5-10% 的性能损失。
另一个减少 Java 应用程序内存消耗的有用解决方案是在 java 进程运行时动态调整 JVM 可管理选项。自 JDK7u60 和 JDK8u20 以来,选项 MinHeapFreeRatio 和 MaxHeapFreeRatio 变得易于管理,这意味着我们可以在运行时更改它们的值,而无需重新启动 Java 进程。
在 Runtime Committed Heap Resizing 一文中,作者描述了如何通过调整这些可管理的选项来减少内存使用:
“……调整大小又成功了一次,堆容量从 159MB 增加到 444MB。我们描述了至少 85% 的堆容量应该是空闲的,这指示 JVM 调整堆的大小以获得最多 15% 的使用率。”
这种方法可以为可变工作负载带来显着的资源使用优化。改进 JVM 内存大小的下一步可以是允许在运行时模式下更改 Xmx,而无需重新启动 Java 进程。
在许多情况下,客户希望尽量减少 Java 应用程序中使用的内存量,从而导致更频繁的 GC。例如,它可以通过在开发、测试和构建环境以及负载高峰后的生产环境中更有效地利用资源来帮助节省资金。然而,根据官方增强票据,当前的 GC 算法需要多个完整的垃圾收集周期来释放所有空闲未使用的内存。
因此,引入了一个新的 JVM 选项 (-XX:+ShrinkHeapInSteps) 来规范 JDK9 中的 GC 算法行为。此设置应更改为 -XX:-ShrinkHeapInSteps 以禁用 4 个完整的 GC 周期。这将更快地释放未使用的 RAM 资源,并最大限度地减少具有可变负载的应用程序中的 Java 堆大小使用。
内存消耗大的 Java 应用程序的实时迁移需要大量时间。为了减少总迁移时间和资源开销,迁移引擎应尽量减少主机之间传输的数据。这可以通过在实时迁移过程之前借助完整的 GC 周期压缩 RAM 来完成。对于各种应用程序来说,这种方法可以更经济高效地克服 GC 周期期间的性能下降,而不是使用未打包的 RAM 进行迁移。
我们找到了与此主题相关的出色研究工作:GC-assisted JVM Live Migration for Java Server Applications。 作者将 JVM 与 CRIU(Checkpoint/Restore In Userspace)相结合,并引入了一种新的 GC 逻辑,以减少 Java 应用程序从一台主机实时迁移到另一台主机的时间。所提供的方法允许在拍摄 Java 进程状态快照之前启用迁移感知垃圾收集,然后通过在磁盘上设置检查点来冻结正在运行的容器,然后从冻结点恢复容器。
此外,Docker 社区正在整合 CRIU 成为主流。目前此功能仍处于试验阶段。
两者(Java 和 CRIU)的结合可以释放尚未发现的性能和部署优化机会,以改进云中的 Java 应用程序托管。您可以在文章“容器实时迁移:幕后”中找到容器实时迁移如何在云中工作的更多细节。
Java 很棒并且已经在云中很好地工作,特别是在容器中,但我们相信它可以做得更好。因此,在本文中,我们介绍了一组当前问题,这些问题已经可以改进以平稳高效地运行 Java 应用程序。
在 Jelastic,我们在全球数百个数据中心运行着数千个 Java 容器。良好的内存管理对我们至关重要。这就是为什么我们不断将有关 Java 内存的新发现整合到我们的平台中,因此开发人员不必明确处理这些问题。 在 Jelastic 增强平台上试验运行您的 Java 容器。
标签2: Java教程地址:https://www.cundage.com/article/jcg-java-ram-usage-containers-top-5-tips-not-lose-memory.html