2. 启动参数基础
聊 JVM 参数,绕不开的就是内存。我见过太多团队,上来就抄网上的配置,结果线上频繁 Full GC,或者直接 OOM。说白了,堆、栈、元空间这三块,是 JVM 性能的基石。今天咱们就把它们掰开揉碎了讲清楚。
堆内存:-Xms 与 -Xmx
堆是 Java 对象的老家。几乎所有对象都在这里分配。-Xms 是初始堆大小,-Xmx 是最大堆大小。这两个参数,我建议你一定要设成一样。
核心原则:-Xms = -Xmx
为什么?避免运行时动态扩容。JVM 在堆不够用时会申请更多内存,这个过程会触发 STW(Stop-The-World)。你想想看,线上服务正跑着,突然卡一下去扩内存,多冤枉。
我在项目中遇到过这样一个案例:某团队把 -Xms 设为 2G,-Xmx 设为 4G。平时流量低时还好,一到促销高峰,堆内存从 2G 往 4G 涨,每次扩容都导致接口响应时间飙升。后来改成固定 4G,问题就消失了。
具体怎么设?看业务场景:
| 场景 | 推荐堆大小 | 说明 |
|---|---|---|
| 高并发 Web 服务 | 4G - 8G | 对象多,吞吐量要求高 |
| 批处理/大数据 | 8G - 32G | 大对象多,注意 GC 停顿 |
| 微服务/轻量应用 | 512M - 2G | 够用就好,别浪费 |
| 开发/测试环境 | 256M - 1G | 快速启动,方便调试 |
小技巧:堆大小不要超过物理内存的 70%。留点空间给操作系统、元空间和堆外内存。我曾经见过有人把堆设到 32G,结果机器 48G 内存,Swap 直接爆炸。
栈大小:-Xss
每个线程都有自己的栈,用来存局部变量、方法调用链、操作数栈等。-Xss 就是设置每个线程栈的大小。
这个参数很有意思。设大了,线程数上不去;设小了,容易栈溢出。我建议你根据应用类型来权衡:
- 默认值:Linux x64 下通常是 1M,Windows 下是 512K
- 高并发服务:256K - 512K。线程数多,栈小一点能省大量内存
- 深度递归/复杂计算:1M - 2M。防止 StackOverflowError
避坑指南:我曾经接手过一个项目,线上频繁报 StackOverflowError。查了半天,发现是某个递归算法没写好,但更根本的原因是 -Xss 被设成了 128K。对于正常的业务逻辑,256K 是底线。低于这个值,即使代码没问题,也可能因为 JVM 自身的方法调用而溢出。
举个例子,假设你的应用需要 500 个线程:
# 栈大小 1M:500 个线程占用 500M 栈内存
-Xss1M
# 栈大小 256K:500 个线程只占 128M
-Xss256K
省下来的 372M 可以给堆用。你想想看,是不是很划算?
元空间:-XX:MetaspaceSize
元空间取代了 JDK 8 之前的永久代(PermGen)。它存的是类的元数据、方法、字段信息等。和堆不同,元空间默认使用本地内存(Native Memory),理论上只受操作系统限制。
但这里有个坑:-XX:MetaspaceSize 不是最大值,而是触发 Full GC 的阈值。
关键理解:
-XX:MetaspaceSize:当元空间使用量达到这个值时,触发 Full GC 来回收类卸载-XX:MaxMetaspaceSize:元空间的最大值,超过则 OOM
我建议你这样设:
# 设置初始阈值,避免频繁 Full GC
-XX:MetaspaceSize=256M
# 设置上限,防止内存泄漏撑爆系统
-XX:MaxMetaspaceSize=512M
为什么阈值要设大一点?因为默认的 MetaspaceSize 只有 20M 左右。如果你的应用加载了很多类(比如用了 Spring Boot + 各种框架),很容易就超过这个值,然后频繁触发 Full GC。嗯,这里要注意,Full GC 是 STW 的,对性能影响很大。
我在项目中遇到过这样一个场景:一个微服务上线后,每 10 分钟就来一次 Full GC。监控一看,元空间从 20M 涨到 80M 就触发 GC,GC 完降到 60M,然后又涨回去。反复折腾。后来把 MetaspaceSize 设到 256M,Full GC 频率直接降到一天一次。
三者的关系:一张图看懂
下面这张图展示了堆、栈、元空间在 JVM 内存布局中的位置和关系:
实战配置模板
说了这么多,给出一份我常用的配置模板。你可以根据实际情况调整:
# 堆内存:固定大小,避免动态扩容
-Xms4G
-Xmx4G
# 栈大小:高并发场景用 256K,普通应用用 512K
-Xss256K
# 元空间:阈值设大,上限设死
-XX:MetaspaceSize=256M
-XX:MaxMetaspaceSize=512M
最后提醒一句:这些参数不是设完就完事了。上线后一定要用 jstat、jmap 或者监控工具观察实际使用情况。我习惯在压测阶段就调好,省得上线后手忙脚乱。