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 知识体系总览
这一章的内容,我用一张图帮你串起来。你把它存下来,后面每学一章都可以回来对照。
这张图把本章的核心内容都串起来了:三个分类、各自特点、以及查看参数的命令。你后面学到内存调优、GC 调优时,随时可以回来看看参数属于哪一类,心里就有底了。
1.5 本章小结
这一章我们干了三件事:
- 搞清楚了 JVM 启动参数是什么——就是启动时传给 JVM 的配置指令,控制内存、GC、JIT 等行为。
- 分清了三个分类——标准参数(
-)所有 JVM 都认;非标准参数(-X)HotSpot 特有但稳定;不稳定参数(-XX)最灵活也最危险。 - 学会了查参数的命令——
PrintCommandLineFlags看显式设置,PrintFlagsFinal看所有值,jcmd看运行中参数。