前段时间生产环境一个Java应用出现了服务不可用问题,通过日志发现原来是Full GC时间过长(21.40秒),导致服务不可用。
GC日志:
[Full GC (Ergonomics) [PSYoungGen: 22184K->0K(3463168K)]
[ParOldGen: 6989862K->701860K>(6990848K)] 7012047K->701860K(10454016K),
[Metaspace: 208715K->207356K(1255424K)], 21.4039625 secs]
[Times: user=40.17 sys=114.15, real=21.40 secs]
JDK1.8垃圾回收默认使用Parallel算法。Parallel算法优点是吞吐量高,并发性。缺点也明显,暂停时间长(stop the world),对于响应时间敏感的应用来说不合适。
如何选择GC算法
GC 算法主要关注三个指标:
- 吞吐量:长时间内未用于 GC 的总时间百分比。
- 延迟:应用程序的整体响应能力,受 GC 暂停的影响。
- 占用空间:进程的工作集,以页面和缓存行来衡量。
和很多问题一样,你没办法“全都要”,需要根据应用特点做选择。
垃圾收集器 | 场景 |
---|---|
Serial | - 小型数据集(最大约 100 MB) - 资源有限(例如单核) - 暂停时间短 |
Parallel | - 多核系统上的峰值性能 - 非常适合高计算负载 - > 1 秒的暂停是可以接受的 |
G1 CMS | - 响应时间 > 吞吐量 - 大堆 - 暂停时间 < 1 秒 |
Shenandoah | - 尽量减少暂停时间 - 可预测的延迟 |
ZGC | - 响应时间是高优先级,和/或 - 非常大的堆 |
Epsilon GC | - 性能测试和故障排除 |
由上面表格对比,我们需要响应优先,暂停时间短的算法,JDK1.8可以支持G1算法
启动时使用G1
java -XX:+UseG1GC
打印相关GC日志
java -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintGCDateStamps -Xloggc:gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=10M -jar app.jar
总结
实际测试后服务没有再出现类似延迟问题,G1还是很好用的。另外建议新的项目使用更高Java版本,比如Java21,目前也已用到生产环境,没必要守着1.8不放。
参考: