使用正确的垃圾收集器最大限度地减少 Java 内存使用

位置:首页>文章>详情   分类: Java教程 > 编程技术   阅读(85)   2024-04-20 16:21:30

就软件而言,大小很重要。很明显,与大单体方法相比,在微服务架构中使用小块可以带来更多优势。 Jigsaw 的最新 Java 版本有助于分解遗留应用程序或从头构建新的云原生应用程序。

这种方法减少了磁盘空间、构建时间和启动时间。但是,它对 RAM 使用管理没有足够的帮助。众所周知,Java 在很多情况下都会消耗大量的内存。与此同时,许多人没有注意到 Java 在内存使用方面变得更加灵活,并提供了满足微服务要求的功能。在本文中,我们将分享我们的经验,如何调整 Java 进程中的 RAM 使用以使其更具弹性并获得更快扩展和更低总拥有成本 (TCO) 的好处。

有三种缩放选项:垂直、水平和两者的组合。为了最大化结果,首先您需要以最佳方式设置垂直缩放。然后,当你的项目横向增长时,单个容器内预配置的资源消耗将被复制到每个实例中,效率将成比例增长。

如果配置得当,垂直扩展对微服务和整体应用都非常有效,可以根据容器内的当前负载优化内存和 CPU 使用率。选择的垃圾收集器是主要的基础砖之一,其设置可以影响整个项目。

OpenJDK 有五种广泛使用的垃圾收集器解决方案:

  • G1
  • 并行
  • ConcMarkSweep(内容管理系统)
  • 连续剧
  • 谢南多厄

让我们看看它们在缩放方面的表现如何,以及可以应用哪些设置来改善结果。

为了进行测试,我们将使用有助于跟踪 JVM 垂直缩放结果的示例 Java 应用程序: https://github.com/jelastic/java-vertical-scaling-test

每次 GC 测试都会启动以下 JVM 启动选项:

java -XX:+Use[gc_name]GC -Xmx2g -Xms32m -jar app.jar [sleep]

在哪里:

  • [gc_name] 将替换为特定的垃圾收集器类型
  • Xms 是一个缩放步骤(在我们的例子中是 32 MB)
  • Xmx 是最大缩放限制(在我们的例子中为 2 GB)
  • [sleep] 是内存加载周期之间的间隔,以毫秒为单位,默认为 10

目前,需要调用 Full GC 才能正确释放未使用的资源。它可以通过各种选项轻松启动:

  • jcmd <pid> GC.run – 执行外部调用
  • System.gc() – 在源代码里面
  • jvisualvm – 通过出色的 VisualVM 故障排除工具手动操作
  • -javaagent:agent.jar – 可插入的常用方法。 Github 存储库 Java Memory Agent 提供了开源自动化插件。

可以在输出日志中跟踪内存使用情况,或使用 VisualVM 进行更深入的审查。

G1 垃圾收集器

对于 Java 生态系统来说,好消息是从 JDK 9 开始,默认启用现代收缩 G1 垃圾收集器。如果您使用较低版本的 JDK,可以使用 -XX:+UseG1GC 参数启用 G1。

G1 的主要优势之一是能够压缩可用内存空间,而无需长时间暂停和取消提交未使用的堆。我们发现此 GC 是垂直扩展在 OpenJDK 或 HotSpot JDK 上运行的 Java 应用程序的最佳选择。

为了更好地了解 JVM 在不同内存压力水平下的行为,我们将运行三种情况:1) 快速,2) 中等,以及 3) 缓慢的内存使用增长。通过这种方式,我们可以检查 G1 ergonomic 的智能程度以及 GC 如何处理不同的内存使用动态。

快速内存使用增长

java -XX:+UseG1GC -Xmx2g -Xms32m -jar app.jar 0

内存在 25 秒内从 32 MiB 增长到 1 GiB。

G1 快速内存使用增长

如果内存使用量增长非常快,JVM 人体工程学会忽略 Xms 缩放步骤,并根据其内部自适应优化算法更快地保留 RAM。因此,相对于快速的实际使用量(蓝色)增长,我们看到 JVM(橙色)的 RAM 分配要快得多。因此,使用 G1 我们是安全的,即使在负载峰值的情况下也是如此。

