吞吐量、延迟和整体应用程序性能的提升是考虑在现代 Java 部署中使用分代 ZGC 的有力理由。当存在高时间对象分配时尤其如此。
管理临时对象是 Java 虚拟机优化中的一个关键性能因素。随着Java 21中分代 ZGC(Z 垃圾收集器)的引入(现已在 Java 22 中提供),我们进入了内存管理的新时代,可以有效地处理短暂生存的对象。
在本教程中,我们将详细探讨分代 ZGC,并讨论它如何增强临时对象的管理。我们将展示它如何有助于提高 JVM 性能的整体水平。最后,我们将分享一个可以从我们新获得的知识中受益的实际用例。
Java 中的临时对象
临时或短暂的对象是在程序执行期间创建的具有较短生命周期的对象。
一般来说,此类对象是中间结果、方法参数或临时数据结构。在许多 Java 程序中,大部分对象都属于此类。在涉及频繁方法调用、临时计算或快速数据转换的应用程序中尤其如此。
举个例子,我们将考虑 Java 中临时对象的生命周期,特别是在 Web 请求的上下文中。这包括在客户端与服务器交互期间的创建、处理和最终处置。
- 客户端首先通过向服务器发送 HTTP 请求与服务器通信,该请求提示服务器生成要处理的临时请求对象。
- 在服务器处理请求时,它可能会创建其他临时对象以进行计算和数据管理(例如访问或修改作为临时对象保存的会话数据)。
- 完成后,服务器将 HTTP 响应返回到封装在临时响应对象中的客户端。最后,一旦收到响应,服务器就会销毁请求期间生成的临时对象以释放内存。
ZGC 的演变
从 Java 11 开始引入的Z 垃圾收集器是JVM 垃圾收集演进的最新里程碑。ZGC 具有低延迟和可扩展性特性,是低延迟服务器工作负载的理想选择。
随后的每个 Java 版本都通过额外的优化增强了 ZGC。2023 年 4 月,在预览之后,Java 21 中推出了 Generational ZGC,以改进临时对象管理。
分代 ZGC 的工作原理
分代 ZGC 将堆分为两段:
- 一段用于新创建的对象(年轻代),
- 另一段用于寿命较长的对象(老生代)。
对象分配和年轻代收集
年轻对象最初分配在 Z 代垃圾收集器 (ZGC) 的年轻代中。这是为短命对象量身定制的内存区域。
年轻代的回收频率比老代高得多。这意味着可以快速从不再使用的对象中回收内存。
优化并发收集
新一代的收集过程使用并发标记和撤离技术。这种双重方法允许 ZGC 在应用程序运行时标记有资格回收的对象。
减少应用程序线程的重复中断可降低应用程序延迟并提高性能灵敏度。
对象提升和动态阈值
在年轻代中存活了几个回收周期后,对象可以被提升到老生代。这种机制将寿命短的对象与可能存活较长的对象隔离开来。ZGC 根据观察到的对象寿命分布动态调整寿命阈值。
提升阈值决定何时应将对象从年轻代提升到老代。这种灵活性通过仅提升那些需要长期存储的对象来进一步帮助优化内存使用率。
老生代收集
老生代的回收频率低于年轻代,因为它主要包含经过多次垃圾回收周期而存活下来的长寿命对象。老生代中的回收旨在从未使用的对象中回收内存。
由于来自其他应用程序部分的引用,这些对象可能在内存中停留更长时间。老一代收集与年轻一代收集类似,也使用并发技术。这可确保在垃圾收集器回收未使用的内存时,应用程序线程继续以最小的中断运行。
理解分代 ZGC 的机制
理解分代 ZGC 的机制:
- 对象分配在年轻代中,年轻代经常使用并发标记和撤离技术进行收集。这种频繁分配意味着我们可以快速从临时对象中回收内存,这有助于保持低延迟和高性能。
- 那些在多次回收中幸存下来的对象将被移至老生代,其中内存回收频率较低。该算法会根据对象生命周期自动调整提升阈值,从而优化长寿命对象的内存管理。
临时对象的性能影响
分代 ZGC 的引入对于创建许多临时对象的应用程序具有重要意义。
1. 减少 GC 开销
分代 ZGC 为 Java 应用程序带来了高性能提升,尤其是在管理临时对象方面。因此,GC 开销更少。
通过在年轻代(大多数短命对象都位于年轻代)执行工作,分代 ZGC 可以快速回收内存,而无需执行昂贵的全堆收集。这样可以减少垃圾收集所花费的时间,并将更多时间用于实际应用程序处理。
2. 提高吞吐量
分代 ZGC 的另一个好处是提高了吞吐量。内部基准测试表明,与非分代前代产品相比,吞吐量提高了 10%。
处理大量数据或处理大量并发操作的应用程序将从这一改进中受益最多。因此,应用程序可以在更短的时间内处理更多的工作,而且通常不需要额外的计算资源。
3. 减少暂停时间
在 P99暂停时间方面,分代 ZGC 可将应用程序响应速度提高约 10% -20% 。对于低延迟或恒定响应时间是关键成功因素的应用程序来说,减少暂停时间非常重要。
对于股票市场套利的实时交易系统或对延迟敏感的 Web 服务等应用程序来说,情况确实如此。在这种情况下,最小化暂停时间的 Stop-the-world 收集器对于保持应用程序响应能力至关重要。这在高负载或高峰使用期间尤其重要。
4. 高效内存回收
由于分代 ZGC 在回收内存方面比任何其他 GC 方法都更有效,因此它降低了在高分配负载下出现内存不足错误的可能性。通过密切跟踪短命对象的生命周期,GC 可以更积极地提升它们。
这样可以更快地回收未使用的内存,从而腾出空间用于新分配。此行为对于分配率高且创建大量临时对象的应用程序尤其重要。它有助于保持 GC 的延迟影响可预测,并防止因内存耗尽而导致崩溃。
下表总结了分代 ZGC 对临时对象的性能影响:
- 减少 GC 开销 分代 ZGC 通过重点关注年轻代中的短命对象来快速回收内存 减少垃圾收集所花的时间,增加应用程序所用的时间
- 提高吞吐量 与上一代产品相比,吞吐量提高了 10% 应用程序可以在更短的时间内处理更多的工作
- 减少暂停时间 低延迟应用程序的 P99 暂停时间减少 10-20% 提高应用程序响应能力,这对于实时应用程序至关重要
- 高效内存回收 有效的内存回收可减少内存不足错误 帮助管理高分配率,防止内存耗尽
用例:使用实时数据的高频交易
在高频交易(HFT)中,速度至关重要。每秒处理数百万个市场数据点,生成交易,系统执行快速计算。此处理提供临时对象,例如交易订单和市场快照,这些对象可能只存在很短的时间。
分代 ZGC 几乎在这些短暂的对象被创建后立即收集它们,有助于避免内存开销。
ZGC 引擎在后台运行,不会因交易而暂停。电子交易中哪怕是微秒的暂停都可能意味着错失有利可图的交易。
由于数据流入速度很快,系统必须处理大量数据而不减慢速度。分代 ZGC 会以临时对象积累的速度清除它们,确保内存为下一轮计算做好准备。因此,即使市场变得繁忙,系统也能继续前进。
高频交易平台每秒钟都会产生数百万个对象,内存管理必须非常高效,以免系统内存耗尽而崩溃。
随着短期对象被丢弃,ZGC 不断释放空间。
随着交易量的增加,系统必须处理更多数据,同时又不影响性能。该平台可通过分代 ZGC 轻松扩展,即使工作负载增加一倍或三倍,也能高效地使用内存。
针对分代 ZGC 优化应用程序
我们应该遵循一些最佳实践来充分利用 Generational ZGC 的潜力,尤其是在处理临时对象时。
1. 重新评估激进对象池
对象池曾经是最常见的优化方式,即重用对象而不是创建新对象,以优化内存和 GC 消耗。但是,由于分代 ZGC 可以有效地处理临时对象,因此激进的对象池可能不再是必要的,甚至可能导致性能问题。
2. 识别对象分配模式
分析工具有助于识别代码中生成过多临时对象的部分。可视化此过程有助于理解和优化应用程序。我们可以使用JDK Mission Control、VisualVM 或 JProfiler 等分析工具来收集实时对象分配。
开发人员启动分析来监控对象分配,促使分析器检测应用程序代码并收集数据。然后,开发人员分析报告以识别热点并相应地优化代码。
3. 调整分代 ZGC 的堆大小
考虑堆大小调整非常重要。我们可以调整堆大小,以找到适合特定工作负载的年轻代和老年代大小之间的适当平衡。
我们首先使用默认堆大小运行应用程序,并监控性能指标以检查是否存在高临时对象分配。如果检测到高分配,我们将调整年轻代大小并重新运行应用程序。我们继续此过程,反复优化堆大小,直到确定最佳配置。
4. 监控和调整垃圾收集
持续监控有助于微调 GC 行为以提高应用程序性能。JDK Flight Recorder (JFR) 和 J DK Mission Control (JMC) 等工具可以帮助监控 GC 行为并提高性能。
开发人员启用 GC 日志记录并启动应用程序,以便记录和分析垃圾收集事件以识别性能问题。根据此分析,开发人员调整 GC 参数并重新启动应用程序以应用优化。
未来方向
随着 Generational ZGC 的成熟,我们可以期待进一步的优化和功能:
- 自适应代大小调整——基于应用程序行为动态调整年轻代和老代大小的更复杂算法
- 增强的预测收集——改进的启发式方法,用于预测何时在每一代触发收集
- 与 JIT 优化集成 -加强 GC 和JIT 编译器之间的合作,以实现更好的对象分配和管理
- 针对特定对象类型的专门处理——针对 Java 应用程序中常见的短暂对象模式进行潜在优化