外观
JVM 启动参数速查
参数不是背下来的,是出问题时知道该动哪一个。按"内存 / GC / 日志 / 容器 / 兜底"五组记。
一、内存
bash
java -Xms4g -Xmx4g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m \
-Xss512k -XX:MaxDirectMemorySize=1g -jar app.jar| 参数 | 管什么 | 配错的后果 |
|---|---|---|
-Xms / -Xmx | 堆初始 / 最大 | 两者不等会频繁扩容抖动;生产建议设成相同 |
-XX:MetaspaceSize | 触发 Full GC 的元空间阈值 | 设太小会启动期频繁 Full GC |
-XX:MaxMetaspaceSize | 元空间上限 | 不设则只受物理内存限制,类泄漏会吃光内存 |
-Xss | 单线程栈大小 | 调太小 → StackOverflowError;调太大 → 线程一多就 OOM |
-XX:MaxDirectMemorySize | 堆外直接内存 | NIO/Netty 场景的隐形大户,OutOfDirectMemoryError 就是它 |
解释:-Xmx 之外还有堆外(元空间、线程栈、直接内存、JIT 代码缓存、GC 结构)。堆设 4G 的进程实际 RSS 到 5.5G 很正常——容器 limit 只按堆设就会反复被 OOMKilled。经验公式:容器 limit ≈ Xmx × 1.3 ~ 1.5。
二、GC 选择
bash
# G1(JDK 9+ 默认):通用,停顿可控
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
# ZGC(JDK 15+ 分代前是单代):大堆、超低停顿
-XX:+UseZGC -XX:+ZGenerational # JDK 21+ 开分代
# Parallel:吞吐优先,批处理/离线任务
-XX:+UseParallelGC| 场景 | 选择 | 理由 |
|---|---|---|
| 堆 < 8G,普通 Web 服务 | G1 | 均衡,参数少 |
| 堆 ≥ 16G,对 P99 敏感 | ZGC | 停顿与堆大小基本无关 |
| 跑批、离线计算 | Parallel | 吞吐最高,不在乎单次停顿 |
| CMS | 已删除 | JDK 14 起移除,遇到残留配置直接换 G1 |
三、GC 日志(JDK 9+ 统一格式)
bash
-Xlog:gc*:file=/var/log/app/gc.log:time,uptime,level,tags:filecount=5,filesize=50mJDK 8 的等价写法:
bash
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime \
-Xloggc:/var/log/app/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=50M解释:filecount/filesize 是轮转,不加这行 GC 日志能写到几个 G 把磁盘吃满。两套语法完全不兼容——JDK 11 上用 -XX:+PrintGCDetails 会直接启动失败并提示改用 -Xlog。
四、容器内必配
bash
-XX:+UseContainerSupport # JDK 10+ 默认开,8u191+ 可用
-XX:MaxRAMPercentage=70.0
-XX:InitialRAMPercentage=70.0
-XX:+ExitOnOutOfMemoryError # OOM 就退出,让编排系统重启输出(验证是否识别到 limit):
text
$ java -XX:+PrintFlagsFinal -version | grep -E 'MaxHeapSize|MaxRAMPercentage'
size_t MaxHeapSize = 3221225472 {pd product} # 3G,对应 limit 4G × 70%解释:老 JDK(8u131 之前)在容器里读的是宿主机内存,4G limit 的容器里起一个默认堆为宿主 1/4 的进程,直接被 cgroup 杀掉。MaxRAMPercentage 比写死 -Xmx 更适合弹性扩缩容的场景。
五、故障兜底参数(强烈建议默认带上)
bash
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/dump/app.hprof
-XX:ErrorFile=/var/log/app/hs_err_pid%p.log
-XX:+ExitOnOutOfMemoryError解释:OOM 发生的那一刻是唯一能看清"什么东西占着内存"的机会。不预先配 HeapDumpOnOutOfMemoryError,进程一旦被杀现场就没了,事后只能靠猜。%p 会替换成 pid,避免多实例互相覆盖。
HeapDumpPath 目录必须存在且可写
配成不存在的目录时 JVM 会打印 Unable to open heap dump file 然后什么都不落盘。上线前用 jcmd <pid> GC.heap_dump /data/dump/test.hprof 手动验证一次能不能真的写出来。
六、查当前进程实际生效的参数
bash
jcmd <pid> VM.flags
jinfo -flag MaxHeapSize <pid>
jcmd <pid> VM.flags -all | grep -E 'GCTimeRatio|MaxGCPauseMillis'输出:
text
-XX:CICompilerCount=4 -XX:ConcGCThreads=4 -XX:G1HeapRegionSize=8388608
-XX:MaxGCPauseMillis=200 -XX:MaxHeapSize=4294967296 -XX:MinHeapSize=4294967296解释:jcmd 读的是运行时真值,比翻启动脚本可靠——脚本里可能被 JAVA_OPTS、CATALINA_OPTS、镜像 ENV 层层覆盖。jinfo -flag 看单项,jcmd VM.flags 看全量。
七、常见参数错误信号
| 启动报错 | 原因 | 处理 |
|---|---|---|
Unrecognized VM option 'PrintGCDateStamps' | JDK 9+ 用了旧版 GC 日志语法 | 换 -Xlog:gc* |
CMS is deprecated, this will be removed in a future release | JDK 9+ 用 CMS | 换 G1 |
Could not reserve enough space for object heap | Xmx 大于可用内存/容器 limit | 调小或改百分比 |
Invalid maximum heap size on 32-bit JVM | 装了 32 位 JDK | 换 64 位 |
| 进程起来但 RSS 远超 Xmx | 堆外内存(直接内存/线程数/元空间) | 查 -XX:MaxDirectMemorySize 和线程数 |