中等内存使用增长

java -XX:+UseG1GC -Xmx2g -Xms32m -jar app.jar 10

在 4 个周期内,内存在 90 秒内从 32 MiB 增长到 1 GiB。

有时 JVM 人体工学需要几个周期才能找到最佳的 RAM 分配算法。

G1 中等内存使用增长

正如我们所见,JVM 在第 4th 周期调整了符合人体工程学的 RAM 分配,使垂直扩展对于可重复周期非常有效

内存使用增长缓慢

java -XX:+UseG1GC -Xmx2g -Xms32m -jar app.jar 100

内存从 32 MiB 增长到 1 GiB,增量时间增长大约 300 秒。非常灵活和高效的资源扩展,满足我们的期望——令人印象深刻。

G1 内存使用增长缓慢

正如您所看到的,橙色区域(保留 RAM)随着蓝色区域(实际使用)的增长而缓慢增加。因此不会过度使用或不必要地保留内存。

重要:激进的堆或垂直扩展

用于提高 Java 应用程序性能的流行 JVM 配置通常会阻碍有效垂直扩展的能力。因此,您需要在对您的应用程序最重要的属性的优先级之间进行选择。

许多广泛使用的设置之一是激活 Aggressive Heap 以尝试最大限度地利用堆的物理内存。让我们分析一下使用此配置时会发生什么。

java -XX:+UseG1GC -Xmx2g -Xms2g

或者

java -XX:+UseG1GC -Xmx2g -XX:+AggressiveHeap

G1 激进堆

正如我们所见,保留堆(橙色)是恒定的,不会随时间变化,因此容器中的 JVM 没有垂直扩展。即使您的应用程序只使用了可用 RAM 的一小部分(蓝色),其余部分也无法与其他进程或其他容器共享,因为它已完全分配给 JVM。

因此,如果您想垂直扩展您的应用程序,请确保未启用积极堆(参数应为 -XX:-AggressiveHeap),也不要将 -Xms 定义为 -Xmx(例如,不要声明 -Xmx2g -Xms2g)。

并行垃圾收集器

Parallel 是一种高吞吐量的 GC,在 JDK8 中默认使用。同时,它不适合内存收缩,不适合灵活的垂直缩放。为了确认这一点,让我们用我们的示例应用程序运行一个测试:

java -XX:+UseParallelGC -Xmx2g -Xms32m -jar app.jar 10

并行垃圾收集器

正如我们所见,未使用的 RAM 不会释放回操作系统。具有并行 GC 的 JVM 会永远保留它,甚至忽略显式的 Full GC 调用。

因此,如果您想根据应用程序负载从垂直扩展中获益,请将并行更改为 JDK 中可用的收缩 GC。它会将所有活动对象打包在一起,删除垃圾对象,取消提交并将未使用的内存释放回操作系统。

Serial 和 ConcMarkSweep 垃圾收集器

Serial 和 ConcMarkSweep 也在缩小垃圾收集器,并且可以垂直扩展 JVM 中的内存使用。但与 G1 相比,它们需要 4 个 Full GC 周期来释放所有未使用的资源。

让我们看看这两个垃圾收集器的测试结果:

java -XX:+UseSerialGC -Xmx2g -Xms32m -jar app.jar 10

串行垃圾收集器

java -XX:+UseConcMarkSweepGC -Xmx2g -Xms32m -jar app.jar 10

ConcMarkSweep 垃圾收集器

从 JDK9 开始,可以使用新的 JVM 选项 -XX:-ShrinkHeapInSteps 加速内存释放,该选项会在第一个 Full GC 周期后立即关闭已提交的 RAM。

Shenandoah 垃圾收集器

Shenandoah 是垃圾收集器中一颗冉冉升起的新星,已经被认为是 JVM 垂直扩展的最佳未来解决方案。

与其他方法相比,主要区别在于无需调用 Full GC 即可异步收缩(取消提交并将未使用的 RAM 释放给操作系统)的能力。 Shenandoah 几乎可以在检测到空闲内存后立即压缩活动对象、清理垃圾并将 RAM 释放回操作系统。并且省略 Full GC 的可能性导致消除相关的性能下降。

