• 约 4 分钟

JVM 常见命令

虽说日常开发很少接触到 JVM 的命令行参数吧,但了解一些常用的,明白它们分别是做什么的,对于问题排查和性能调优也是有帮助的。

启动参数

java 命令后的参数分三类:

  • 标准参数(-):如 -classpath、-version,所有 JVM 实现都支持;
  • 非标准参数(-X):HotSpot 特有,不保证跨实现可用;
  • 不稳定参数(-XX):官方声明随时可能变更,却是调优的主力。-XX:+Flag / -XX:-Flag 是布尔开关,-XX:Flag=value 带值。
$$ java -Xms512m -Xmx1024m -Xss256k -jar app.jar
  • -Xms / -Xmx:初始 / 最大堆大小,两者设为一致可避免堆反复伸缩;
  • -Xss:每个线程的栈大小,线程多而栈不深的场景调小它能容纳更多线程。

打开 GC 日志(JDK 9+ 统一日志语法;JDK 8 用 -XX:+PrintGCDetails -Xloggc:gc.log):

$$ java -Xmx1g -Xlog:gc*:file=gc.log:time,uptime,level,tags -jar app.jar

OOM 时自动留现场的保险丝:

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path/with/enough/space

命令行工具

均在 JDK bin 目录下。套路是固定的:jps 找到 pid,再按需用下面的工具。

jps:查看 Java 进程

类似 ps,只列出 JVM 进程。-q 只输出 pid,-l 输出主类全名(jar 则为路径),-v 输出 JVM 参数,-m 输出传给 main() 的参数。

$$ jps -l
12345 com.example.App
67890 sun.tools.jps.Jps

jstat:监视 GC 等运行数据

格式为 jstat -<option> [-t] [-h<n>] <vmid> [<间隔> [<次数>]]。例如 jstat -gc -h3 12345 1000 10:每 1s 打印一次共 10 次,每 3 行重复表头;加 -t 在输出前加时间戳列。

$$ jstat -gcutil 12345 1000
  S0     S1     E      O      M     YGC    YGCT    FGC    FGCT     GCT
  0.00  98.44  45.12  67.89  95.23   42    0.836     3    1.102    1.938
  • YGC 涨太快:分配速率过高或新生代太小;
  • FGC 持续增长且 FGCT 很大:老年代回收赶不上分配,重点怀疑泄漏。

其他常用 option:-class(类加载)、-compiler(JIT)、-gc(各区容量与用量)、-gcnew / -gcold(新生代、老年代行为)。连续采样比单点数据有用。

jinfo:查看和动态修改参数

$$ jinfo -flag MaxHeapSize 12345                    # 查单个 flag
$$ jinfo -sysprops 12345                            # 系统属性
$$ jinfo -flag +HeapDumpOnOutOfMemoryError 12345    # 不重启动态打开

在线上动态开关 flag 是它最有价值的场景:忘配了 HeapDump 参数,等不及重启就用它补上。只有标记为 manageable 的参数支持运行时修改,并非全部。

jmap:生成堆转储快照

$$ jmap -histo:live 12345 | head -20
$$ jmap -dump:live,format=b,file=heap.hprof 12345

内存告警先看 -histo:live 直方图,某个业务类占用异常高基本当场定位;需要深挖再导出 hprof,用 MAT 或 VisualVM 离线分析,重点看支配树和到 GC Roots 的引用链。

注意 -dump:live 会先触发一次 Full GC,对大堆是可观的停顿,生产慎用;也可以不手动 dump,靠 -XX:+HeapDumpOnOutOfMemoryError 自动留档。

jstack:生成线程快照

定位线程长时间停顿的原因:死锁、死循环、等锁、等外部资源。每隔几秒连续抓两份以上对比——多份栈停在同一处才可信。

$$ jstack 12345 > thread1.txt
  • CPU 飙高:top -H -p <pid> 找到热点线程,printf '%x\n' <tid> 把十进制线程 ID 转为十六进制,到栈里搜 nid=0x...;
  • 死锁:输出末尾会直接列出 Found one Java-level deadlock 和互相等待的线程。

jcmd:一统江湖

JDK 7+ 的综合工具,覆盖 jinfo / jmap / jstack 的大部分功能:

$$ jcmd 12345 VM.flags                  # jinfo -flags
$$ jcmd 12345 GC.heap_info              # 堆各区概况
$$ jcmd 12345 Thread.print -l           # jstack
$$ jcmd 12345 GC.class_histogram        # jmap -histo
$$ jcmd 12345 GC.heap_dump heap.hprof   # jmap -dump
$$ jcmd 12345 help                      # 列出全部子命令

还能录制 JFR(JDK 11 起免费开源,开销极低,适合线上):

$$ jcmd 12345 JFR.start duration=60s filename=rec.jfr settings=profile

排查套路

jps 找 pid → jstat -gcutil <pid> 1000 10 看 GC 趋势 → CPU 高:top -H + jstack,内存高:jmap -histo:live → 深挖再 jcmd 拉 dump / 录 JFR → 事后补 -XX:+HeapDumpOnOutOfMemoryError。

参考文献

林威
林威 咖味十足的软件工程师