1. 启动参数初探:JVM 到底在听谁的指挥?

各位同学,咱们今天正式开课。第一讲,先聊聊 JVM 启动参数到底是什么。

你想想看,每次你敲下 java -jar myapp.jar 的时候,JVM 是怎么知道该分配多少内存?用哪个垃圾回收器?要不要打印 GC 日志?

答案很简单——它听启动参数的指挥。

我个人习惯把 JVM 启动参数比作「汽车的出厂设置」。你买一辆车,发动机、变速箱、悬挂系统都是固定的,但你可以通过调节 ECU 参数、胎压、避震软硬来改变驾驶感受。JVM 也一样,参数调好了,应用跑得又快又稳;调不好,三天两头 OOM、CPU 爆满、GC 停顿几十秒。

我在项目中遇到过不止一次,线上服务莫名其妙 Full GC 频繁,查了半天发现是启动参数里把堆内存设得太小,还开了 CMS 回收器但没配触发阈值。说白了,就是参数没设对。

1.1 什么是 JVM 启动参数?

JVM 启动参数,就是在启动 Java 进程时,通过命令行传递给 JVM 的配置指令。它们控制着 JVM 的运行时行为——内存大小、垃圾回收策略、类加载机制、JIT 编译、调试开关等等。

举个例子:

java -Xms512m -Xmx1024m -XX:+UseG1GC -jar application.jar

这里 -Xms512m、-Xmx1024m、-XX:+UseG1GC 都是启动参数。它们告诉 JVM:初始堆 512MB,最大堆 1024MB,用 G1 垃圾回收器。

小提示: 启动参数和程序参数是两码事。启动参数在 -jar 之前,给 JVM 用的;程序参数在 -jar 之后,给 main() 方法用的。别搞混了。

1.2 参数分类:标准、非标准、不稳定

JVM 参数分三类。这个分类不是随便分的,它决定了参数的兼容性和稳定性。我刚开始学的时候也记不住,后来用了一个比喻就记住了——「官方承诺、厂商建议、实验特性」。

分类 前缀 稳定性 典型例子
标准参数 - 所有 JVM 实现必须支持,长期稳定 -client, -server, -jar, -Dproperty=value
非标准参数 -X HotSpot 特有,但相对稳定 -Xms, -Xmx, -Xss, -Xmn
不稳定参数 -XX 随时可能变更,无警告 -XX:+UseG1GC, -XX:MaxMetaspaceSize

标准参数(Standard Options)

以单个 - 开头。所有 JVM 厂商(Oracle、IBM、OpenJ9 等)都必须支持。你写一个 -version,在任何 JVM 上都能跑。这类参数通常不涉及内存调优,更多是运行模式、系统属性、类路径等基础配置。

常用标准参数:

  • -version:打印 JVM 版本
  • -help:打印帮助信息
  • -D<name>=<value>:设置系统属性
  • -jar:运行 jar 包
  • -client / -server:选择客户端或服务器模式

非标准参数(Non-Standard Options)

以 -X 开头。这些是 HotSpot JVM 特有的,其他 JVM 实现不一定支持。但它们在 HotSpot 内部相对稳定,不会轻易删除。大部分内存相关的参数都在这里。

常用非标准参数:

  • -Xms<size>:初始堆大小
  • -Xmx<size>:最大堆大小
  • -Xss<size>:线程栈大小
  • -Xmn<size>:新生代大小
  • -Xloggc:<file>:GC 日志文件(已废弃,但老项目还在用)
避坑指南: 我曾经见过一个项目,把 -Xmx 写成了 -Xms 的十倍,结果启动时堆内存直接撑爆。记住:-Xms 和 -Xmx 最好设成一样,避免运行时动态调整带来的性能损耗。

不稳定参数(Unstable Options)

以 -XX 开头。这类参数最灵活,也最危险。Oracle 明确表示「不保证这些参数在后续版本中继续存在」。它们用于控制垃圾回收器、JIT 编译、内存布局、调试开关等底层行为。

常用不稳定参数:

  • -XX:+UseG1GC:启用 G1 垃圾回收器
  • -XX:MaxMetaspaceSize=<size>:元空间上限
  • -XX:+PrintGCDetails:打印 GC 详情
  • -XX:NewRatio=<n>:老年代与新生代比例
  • -XX:SurvivorRatio=<n>:Eden 与 Survivor 比例

嗯,这里要注意:-XX 参数有两种写法——布尔型用 + 或 - 表示开启/关闭,数值型用 = 赋值。比如 -XX:+PrintGCDetails 是开启,-XX:-PrintGCDetails 是关闭,-XX:MaxMetaspaceSize=256m 是赋值。

1.3 查看当前参数的命令

你可能会问:「我怎么知道当前 JVM 用了哪些参数?」

好问题。我每次接手一个线上服务,第一件事就是查它的 JVM 参数。下面这几个命令,你最好背下来。

1. java -XX:+PrintCommandLineFlags -version

这个命令会打印出 JVM 启动时被显式设置过的参数。注意,它只显示你手动传的参数,不显示默认值。

$ java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintCommandLineFlags -XX:+UseG1GC
openjdk version "17.0.1" 2021-10-19

2. java -XX:+PrintFlagsFinal -version

这个命令会打印出所有 JVM 参数的最终值——包括你手动设置的、JVM 自动调整的、以及默认值。输出内容非常多,通常配合 grep 使用。

$ java -XX:+PrintFlagsFinal -version 2>&1 | grep -i "heap"
   uintx InitialHeapSize                          = 268435456
   uintx MaxHeapSize                              = 4294967296
   uintx MinHeapSize                              = 8388608
   bool  UseContainerSupport                      = true

输出格式:第一列是参数类型(bool、uintx、intx 等),第二列是参数名,第三列是等号,第四列是当前值。

3. jinfo -flags <pid>

如果 JVM 已经在运行了,用 jinfo 可以查看运行中的参数。这个命令来自 JDK 自带的 jinfo 工具。

$ jinfo -flags 12345
Attaching to process ID 12345, please wait...
Debugger attached successfully.
Client compiler detected.
JVM version is 17.0.1
Non-default VM flags: -XX:CICompilerCount=4 -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+UseG1GC

4. jcmd <pid> VM.flags

jcmd 是 JDK 8 之后推荐的诊断工具,功能比 jinfo 更强大。

$ jcmd 12345 VM.flags
12345:
-XX:CICompilerCount=4 -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+UseG1GC
核心要点: 查参数用 PrintFlagsFinal,查运行中参数用 jcmd。这两个命令覆盖了 90% 的场景。

1.4 知识体系总览

这一章的内容,我用一张图帮你串起来。你把它存下来,后面每学一章都可以回来对照。

JVM 启动参数知识体系 JVM 启动参数 标准参数 (-) 非标准参数 (-X) 不稳定参数 (-XX) 所有 JVM 实现 长期稳定 HotSpot 特有 相对稳定 随时可能变更 无警告 查看命令 运行中查看 PrintCommandLineFlags jinfo / jcmd

这张图把本章的核心内容都串起来了:三个分类、各自特点、以及查看参数的命令。你后面学到内存调优、GC 调优时,随时可以回来看看参数属于哪一类,心里就有底了。

1.5 本章小结

这一章我们干了三件事:

  1. 搞清楚了 JVM 启动参数是什么——就是启动时传给 JVM 的配置指令,控制内存、GC、JIT 等行为。
  2. 分清了三个分类——标准参数(-)所有 JVM 都认;非标准参数(-X)HotSpot 特有但稳定;不稳定参数(-XX)最灵活也最危险。
  3. 学会了查参数的命令——PrintCommandLineFlags 看显式设置,PrintFlagsFinal 看所有值,jcmd 看运行中参数。