JVM参数必须在java命令启动时传入,不能写进Java代码或依赖系统全局配置;JAVA_HOME等环境变量仅指定JDK路径,不传递JVM参数;真正生效的是启动命令中显式的-Xms、-XX等选项。

直接说结论:JVM参数必须在 java 命令启动时传入,不是写进 Java 代码里,也不是改系统全局配置就能生效的。
参数必须随 java 命令一起启动才生效
很多人误以为改了 JAVA_HOME 或 /etc/environment 就能影响所有 Java 进程 —— 实际上这些环境变量本身不传递 JVM 参数,只是指定 JDK 路径。真正起作用的是启动命令里显式写的 -Xms、-XX:+UseG1GC 这类选项。
-
JAVA_OPTS只是约定俗成的变量名,它本身不会自动生效,必须手动拼进java $JAVA_OPTS -jar app.jar才行 - 如果用
systemd启动,Environment="JAVA_OPTS=..."必须配合ExecStart=/usr/bin/java $JAVA_OPTS -jar ...,否则变量被忽略 - 直接写死在
ExecStart里(如java -Xmx2g -jar ...)反而更可靠,避免 shell 变量展开失败或空格截断
-Xms 和 -Xmx 必须配对,且值建议相等
设 -Xms512m -Xmx2g 看似“省内存”,但实际会让 JVM 在运行中反复扩容堆,触发额外 GC,尤其在高负载下容易卡顿。生产环境强烈建议设成相同值,比如 -Xms2g -Xmx2g。
- 如果物理内存不足,
-Xmx超过可用内存会导致java进程启动失败,报错Could not reserve enough space for object heap - 单位大小写敏感:
-Xmx2G合法,-Xmx2g也合法,但-Xmx2GM或漏掉单位(如-Xmx2)会直接报错 - 不要混用单位:
-Xms512m -Xmx2g是允许的,但可读性差,统一用m或g更稳妥
Java 8+ 必须用 -XX:MetaspaceSize,别再写 -XX:PermSize
-XX:PermSize 和 -XX:MaxPermSize 在 Java 8 及之后已废弃,继续使用不会报错但完全无效,元空间(Metaspace)会无限增长直到 OOM。正确写法是:
立即学习“Java免费学习笔记(深入)”;
java -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -jar app.jar
-
-XX:MetaspaceSize是触发首次 Metaspace GC 的阈值,不是初始分配量 —— JVM 启动时实际只分配很小一块,按需增长 - 如果应用加载大量类(如 Spring Boot + 多模块),
-XX:MaxMetaspaceSize设太小会频繁 Full GC;设太大又可能掩盖类加载泄漏 - 可通过
jstat -gc <pid>观察MU(Metaspace used)和MC(Metaspace capacity)来调优
systemd 服务里配 JVM 参数最容易漏掉 EnvironmentFile 或引号
在 /etc/systemd/system/myapp.service 里写 Environment="JAVA_OPTS=-Xms2g -Xmx2g" 看似没问题,但 shell 解析时若参数含空格或特殊字符(如 -Dfile.encoding=UTF-8),没加引号就会断裂。
- 更安全的做法是用
EnvironmentFile单独存参数:EnvironmentFile=/etc/default/myapp,然后在该文件里写JAVA_OPTS="-Xms2g -Xmx2g -XX:MaxMetaspaceSize=512m" -
ExecStart必须写成ExecStart=/usr/bin/java $JAVA_OPTS -jar /opt/app.jar,不能写成ExecStart=/usr/bin/java ${JAVA_OPTS} ...(systemd 不支持${}语法) - 改完 service 文件后,必须执行
sudo systemctl daemon-reload,否则systemctl restart不会加载新参数
最常被忽略的一点:JVM 参数生效与否,不能只看启动命令有没有写,得用 jinfo -flags <pid> 或 ps -ef | grep java 确认进程实际加载的参数。有时候脚本里变量没导出、systemd 没 reload、甚至 jar 包自己带了内嵌启动器(如 Spring Boot 的 Launcher),都会导致你写的参数根本没进 JVM。


















