减少 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(老年代)
- Parallel Scavenge GC:
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。
- 使用对象池复用对象。
- 使用基本数据类型代替包装类(如果可能)。
- 使用
StringBuilder或StringBuffer进行字符串拼接。
- 避免内存泄漏:
- 及时释放不再使用的对象(将对象的引用设置为
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 停顿时间是一个综合性的工作,需要从多个方面入手:
- 选择合适的垃圾收集器: 根据应用程序的特点(延迟敏感型还是吞吐量优先型)选择合适的垃圾收集器。
- 调整堆内存大小: 根据应用程序的内存需求调整堆大小、新生代大小、Survivor 区大小等。
- 优化代码: 减少对象创建、避免内存泄漏、避免大对象、使用弱引用等。
- 禁用
System.gc()调用。 - 使用 JVM 内置工具和 APM 工具监控和分析 JVM 性能。
- 分析 GC 日志,找出 GC 瓶颈。
需要根据具体情况进行调整和优化,没有一成不变的万能方案。
14万+

被折叠的 条评论
为什么被折叠?



