4. JVM参数调优(Java):堆内存结构,垃圾回收器选择(G1、ZGC),堆大小与新生代比例
聊到Java性能调优,JVM这块是绕不开的。说实话,很多新手觉得JVM参数就是照着网上的模板抄一抄,但实际跑起来该崩还是崩。我个人的习惯是,先搞清楚堆内存到底怎么分的,再谈参数怎么设。
4.1 堆内存结构:别被“堆”字骗了
堆不是一整块大平地。它分了好几层,每一层干的事都不一样。
核心结构:
- 新生代(Young Generation):新对象出生地。分Eden、Survivor0、Survivor1。
- 老年代(Old Generation):熬过多次GC的对象,或者大对象直接进来。
- 元空间(Metaspace):存类信息、方法数据。注意,它不在堆里,用的是本地内存。
我记得有一次线上服务频繁Full GC,一看监控,老年代一直在涨。查了半天,发现是某个缓存对象没设过期时间,全堆在老年代里了。嗯,这就是典型的“对象晋升太快”。
为什么会这样?说白了,新生代太小,对象还没活够就被挤到老年代了。老年代一满,Full GC就来了。
4.2 垃圾回收器选择:G1 还是 ZGC?
选垃圾回收器,说白了就是看你的应用要什么。要低延迟?要高吞吐?还是两者都要?
| 回收器 | 适用场景 | 特点 | 我踩过的坑 |
|---|---|---|---|
| G1 | 大堆(4GB+),可预测停顿 | 分区式,并发标记,可设目标停顿时间 | 曾经设了-XX:MaxGCPauseMillis=10ms,结果GC频率飙升,CPU打满 |
| ZGC | 超大堆(几百GB),亚毫秒级停顿 | 染色指针,并发处理,几乎不停顿 | JDK 11刚出时试过,兼容性有点问题,现在好多了 |
| Parallel | 批处理、科学计算 | 高吞吐,但停顿时间长 | 适合离线任务,线上服务慎用 |
我的建议:
- 堆小于4GB,用Parallel GC,简单粗暴。
- 堆4GB~64GB,用G1,调好停顿时间。
- 堆64GB以上,或者要求延迟极低,上ZGC。
你想想看,ZGC号称停顿不超过10ms,但代价是什么?CPU占用会高一些。我有个项目,堆内存128GB,用了ZGC后,GC停顿从原来的200ms降到了2ms,但CPU从30%涨到了45%。值不值?看你的业务。
4.3 堆大小与新生代比例:别瞎调
堆大小设多少?新生代占多少?这是调优里最基础也最容易翻车的地方。
堆大小设置
- -Xms:初始堆大小。我习惯设成和-Xmx一样,避免运行时动态扩容。
- -Xmx:最大堆大小。别超过物理内存的70%,要给操作系统留点余量。
注意:我曾经在一个4GB内存的机器上设了-Xmx3.5GB,结果系统直接OOM killer把Java进程杀了。留点内存给系统,别贪心。
新生代比例
新生代太小,对象频繁晋升到老年代,Full GC频繁。新生代太大,GC停顿时间变长。怎么平衡?
| 参数 | 作用 | 推荐值 |
|---|---|---|
| -XX:NewRatio | 老年代:新生代比例 | 2~3(老年代是新生代的2~3倍) |
| -XX:SurvivorRatio | Eden:Survivor比例 | 8(Eden是单个Survivor的8倍) |
| -XX:MaxTenuringThreshold | 对象晋升老年代的年龄阈值 | 6~15(默认15,可适当调低) |
举个例子。我有个Web服务,堆内存8GB,设了-XX:NewRatio=2,新生代大概2.6GB。跑了一段时间,发现Minor GC频率还是高。后来把NewRatio改成3,新生代降到2GB,Minor GC少了,但老年代增长变快了。嗯,这就是在找平衡点。
实战参数示例(G1回收器):
-Xms8g -Xmx8g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=100
-XX:NewRatio=3
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=6
-XX:+PrintGCDetails -XX:+PrintGCDateStamps
-Xloggc:/path/to/gc.log
为什么要设MaxGCPauseMillis=100?默认是200ms。我个人习惯先设100ms,跑一段时间看GC日志,如果停顿时间达标,就保持;如果频繁触发GC,就适当放宽到150ms。
调优小技巧:
- 先看GC日志,别凭感觉调参数。
- 每次只改一个参数,观察效果。
- 用jstat、jvisualvm等工具实时监控。
- 别迷信网上的“万能参数”,每台机器、每个应用都不一样。
最后说一句,JVM调优不是一锤子买卖。应用上线后,随着流量变化、代码迭代,参数可能需要重新调整。保持监控,持续优化,这才是正道。
可可精品资料库