让我们看看它在实践中是如何工作的:

java -XX:+UseShenandoahGC -Xmx2g -Xms32m -XX:+UnlockExperimentalVMOptions -XX:ShenandoahUncommitDelay=1000 -XX:ShenandoahGuaranteedGCInterval=10000 -jar app.jar 10

这里我们添加了一些在 Shenandoah 中可用的额外参数:

  • -XX:+UnlockExperimentalVMOptions – 需要启用下面列出的取消提交选项
  • -XX:ShenandoahUncommitDelay=1000 ——垃圾收集器将开始取消提交未使用时间超过此时间(以毫秒为单位)的内存。请注意,当应用程序想要取回内存时,将延迟设置得太低可能会导致分配停顿。在实际部署中,建议将延迟设置为大于 1 秒
  • -XX:ShenandoahGuaranteedGCInterval=10000 - 这保证了 GC 周期在规定的时间间隔内(以毫秒为单位)

Shenandoah 垃圾收集器

Shenandoah 非常有弹性,只分配必要的资源。它还会压缩已使用的 RAM(蓝色)并将未使用的保留 RAM(橙色)动态释放回操作系统,而无需昂贵的 Full GC 调用。请注意此 GC 是实验性的,因此您对稳定性的反馈将对其创建者有所帮助。

总结

Java 不断完善并适应不断变化的需求。因此,目前它的 RAM 胃口不再是传统应用程序的微服务和云托管的问题,因为已经有合适的工具和方法来适当地扩展它、清理垃圾并释放所需进程的资源。通过巧妙配置,Java 可以对所有范围的项目都具有成本效益——从云原生初创公司到遗留企业应用程序。

标签2: Java教程
地址:https://www.cundage.com/article/jcg-minimize-java-memory-usage-right-garbage-collector.html

相关阅读

Java HashSet 教程展示了如何使用 Java HashSet 集合。 Java哈希集 HashSet 是一个不包含重复元素的集合。此类为基本操作(添加、删除、包含和大小)提供恒定时间性...
SpringApplicationBuilder 教程展示了如何使用 SpringApplicationBuilder 创建一个简单的 Spring Boot 应用程序。 春天 是用于创建企业应...
通道是继 buffers 之后 java.nio 的第二个主要新增内容,我们在之前的教程中已经详细了解了这一点。通道提供与 I/O 服务的直接连接。 通道是一种在字节缓冲区和通道另一端的实体(通...
课程大纲 Elasticsearch 是一个基于 Lucene 的搜索引擎。它提供了一个分布式的、支持多租户的全文搜索引擎,带有 HTTP Web 界面和无模式的 JSON 文档。 Elasti...
解析器是强大的工具,使用 ANTLR 可以编写可用于多种不同语言的各种解析器。 在这个完整的教程中,我们将: 解释基础:什么是解析器,它可以用来做什么 查看如何设置 ANTLR 以便在 Java...
Java 是用于开发各种桌面应用程序、Web 应用程序和移动应用程序的最流行的编程语言之一。以下文章将帮助您快速熟悉 Java 语言,并迈向 API 和云开发等更复杂的概念。 1. Java语言...
Java中的继承是指子类继承或获取父类的所有非私有属性和行为的能力。继承是面向对象编程的四大支柱之一,用于提高层次结构中类之间的代码可重用性。 在本教程中,我们将了解 Java 支持的继承类型,...
Java Message Service 是一种支持正式通信的 API,称为 网络上计算机之间的消息传递。 JMS 为支持 Java 程序的标准消息协议和消息服务提供了一个通用接口。 JMS 提...
Java 项目中的一项常见任务是将日期格式化或解析为字符串,反之亦然。解析日期意味着你有一个代表日期的字符串,例如“2017-08-3”,你想把它转换成一个代表 Java 中日期的对象,例如Ja...
之前,我介绍了spring 3 + hibernate 集成 示例和struts 2 hello world 示例。在本教程中,我将讨论在将 spring 框架与 struts 与 hibern...