4、堆内存调优实战:-Xms、-Xmx、-Xmn、-XX:SurvivorRatio等参数配置与案例分析

堆内存调优,说白了就是给JVM“分地盘”。

分大了浪费,分小了频繁GC。我刚开始做调优时,也踩过不少坑。今天咱们就聊聊这几个核心参数:-Xms-Xmx-Xmn-XX:SurvivorRatio

4.1 -Xms 和 -Xmx:堆的“下限”与“上限”

这两个参数,一个管初始堆大小,一个管最大堆大小。

我个人习惯,生产环境一定把-Xms-Xmx设成一样。为什么?

你想想看,如果初始堆只有256MB,最大堆是2GB。JVM在运行中会不断申请内存,直到达到上限。这个过程会触发多次Full GC,性能损耗很大。

核心原则:生产环境 -Xms = -Xmx,避免动态扩容带来的性能抖动。

举个例子:

java -Xms2g -Xmx2g -jar myapp.jar

这样JVM启动时就分配好2GB堆内存,运行中不再调整大小。GC压力会小很多。

小技巧:如果服务器内存充裕,可以适当调大 -Xmx。我一般留出系统和其他进程所需内存后,剩余的都分给JVM。

4.2 -Xmn:年轻代的大小博弈

-Xmn控制年轻代大小。年轻代越大,对象在GC前存活时间越长,但老年代空间就小了。

我记得有一次线上OOM,排查下来就是年轻代设得太大,老年代只剩几百MB,大对象一进来就撑爆了。

一般建议:

  • 年轻代占堆的1/3到1/2
  • 如果应用创建大量临时对象,可以适当调大
  • 如果老年代频繁Full GC,考虑缩小年轻代
java -Xms4g -Xmx4g -Xmn1.5g -jar myapp.jar

这里年轻代1.5GB,老年代2.5GB。比例还算合理。

注意:-Xmn设得太大,会导致老年代过小,大对象直接晋升老年代,引发Full GC。我曾经遇到过年轻代占堆70%的情况,结果老年代频繁GC,吞吐量直接腰斩。

4.3 -XX:SurvivorRatio:Eden与Survivor的分配艺术

年轻代内部,又分为Eden区和两个Survivor区(From和To)。-XX:SurvivorRatio控制Eden与单个Survivor的比例。

默认值是8,即Eden:Survivor = 8:1:1。年轻代总共10份,Eden占8份,每个Survivor占1份。

为什么要调这个?

如果Survivor太小,Minor GC后存活对象放不下,直接晋升老年代。如果太大,Eden区变小,Minor GC更频繁。

我一般这样调:

  • 对象存活率低(大部分在Minor GC后死亡),保持默认8:1:1
  • 对象存活率高,可以调小SurvivorRatio,比如6:1:1
  • 如果频繁出现晋升失败,检查Survivor是否过小
java -Xms4g -Xmx4g -Xmn1.5g -XX:SurvivorRatio=6 -jar myapp.jar

这样Eden:Survivor = 6:1:1,Eden约1.125GB,每个Survivor约187.5MB。

经验之谈:我习惯用jstat观察Survivor使用率。如果每次Minor GC后Survivor占用超过80%,说明比例不合适,需要调大Survivor。

4.4 实战案例分析

说个真实案例。去年我帮一个电商团队调优,他们的订单服务频繁Full GC。

原配置:

-Xms2g -Xmx4g -Xmn1g -XX:SurvivorRatio=8

问题表现:

  • 每10分钟一次Full GC,每次耗时2-3秒
  • 接口响应时间波动大,从50ms到500ms
  • Survivor区使用率经常达到100%

分析过程:

  1. 先看堆内存使用,老年代增长很快
  2. 用jstat看GC日志,发现Minor GC后大量对象晋升老年代
  3. 检查SurvivorRatio,默认8:1:1,Survivor只有100MB左右
  4. 订单对象平均大小约50KB,一次Minor GC存活几百个对象,Survivor根本装不下

优化方案:

-Xms4g -Xmx4g -Xmn2g -XX:SurvivorRatio=4

改动点:

  • 堆内存从2-4G改为固定4G
  • 年轻代从1G扩大到2G
  • SurvivorRatio从8改为4,Eden:Survivor = 4:1:1

结果:

  • Full GC频率从10分钟一次降到2小时一次
  • 接口响应时间稳定在80ms以内
  • Survivor使用率降到40%左右

关键点:调优不是盲目改参数,而是基于数据做决策。GC日志、jstat、jmap这些工具,一定要会用。

4.5 参数配置速查表

参数 作用 建议值
-Xms 初始堆大小 与-Xmx相同
-Xmx 最大堆大小 不超过物理内存的70%
-Xmn 年轻代大小 堆的1/3到1/2
-XX:SurvivorRatio Eden与Survivor比例 默认8,根据存活率调整

嗯,堆内存调优就这些核心点。记住一个原则:先监控,后调优,再验证。别一上来就改参数,容易翻车。

下一章咱们聊聊Native内存,那玩意儿比堆内存更隐蔽,踩坑的人更多。