JVM参数来源、JDK7/8配置与诊断命令
JVM参数不是一张“背下来就能调优”的清单。生产中真正要回答的是:参数从哪里注入、目标JDK是否支持、最终值是多少、影响哪个内存区域或运行机制、怎样观察效果、错误配置如何回退。
本页负责“启动参数和低风险诊断入口”;OOM、Heap Dump、线程栈、MAT和Arthas等深入故障分析继续放在JVM常用排查工具和OOM生产级排查手册。
学习目标
学完后应能:
- 区分标准参数、
-X参数、-XX参数、系统属性和应用参数。 - 解释
-XX:+Flag、-XX:-Flag、-XX:Flag=value语法。 - 找出参数来自启动脚本、环境变量、容器Entrypoint、systemd还是命令行。
- 区分参数“配置值”“JVM最终值”和“当前运行状态”。
- 使用
PrintCommandLineFlags、PrintFlagsFinal、jcmd VM.flags核实参数。 - 正确设置Heap、年轻代、线程栈、PermGen/Metaspace、Direct Memory和Code Cache相关参数。
- 说明JDK 7/8与JDK 9+ GC日志配置差异。
- 理解JDK 8容器感知与具体8u更新/发行版有关,不能机械使用宿主机资源。
- 选择
jps、jcmd、jinfo、jstat、jstack、jmap并评估风险。 - 理解Attach机制、用户权限、PID命名空间和极简JRE镜像为什么会让命令失败。
- 为Java服务配置可回溯的GC日志、OOM Dump、错误日志和版本信息。
- 从启动失败、GC频繁、容器OOMKilled等商业场景建立证据链。
一、一条Java启动命令怎样被解释
java -Xms512m -Xmx512m -Dspring.profiles.active=prod \
-jar order-service.jar \
--server.port=8080可分为:
flowchart TD
A["java启动器"] --> B["JVM选项:-Xms、-Xmx"]
A --> C["系统属性:-Dname=value"]
A --> D["入口:-jar或主类"]
D --> E["应用参数:--server.port=8080"]最容易犯的错误是把应用参数放在-jar前后弄混:
- JVM选项和
-D通常放在-jar或主类之前,由Java启动器/JVM处理。 - JAR名或主类之后的内容传给应用
main(String[] args)。 - Spring Boot的
--server.port属于应用参数,不是JVM Heap参数。
下面通常不会设置JVM最大堆,而只是把字符串传给应用:
java -jar app.jar -Xmx2g除非应用自己解析-Xmx2g,JVM不会把它当启动选项。
二、参数语法分类
2.1 标准选项
规范性和跨实现稳定性相对更高,例如:
-classpath
-cp
-Dname=value
-verbose:class
-version2.2 -X扩展选项
由JVM实现提供,长期常用但不保证所有虚拟机一致:
-Xms512m
-Xmx2g
-Xss1m
-Xmn512m
-Xloggc:logs/gc.log2.3 -XX高级选项
布尔型:
-XX:+UseG1GC
-XX:-UseBiasedLocking+是开启,-是关闭。
数值/字符串型:
-XX:MaxMetaspaceSize=256m
-XX:MaxDirectMemorySize=512m
-XX:HeapDumpPath=/data/dump不同JDK版本可能新增、废弃、移除或改变默认值。启动时报Unrecognized VM option时,先核对目标java -version,不能直接删参数上线。
2.4 系统属性
-Dfile.encoding=UTF-8
-Duser.timezone=Asia/Shanghai
-Dspring.profiles.active=prodJava代码通过:
System.getProperty("spring.profiles.active");读取。系统属性不是操作系统环境变量:
System.getenv("SPRING_PROFILES_ACTIVE");读取的是另一套来源。
三、最终参数可能从哪里来
生产进程的最终命令可能经过多层拼接:
flowchart TD
A["基础镜像/主机JDK默认值"] --> B["JVM Ergonomics自动选择"]
C["启动脚本JAVA_OPTS"] --> F["最终java命令"]
D["环境变量/平台注入"] --> F
E["systemd、Docker、K8s配置"] --> F
B --> F
F --> G["JVM解析并校验参数"]
G --> H["运行时最终Flags"]常见来源:
- Shell/批处理脚本中的
JAVA_OPTS、JAVA_TOOL_OPTIONS等。 - systemd的
ExecStart和Environment。 - Dockerfile的
ENTRYPOINT、CMD。 - Compose的
command、entrypoint和environment。 - Kubernetes的container command/args、ConfigMap、Secret和环境变量。
- Jenkins或发布平台模板。
- 应用服务器自己的JVM配置文件。
- JVM基于机器资源和收集器执行的Ergonomics。
JAVA_OPTS只是很多脚本约定的普通变量,Java启动器不会自动读取任意名为JAVA_OPTS的变量;必须由脚本显式展开。JAVA_TOOL_OPTIONS等则可能由JVM/启动器机制读取,但支持、输出和安全边界需要按目标JDK验证。
3.1 容器中最常见的“我明明没配”
Docker inspect:
docker inspect order-api --format 'Path={{.Path}} Args={{json .Args}} Env={{json .Config.Env}}'Kubernetes:
kubectl get pod order-api-xxx -o yaml
kubectl exec order-api-xxx -- sh -c 'tr "\0" " " < /proc/1/cmdline'Linux宿主机:
tr '\0' ' ' < /proc/<pid>/cmdline
systemctl cat order-api注意命令行和环境变量可能包含密码、Token或密钥,保存和分享前必须脱敏。
四、配置值、最终值和运行状态不是一回事
配置值:启动脚本中写了-Xmx2g
最终值:JVM解析后MaxHeapSize是多少
运行状态:此刻Heap用了多少、GC发生几次分别使用:
# JVM启动时打印被选择/修改的重要命令行Flag
java -XX:+PrintCommandLineFlags -version
# 打印大量最终Flag
java -XX:+PrintFlagsFinal -version
# 运行进程最终Flag
jcmd <pid> VM.flags
# 当前Heap/GC状态
jstat -gcutil <pid> 1000 10PrintFlagsFinal输出中的=、:=等标记可帮助观察默认值与被修改值,但具体来源还要结合命令行、环境、Ergonomics和版本;不能只凭一个符号认定是谁修改。
五、Heap参数完整关系
5.1 Xms与Xmx
-Xms2g
-Xmx2gXms是初始Java堆相关目标。Xmx是最大Java堆上限。
“默认Xms固定为物理内存1/64、Xmx固定为1/4”只适合作为某些历史HotSpot环境的粗略记忆,不是跨JDK、容器和发行版保证。正确方式是查看最终Flags和实际Heap。
将Xms和Xmx设为相同值可减少运行期扩堆变化,常用于延迟敏感服务;但它会让进程更早提交/保留较大内存,增加高密度部署压力。是否相同要结合启动速度、容器limit和容量规划,不是绝对规范。
5.2 Xmn、NewSize、MaxNewSize与NewRatio
传统分代收集器中常见:
-Xmn512m
-XX:NewSize=512m
-XX:MaxNewSize=512m
-XX:NewRatio=2Xmn通常同时影响年轻代初始和最大大小。NewRatio=2常表示老年代与年轻代约2:1,但实际布局受对齐、收集器和自适应策略影响。- 同时固定Xmn和配置NewRatio可能产生覆盖或失去自适应空间。
- G1按暂停目标动态选择年轻Region数量,不应机械复制传统固定Xmn调法。
5.3 SurvivorRatio
-XX:SurvivorRatio=8传统年轻代布局中常表示:
Eden : From : To ≈ 8 : 1 : 1它不是“Eden与两个Survivor总和为8:1”,也不保证运行时始终严格固定;Adaptive Size Policy和收集器策略可能调整。
5.4 MaxTenuringThreshold
-XX:MaxTenuringThreshold=15它是晋升年龄上限相关参数,不代表每个对象一定经历15次Young GC。Survivor不足、动态年龄阈值、大对象和收集器策略都可能提前晋升。
六、JDK7 PermGen与JDK8 Metaspace
JDK7常见参数
-XX:PermSize=128m
-XX:MaxPermSize=256mJDK8常见参数
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=256m关键区别:
- JDK 7 HotSpot大量类元数据由永久代实现承载,受MaxPermSize约束。
- JDK 8移除永久代,类元数据主要使用本地内存Metaspace。
MetaspaceSize更接近触发元空间GC/调整的初始阈值概念,不等同“预先固定分配这么大”。- 未配置
MaxMetaspaceSize不等于没有风险,ClassLoader泄漏可继续消耗本地/容器内存。 - JDK 8继续使用
PermSize通常会收到参数被忽略或不支持提示,必须按实际版本处理。
动态代理、脚本、热部署和插件系统应同时监控已加载类数、ClassLoader数量和Metaspace,而不是只调大上限。
七、线程栈、直接内存和Code Cache
7.1 线程栈Xss
-Xss1m每个Java线程通常需要本地线程栈,线程数很大时总量近似受:
线程栈预算 ≈ 线程数 × 单线程栈大小影响。Xss过大降低可创建线程数;过小会增加StackOverflowError风险。无限创建线程的问题不能只靠减小Xss解决,应限制线程池和阻塞并发。
7.2 Direct Memory
-XX:MaxDirectMemorySize=512mDirect Memory不计入Java Heap使用量,但计入进程RSS和容器总内存。NIO、Netty、压缩和本地库可能大量使用。未显式设置时的默认关联目标随JDK实现和版本而异,应查看Flags、框架指标和NMT。
7.3 Code Cache
JIT编译后的机器码存放在Code Cache。JDK 7/8参数名称和分段行为随版本演进,常见检查:
jcmd <pid> Compiler.codecache
jcmd <pid> VM.flagsCode Cache耗尽可能导致编译被禁用、性能下降和警告。不能只看Heap判断JVM内存。
八、容器总内存怎样预算
容器内存limit
> Java Heap
+ Metaspace/类元数据
+ Direct Memory
+ 线程栈
+ Code Cache
+ GC/JIT/JVM本地结构
+ JNI/Agent/共享库
+ mmap和可能的子进程
+ 安全余量如果容器limit为1GiB、-Xmx900m、200个线程且-Xss1m,仅Heap和理论线程栈就可能超过1GiB,还没计算其他区域。最终可能是cgroup OOMKilled/Exit 137,而不是Java抛Java heap space。
8.1 JDK8容器感知必须看更新版
早期JDK 8可能按宿主机CPU和内存估算Heap、GC线程和ForkJoin并行度;后续Oracle/OpenJDK更新或不同发行版回移了容器支持。不能只说“JDK8支持/不支持容器”,必须记录:
java -version
java -XX:+PrintFlagsFinal -version并在目标容器中验证最大Heap、availableProcessors()和实际Flags。若版本不可靠,优先升级受支持更新版,而不是堆叠来源不明的实验参数。
九、垃圾收集器参数必须成组理解
JDK 7/8常见:
-XX:+UseSerialGC
-XX:+UseParallelGC
-XX:+UseConcMarkSweepGC
-XX:+UseG1GC不能同时随意启用多个互斥收集器。CMS、ParNew、Parallel、G1的支持组合和版本边界见HotSpot垃圾收集器完整原理。
常见目标参数:
-XX:MaxGCPauseMillis=200
-XX:GCTimeRatio=99
-XX:+UseAdaptiveSizePolicy- MaxGCPauseMillis是软目标,不是接口SLA保证。
- 参数名是
GCTimeRatio,不是GCTimeRadio。 - 自适应策略可能调整代大小和晋升策略;手工固定过多参数会限制它。
十、JDK7/8 GC日志完整配置
基础:
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-Xloggc:/data/logs/gc.log按需增加:
-XX:+PrintTenuringDistribution
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintGCApplicationConcurrentTimeJDK 7/8不同更新版和收集器支持细节不同,启动前在目标JDK验证。
10.1 JDK7/8日志轮转
部分JDK 7/8 HotSpot版本支持:
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=10
-XX:GCLogFileSize=50M历史轮转实现、文件命名和重启覆盖行为有版本差异。生产必须实际执行重启、轮转、日志采集和磁盘占满演练,不能只看到参数启动成功。
10.2 为什么时间戳要同时考虑
- DateStamps便于与业务日志、监控按真实时间对齐。
- TimeStamps/uptime便于判断JVM启动后多久发生事件,不受部分系统时钟调整影响。
故障分析需要统一时区,并保存发布、流量、容器事件和GC时间线。
十一、JDK9+统一日志不能复制给JDK8
-Xlog:gc*,safepoint:file=/data/logs/gc-%t-%p.log:time,uptime,level,tags:filecount=10,filesize=50MJDK 9+使用统一日志框架,选择器、装饰器和轮转语法与JDK 7/8不同。JDK 8执行-Xlog:gc*会失败;JDK 17/21也不应继续把所有历史PrintGC*参数当作默认模板。
迁移JDK时要做参数兼容清单:
- 哪些参数仍支持。
- 哪些被废弃或移除。
- 哪些默认值改变。
- 日志采集规则是否识别新文件名和格式。
- 告警解析是否仍能识别Pause、Full、OOM等事件。
十二、OOM和致命错误现场参数
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/dump
-XX:ErrorFile=/data/error/hs_err_pid%p.log12.1 HeapDumpPath是目录还是文件
目标JDK对路径行为要实测。无论使用目录还是带PID/时间的文件方案,都必须确保:
- 路径存在且运行用户可写。
- 挂载为持久存储,容器重建后仍能取回。
- 磁盘空间大于可能的Dump体积并留安全余量。
- 多实例不会互相覆盖。
- 文件包含敏感业务数据,访问、传输和销毁受控。
12.2 OnOutOfMemoryError与ExitOnOutOfMemoryError
不同JDK版本支持不同。执行外部脚本可能阻塞、重复触发或带来命令注入、权限风险;自动退出会影响可用性但可能避免假存活。必须按错误类型、编排自愈、Dump完成顺序和恢复策略设计,不能盲目复制。
12.3 容器OOMKilled不会保证生成Heap Dump
内核直接杀进程时,JVM可能没有机会抛OutOfMemoryError和执行Dump逻辑。因此还必须依靠容器事件、历史RSS、Heap/Non-Heap指标和上次日志。
十三、先学会确认PID
13.1 jps
jps -lvm它利用JVM进程信息发现本机Java进程,但可能受以下因素影响:
- 目标JVM禁用或无法写PerfData。
- 当前用户无权限。
- 容器/PID命名空间不同。
- 极简运行镜像没有JDK工具。
- 多个Java进程显示的主类/JAR信息不完整。
所以jps没有输出不等于没有Java进程。Linux还可结合:
ps -ef
pgrep -af java
cat /proc/<pid>/cmdlineDocker/Kubernetes先定位容器,再明确在宿主机还是容器PID命名空间执行。
十四、Attach机制为什么会失败
jcmd、jstack、jmap、jinfo等很多操作需要附加到目标JVM。典型链路包含同用户权限校验、目标进程通信文件/Socket、信号和目标JVM Attach Listener。
flowchart TD
A["诊断工具拿到PID"] --> B["检查OS用户与权限"]
B --> C["请求目标JVM启动/连接Attach Listener"]
C --> D["通过本地通信通道发送命令"]
D --> E["目标JVM在安全点/VM线程等执行诊断"]
E --> F["返回结果"]失败常见原因:
- 使用不同操作系统用户。
/tmp、PerfData或通信文件被清理/权限异常。- 容器内外PID不同。
- 目标JVM长时间无响应或正处于严重GC。
- JRE/精简镜像没有工具。
- 安全加固禁用Attach或缺少能力。
- 工具JDK与目标JVM版本差异太大。
不要为方便把生产容器长期改成privileged。应预先设计诊断镜像、同版本工具、权限和审计流程。
十五、jcmd:优先的综合入口
发现能力:
jcmd -l
jcmd <pid> help低风险信息类命令:
jcmd <pid> VM.version
jcmd <pid> VM.command_line
jcmd <pid> VM.flags
jcmd <pid> VM.system_properties
jcmd <pid> VM.uptime
jcmd <pid> GC.heap_info
jcmd <pid> VM.classloader_stats高成本命令如Heap Dump、类直方图live统计、某些JFR/GC操作需要单独评估。jcmd help显示的命令取决于目标JDK,不要把JDK 21输出当作JDK 7/8必然能力。
十六、jinfo:看参数与有限动态修改
jinfo -flags <pid>
jinfo -sysprops <pid>
jinfo -flag MaxHeapSize <pid>部分可管理Flag可能支持动态修改:
jinfo -flag +PrintGCDetails <pid>但不是所有参数都能运行时修改。Heap上限、收集器等通常不能靠jinfo任意切换;动态修改也必须记录、评估影响并同步部署配置,否则重启后丢失。
十七、jstat:看趋势,不把采样列当绝对真相
jstat -gcutil <pid> 1000 10
jstat -gc <pid> 1000 10
jstat -class <pid> 1000 10
jstat -compiler <pid> 1000 10参数1000 10表示每1000毫秒采样一次,共10次。
-gcutil常见关注:
- E、S0、S1、O等代使用百分比。
- YGC/YGCT:Young GC次数和累计时间。
- FGC/FGCT:Full GC次数和累计时间。
- GCT:累计GC时间。
不同收集器、JDK版本的列含义和可用性不同。单次快照只能说明当时状态;连续采样才能看Eden增长、Old基线、GC后回落和频率。
十八、jstack与Thread.print
jstack -l <pid> > thread-1.txt
jcmd <pid> Thread.print -l > thread-2.txt排查CPU、锁、死循环、线程池阻塞时,建议间隔数秒抓取3份以上线程栈。单份栈里的WAITING可能是正常线程池等待。
风险通常低于Heap Dump,但线程非常多或JVM严重卡顿时仍有开销。输出可能包含SQL、URL、业务参数等敏感信息。
十九、jmap:能力强但风险更高
jmap -heap <pid>
jmap -histo <pid>
jmap -histo:live <pid>
jmap -dump:format=b,file=heap.hprof <pid>注意:
histo:live可能触发Full GC以统计存活对象,不能当无成本查询。- Heap Dump可能导致停顿、巨大磁盘写入和额外I/O压力。
- 堆很大且磁盘不足时可能把宿主机也拖垮。
- 某些JDK版本更推荐使用对应
jcmd命令,但风险不会因命令名字改变而消失。
执行前检查副本、摘流、磁盘、暂停预算和数据安全。
二十、命令风险矩阵
| 操作 | 一般风险 | 使用前检查 |
|---|---|---|
VM.version/uptime/command_line | 低 | 权限、敏感参数 |
VM.flags/system_properties | 低 | 密码和Token脱敏 |
jstat连续采样 | 低 | 采样频率不要过高 |
| 线程栈 | 低到中 | 线程数量、输出敏感信息 |
| 普通Histogram | 中 | 对象数量、目标版本实现 |
histo:live | 中到高 | 可能Full GC |
| Heap Dump | 高 | STW、磁盘、I/O、敏感数据 |
| 强制GC | 高 | 长暂停和业务抖动 |
| 运行时修改Flag | 取决于参数 | 是否可写、回滚和重启一致性 |
“命令能执行”不等于“线上现在应该执行”。
二十一、JDK7/8可运行参数观察Demo
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.List;
public class JvmFlagsDemo {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean();
List<String> inputArguments = bean.getInputArguments();
System.out.println("pid-name=" + bean.getName());
System.out.println("vm=" + bean.getVmName());
System.out.println("version=" + bean.getVmVersion());
System.out.println("inputArguments=" + inputArguments);
System.out.println("maxMemoryMiB="
+ runtime.maxMemory() / 1024L / 1024L);
System.out.println("totalMemoryMiB="
+ runtime.totalMemory() / 1024L / 1024L);
System.out.println("processors="
+ runtime.availableProcessors());
System.out.println("profile="
+ System.getProperty("app.profile"));
}
}编译运行:
javac JvmFlagsDemo.java
java -Xms64m -Xmx128m -Dapp.profile=demo JvmFlagsDemo app-argPowerShell调用某些本地程序时可能对包含点号、等号的-D参数产生意外解析。若出现Java把.profile=demo当作主类的现象,显式加引号:
java -Xms64m -Xmx128m "-Dapp.profile=demo" JvmFlagsDemo app-arg生产启动脚本应在目标Shell中原样打印并验证最终参数数组,不要只在另一个Shell中测试字符串看起来正确。
应该观察:
inputArguments包含JVM参数和-D,通常不包含app-arg。maxMemoryMiB接近但不应武断要求打印恰好128;Heap对齐和实现会影响可见值。totalMemoryMiB反映当前已提交Heap相关容量,不等于RSS。processors用于观察JVM感知CPU,不等于容器一定获得无限CPU。- 系统属性
app.profile=demo能被读取。
二十二、生产模板必须按版本拆分
下面是“验收项目”,不是复制即上线模板。
22.1 JDK8服务应至少明确
Heap预算:Xms/Xmx
收集器:Parallel/CMS/G1及目标原因
JDK8完整更新版和发行版
GC日志:PrintGCDetails、时间戳、Xloggc、轮转验证
OOM:HeapDumpOnOutOfMemoryError、Dump持久路径
Fatal Error:ErrorFile
容器:总内存预算、CPU识别、线程数示意:
java -Xms2g -Xmx2g \
-XX:+UseG1GC \
-XX:+PrintGCDetails \
-XX:+PrintGCDateStamps \
-XX:+PrintGCTimeStamps \
-Xloggc:/data/logs/gc.log \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/data/dump \
-XX:ErrorFile=/data/error/hs_err_pid%p.log \
-jar app.jar必须在目标8u版本验证日志轮转、路径、容器资源和收集器,不代表所有服务都应该2GiB或G1。
22.2 JDK17/21服务示意
java -Xms2g -Xmx2g \
-XX:+UseG1GC \
-Xlog:gc*,safepoint:file=/data/logs/gc-%t-%p.log:time,uptime,level,tags:filecount=10,filesize=50M \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/data/dump \
-XX:ErrorFile=/data/error/hs_err_pid%p.log \
-jar app.jar迁移时不能只替换Java镜像Tag,还要重新验证启动参数、GC日志采集、TLS、字符集、反射访问、依赖和性能基线。
二十三、商业场景:容器反复OOMKilled
23.1 现象
容器limit为1GiB,启动参数-Xmx900m,日志没有Java heap space,容器Exit 137并重启。
23.2 证据链
flowchart TD
A["容器重启"] --> B["inspect/describe确认OOMKilled与137"]
B --> C["读取实际Path、Args、Env"]
C --> D["jcmd/监控确认Heap、线程、Direct、Metaspace"]
D --> E["核算容器总内存预算"]
E --> F{"Heap泄漏还是总预算不足"}
F -- "Heap基线上升" --> G["Dump/MAT查持有链"]
F -- "Heap正常但RSS超限" --> H["线程栈、Direct、Native、Agent"]不能因为Exit 137就直接说Java Heap泄漏,也不能只把limit加倍。修复可能涉及降低Heap占比、限制线程/Direct Memory、修复泄漏、升级容器感知JDK或调整合理容量。
二十四、商业场景:改了参数却没生效
常见原因:
- 参数写在
-jar之后,成为应用参数。 - Docker只restart旧容器,没有按新环境重建。
- systemd ExecStart覆盖Shell变量。
- Compose多文件合并后值被后文件覆盖。
- 发布平台追加了另一组参数,后值覆盖前值。
- 观察的是另一个实例/PID。
- 参数在目标JDK被忽略、废弃或启动失败后回滚到旧版本。
验证闭环:
docker inspect <container>
jcmd <pid> VM.command_line
jcmd <pid> VM.flags同时核对发布记录、镜像Digest和实例启动时间。
二十五、启动失败排查
Unrecognized VM option
→ 目标JDK不支持、拼写错误、参数已移除
Improperly specified VM option
→ 值、单位或语法错误
Could not reserve enough space for object heap
→ 地址空间/内存限制/Heap配置不满足
Could not create the Java Virtual Machine
→ 向前找第一条具体参数错误标准流程:
- 保存完整启动命令和stderr。
- 执行同一Java二进制的
-version。 - 在隔离环境最小化参数,逐组恢复,不在生产盲删。
- 查版本迁移清单和
PrintFlagsFinal。 - 修复部署模板并补启动验收,避免只手工改当前机器。
二十六、常见误区
| 误区 | 正确理解 |
|---|---|
| Xms默认永远是物理内存1/64 | 默认受JDK、Ergonomics和容器环境影响 |
| Xmx等于容器limit最充分利用内存 | Heap外区域会让进程被OOM Kill |
| 参数写在命令里就一定生效 | 要看最终命令和运行时Flags |
| JAVA_OPTS会被Java自动读取 | 它通常只是启动脚本约定,脚本必须展开 |
| MaxGCPauseMillis是暂停保证 | 只是GC软目标 |
| JDK8都以同样方式识别容器 | 必须看8u更新和发行版 |
| jps没有进程就是Java没运行 | 可能是权限、PerfData或PID命名空间问题 |
| jmap histo:live是无风险查询 | 可能触发Full GC |
| Heap Dump只占磁盘不影响服务 | 可能STW并产生巨大I/O |
| 动态jinfo改完就永久生效 | 重启可能丢失,必须同步部署配置 |
| JDK8 GC日志参数可以用于JDK21 | JDK9+使用统一日志体系 |
| OOMKilled一定有Heap Dump | 内核强杀时JVM可能来不及Dump |
二十七、面试标准回答
JVM参数最终值怎么确认
先从发布配置、启动脚本、systemd或容器inspect还原原始命令,再用
jcmd pid VM.command_line看目标进程命令、jcmd pid VM.flags看最终Flags;离线可用PrintCommandLineFlags和PrintFlagsFinal。还要区分启动配置、JVM最终值和jstat当前状态,不能只看某个脚本文件。
Xmx为什么不能等于容器内存
容器limit约束整个进程cgroup,不只Java Heap。还包含Metaspace、Direct Memory、线程栈、Code Cache、GC/JIT结构、Agent和本地库。Xmx接近limit会没有峰值余量,可能在Heap未满时被cgroup OOM Kill,因此要建立总内存预算并监控Heap与RSS。
JDK7/8和JDK9+ GC日志区别
JDK7/8常用
PrintGCDetails、Date/TimeStamps和-Xloggc,轮转使用历史UseGCLogFileRotation等参数并需按更新版验证;JDK9+使用统一日志-Xlog:gc*及filecount/filesize。迁移JDK时日志参数、文件名和采集解析都要重验。
jcmd、jstat、jstack、jmap分别做什么
jcmd是综合入口,可查版本、命令、Flags、Heap、类加载和线程;jstat适合低成本连续观察GC、类加载和编译趋势;jstack/Thread.print用于线程状态、锁和CPU栈;jmap可看直方图和Dump,但live统计与Heap Dump可能触发GC、停顿和大I/O,生产执行前必须评估风险。
为什么诊断工具attach不上
常见原因是OS用户不一致、PID命名空间不同、临时通信文件权限异常、目标JVM严重卡顿、精简镜像无工具、Attach被禁用或工具版本不匹配。应先确认目标真实PID、用户、JDK版本和执行位置,不应直接把容器改成privileged。
二十八、关联知识点
- JVM组成与内存区域
- HotSpot垃圾收集器与JDK7/8选型
- 对象分配、TLAB、晋升与GC
- JVM常用排查工具
- OOM生产级排查手册
- 线上OOM:Java与Arthas两套方案
- 火焰图与性能采样
- Spring Boot容器化运行
- Docker容器信息与生产诊断
- JavaSE面试标准回答
二十九、学习验收
- [ ] 能把Java启动命令拆成JVM参数、系统属性、入口和应用参数。
- [ ] 能从脚本、systemd、Docker/K8s和运行时Flags还原最终配置。
- [ ] 能解释Xms/Xmx、Xmn/NewRatio、SurvivorRatio和晋升阈值边界。
- [ ] 能区分JDK7 PermGen和JDK8 Metaspace参数。
- [ ] 能计算Heap、Direct、线程栈、Metaspace和容器limit总预算。
- [ ] 能分别写出JDK8和JDK17 GC日志方案并解释不能混用的原因。
- [ ] 能说明Attach失败的权限、PID命名空间和工具版本原因。
- [ ] 能根据风险选择jcmd、jstat、线程栈、Histogram或Heap Dump。
- [ ] 能解释为什么配置修改后必须用VM.command_line和VM.flags验证。
- [ ] 能把参数变更纳入压测、金丝雀、监控和回滚闭环。
本章小结
JVM参数管理的核心是“来源可追踪、版本可兼容、最终值可验证、运行效果可观察、变更可回滚”。命令工具的核心是“先选低风险证据,再按问题升级采集强度”。会背-Xmx和jmap不等于会生产排障;能从启动平台一路证明到运行时Flags,并知道每条诊断命令的成本,才是完整能力。
