JVM 如何进行调优,减少 GC 停顿时间?

减少 JVM 的 GC(Garbage Collection)停顿时间是 JVM 调优的一个重要目标,特别是对于延迟敏感型应用(例如,Web 应用、实时交易系统、游戏等)。GC 停顿会导致应用程序暂停响应,影响用户体验。

以下是一些减少 GC 停顿时间的常用方法:

1. 选择合适的垃圾收集器 (Garbage Collector):

  • 低延迟优先:

    • G1 (Garbage-First) GC (推荐):

      • JDK 8 及之后推荐使用 G1 GC 作为默认的垃圾收集器。
      • G1 将堆划分为多个大小相等的区域 (Region),并发地进行垃圾收集,并优先回收垃圾最多的 Region。
      • G1 的目标是提供可预测的停顿时间,可以通过 -XX:MaxGCPauseMillis 参数设置目标停顿时间(默认 200ms)。
      • 适用于大堆内存(例如,几 GB 或更大)和低延迟的场景。
      • 启用: -XX:+UseG1GC
      • 关键参数:
        • -XX:MaxGCPauseMillis=<n>: 设置目标最大 GC 停顿时间。
        • -XX:G1HeapRegionSize=<n>: 设置 Region 的大小(1MB 到 32MB,必须是 2 的幂)。
        • -XX:InitiatingHeapOccupancyPercent=<n>: 设置触发并发标记周期的堆占用率阈值 (默认 45%)。
        • -XX:ConcGCThreads=<n>: 设置并发标记的线程数.
    • ZGC (Z Garbage Collector):

      • JDK 11 引入的实验性垃圾收集器,JDK 15 开始正式可用。
      • ZGC 的目标是实现极低的停顿时间(通常在 10ms 以内,甚至更低),同时保持较高的吞吐量。
      • ZGC 使用了着色指针 (Colored Pointers) 和读屏障 (Load Barriers) 技术,几乎所有 GC 阶段都可以与应用程序线程并发执行。
      • 适用于需要极低延迟的应用,例如金融交易系统、实时游戏等。
      • 启用: -XX:+UseZGC
      • 关键参数:
        • -XX:ConcGCThreads: 设置并发 GC 线程数.
    • Shenandoah GC:

      • OpenJDK 的一个低延迟垃圾收集器,类似于 ZGC。
      • Shenandoah 的大部分 GC 工作可以与应用程序线程并发执行。
      • 适用于需要低延迟的应用。
      • 启用: -XX:+UseShenandoahGC
    • CMS (Concurrent Mark Sweep) GC (已过时):

      • JDK 9 开始被标记为过时 (deprecated),JDK 14 中被移除。
      • CMS 收集器的主要目标是减少老年代垃圾收集的停顿时间。它的大部分工作(并发标记和并发清除)可以与应用程序线程并发执行。
      • 缺点: 会产生内存碎片;可能会发生 Concurrent Mode Failure(退化为 Serial Old GC);需要更多的 CPU 资源。
      • 启用: -XX:+UseConcMarkSweepGC (通常与 -XX:+UseParNewGC 一起使用)
  • 吞吐量优先:

    • Parallel Scavenge GC:
      • Parallel Scavenge 收集器的目标是达到一个可控制的吞吐量(Throughput = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间))。
      • 适用于后台批处理任务、科学计算等对吞吐量要求较高,但对停顿时间不太敏感的场景。
      • 启用: -XX:+UseParallelGC (新生代), -XX:+UseParallelOldGC (老年代)

2. 调整堆内存大小:

  • 增大堆内存 (-Xmx):
    • 优点: 可以减少 GC 的频率。
    • 缺点: 可能会增加单次 GC 的停顿时间。
    • 建议:-Xms (初始堆大小) 和 -Xmx (最大堆大小) 设置为相同的值,避免 JVM 在运行时动态调整堆大小,减少 GC 开销。
  • 调整新生代和老年代的比例 (-XX:NewRatio):
    • 响应时间优先: 可以适当增大新生代大小 (-Xmn),以减少 Minor GC 的频率。
    • 吞吐量优先: 可以适当减小新生代大小,以增加老年代的大小。
  • 调整 Survivor 区的大小 (-XX:SurvivorRatio):
    • Survivor 区用于存放经过 Minor GC 后仍然存活的对象。
    • 如果 Survivor 区太小,对象可能会过早地晋升到老年代,导致老年代 GC 更频繁。
    • 如果 Survivor 区太大,会浪费内存空间。
  • -XX:MaxTenuringThreshold:
    • 设置对象晋升到老年代的年龄阈值 (经历 GC 的次数)。
    • 默认值:15 (CMS 为 6)。
    • 如果设置为 0,则对象不会在 Survivor 区停留,直接晋升到老年代。

3. 优化代码:

  • 减少对象创建:
    • 避免不必要的对象创建,特别是 in loops。
    • 使用对象池复用对象。
    • 使用基本数据类型代替包装类(如果可能)。
    • 使用 StringBuilderStringBuffer 进行字符串拼接。
  • 避免内存泄漏:
    • 及时释放不再使用的对象(将对象的引用设置为 null)。
    • 关闭不再使用的资源(例如,文件、数据库连接、网络连接)。
    • 避免使用静态集合类持有大量对象,并且没有及时清理。
  • 使用弱引用、软引用、虚引用:
    • 对于一些可以被回收的对象,可以使用弱引用、软引用或虚引用,以便垃圾回收器在内存不足时回收这些对象。
  • 避免大对象:
    • 尽量避免创建过大的对象(例如,大数组、大集合)。
    • 如果必须创建大对象,可以考虑将其分配到老年代(-XX:PretenureSizeThreshold)。
  • 减少Full GC次数: Full GC 通常比 Minor GC 停顿时间长很多。

4. 其他优化:

  • 禁用 System.gc() 调用:
    • System.gc() 会触发 Full GC,导致长时间的停顿。
    • 使用 -XX:+DisableExplicitGC 参数禁用 System.gc() 调用。
  • 使用 JVM 内置工具:
  • 使用 jstat, jmap, jstack, jinfo, jcmd 等命令行工具监控和诊断 JVM.
  • 使用 JConsole, VisualVM 等可视化工具监控 JVM.
  • 使用 APM 工具:
    • 使用应用性能管理 (APM) 工具(例如,SkyWalking、Pinpoint、Jaeger、Zipkin、New Relic、AppDynamics)监控应用程序的性能,包括 GC 情况。
  • 分析 GC 日志:
    • 使用 GCViewer、gceasy.io、GChisto 等工具分析 GC 日志,找出 GC 瓶颈。
  • 升级 JDK 版本: 新版本的 JDK 通常会对垃圾回收器进行优化。
  • 避免使用 finalize() 方法: finalize() 方法会影响垃圾回收的性能,应尽量避免使用。

总结:

减少 GC 停顿时间是一个综合性的工作,需要从多个方面入手:

  1. 选择合适的垃圾收集器: 根据应用程序的特点(延迟敏感型还是吞吐量优先型)选择合适的垃圾收集器。
  2. 调整堆内存大小: 根据应用程序的内存需求调整堆大小、新生代大小、Survivor 区大小等。
  3. 优化代码: 减少对象创建、避免内存泄漏、避免大对象、使用弱引用等。
  4. 禁用 System.gc() 调用。
  5. 使用 JVM 内置工具和 APM 工具监控和分析 JVM 性能。
  6. 分析 GC 日志,找出 GC 瓶颈。

需要根据具体情况进行调整和优化,没有一成不变的万能方案。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰糖心书房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值