所有参数基于 HotSpot JVM。参数后标注版本状态:[JDK8]仅 JDK 8 可用,[JDK17+]需要 JDK 17 及以上,[已移除]表示在后续版本中被删除。
LTS 版本关键差异速查 Java 的 LTS(长期支持)版本是8、11、17、21、25 。跨版本升级时,JVM 参数有实质性变化的集中在以下几个点:
参数行为差异一览 差异点 JDK 8 JDK 11 JDK 17 JDK 21 JDK 25 默认 GC Parallel GC G1 G1 G1 G1 CMS 收集器 ✅ 可用 ⚠️ 已废弃 ❌ 已移除 ❌ ❌ ZGC ❌ 实验性 ✅ 生产可用 ✅分代 ZGC ✅ 分代为唯一模式(非分代已移除) Shenandoah ❌ ❌ ✅ 生产可用 ✅ ✅ GC 日志格式 -Xloggc旧格式-Xlog统一格式-Xlog-Xlog-Xlog偏向锁 默认开启 默认开启 默认关闭 已移除 已移除 强封装 JDK 内部 不封装 不封装 强封装 (--add-opens必须)同 17 同 17 Metaspace 刚替换永久代 稳定 稳定 稳定 稳定 Flight Recorder 仅 Oracle JDK ✅ 免费开放 ✅ ✅ ✅ 字符串去重 默认关闭 默认关闭 默认关闭 默认开启 默认开启 虚拟线程 ❌ ❌ ❌ ✅正式发布 ✅ NMT (Native Memory Tracking)需显式开启 需显式开启 需显式开启 支持detail级别 同 21
已废弃 / 已移除参数对照 升级 JDK 时,启动脚本里的这些参数必须改掉,否则 JVM 会报错或静默忽略。
旧参数 状态 替代方案 -XX:PermSize=NJDK ≤ 7 可用;JDK 8 起忽略(有警告) -XX:MetaspaceSize=N-XX:MaxPermSize=NJDK ≤ 7 可用;JDK 8 起忽略(有警告) -XX:MaxMetaspaceSize=N-XX:+UseConcMarkSweepGCJDK 9 废弃,JDK 14 移除 -XX:+UseG1GC或-XX:+UseZGC-XX:+UseParNewGCJDK 9 废弃,JDK 14 移除 由 G1/ZGC 自动处理 -XX:+CMSIncrementalModeJDK 9 废弃,JDK 14 移除 无直接替代 -XX:+UseBiasedLockingJDK 15 废弃,JDK 18+ 默认关闭 无需替代,JVM 自动选择锁策略 -XX:BiasedLockingStartupDelayJDK 15 废弃,JDK 18+ 默认关闭 无需替代 -XX:+PrintGCDetailsJDK 9 废弃 -Xlog:gc*-XX:+PrintGCDateStampsJDK 9 废弃 -Xlog:gc*:...time-Xloggc:fileJDK 9 废弃 -Xlog:gc*:file=path-XX:+PrintTenuringDistributionJDK 9 废弃 -Xlog:gc+age=trace-XX:+PrintReferenceGCJDK 9 废弃 -Xlog:gc+ref=debug-XX:+PrintAdaptiveSizePolicyJDK 9 废弃 -Xlog:gc+ergo*=trace-XX:+PrintHeapAtGCJDK 9 废弃 -Xlog:gc+heap=debug-XX:MaxGCMinorPauseMillisJDK 23 废弃 -XX:MaxGCPauseMillis
从 JDK 8 升级到 JDK 17 的参数改造清单 这是最常见的升级路径,需要改的东西最多:
# ═══ 必须改 ═══ # 1. GC 收集器:CMS 已移除,换 G1 或 ZGC - -XX:+UseConcMarkSweepGC -XX:+UseParNewGC + -XX:+UseG1GC # 2. GC 日志:旧格式全部换掉 - -XX:+PrintGCDetails - -XX:+PrintGCDateStamps - -Xloggc:/var/log/app/gc.log + -Xlog:gc*,gc+age=trace:file=/var/log/app/gc.log:time,uptime,level,tags:filecount=5,filesize=10m # 3. 永久代参数:删掉 - -XX:PermSize=256m - -XX:MaxPermSize=512m + -XX:MetaspaceSize=256m + -XX:MaxMetaspaceSize=512m # 4. 偏向锁:JDK 17 已默认关闭,显式开启反而有警告 - -XX:+UseBiasedLocking # ↑ 直接删除此行,不需要替代 # 5. 强封装:如果用反射访问 JDK 内部类,必须加 --add-opens + --add-opens java.base/java.lang=ALL-UNNAMED + --add-opens java.base/java.util=ALL-UNNAMED # ═══ 建议改 ═══ # 6. Flight Recorder 现在免费了,可以加上 + -XX:StartFlightRecording=duration=60s,filename=/tmp/jfr.jfr # 7. G1 成为默认 GC,可以不显式指定(但建议还是写上,明确意图)从 JDK 17 升级到 JDK 21 的参数改造 相对平滑,主要变化:
# ═══ 建议改 ═══ # 1. 分代 ZGC 大幅提升,低延迟场景强烈推荐 - -XX:+UseZGC + -XX:+UseZGC -XX:+ZGenerational # 2. 虚拟线程正式发布(不需要 JVM 参数,但需要 JDK 21) # 代码层面:使用 Executors.newVirtualThreadPerTaskExecutor() # 3. 字符串去重默认开启(减少内存,一般不用管) # ═══ 注意 ═══ # 4. 偏向锁参数已被移除,如果还在用会报错 - -XX:+UseBiasedLocking # ↑ 必须删除(JDK 18+ 默认关闭,JDK 19+ 参数已移除) # 5. G1 默认 Region 大小算法优化,通常不需要手动设了 # 如果之前手动设了 G1HeapRegionSize,可以试试去掉让 JVM 自己选容器环境专属参数(Docker / K8s) 这是最容易踩坑的地方 。JVM 在容器里看到的可能不是容器的资源限制,而是宿主机的。
容器感知(Container Awareness) 参数 说明 默认值 建议 -XX:+UseContainerSupport启用容器感知(JVM 读取 cgroup 限制) JDK 10+默认开启 JDK 8u191+ 才有此参数;8u191 之前必须手动升级 JDK -XX:-UseContainerSupport禁用容器感知 — 几乎不要关 ,除非你确定容器限制不合理-XX:MaxRAMPercentage=N堆占容器可用内存的百分比 25%(JDK 8~21)/50%(JDK 22+) 专用容器设75 ;共享容器设50~60 -XX:InitialRAMPercentage=N初始堆占容器可用内存的百分比 1.5625% 通常不用设(配合-Xms) -XX:MinRAMPercentage=N小内存时的最小堆百分比 50% 通常不用改
为什么这很重要 — 不设置的后果 场景:Docker 容器限制 4GB 内存,宿主机 64GB JDK 8u190(无容器感知): JVM 看到 64GB → -Xmx 默认 16GB → 容器被 OOM Killed 💀 JDK 8u191+(有容器感知): JVM 看到 4GB → -Xmx 默认 1GB(25%)→ 正常运行但堆太小 正确配置: -XX:+UseContainerSupport -XX:MaxRAMPercentage=75 → JVM 看到 4GB → -Xmx 约 3GB → 合理 ✅容器里的 CPU 核数 场景:容器限制 2 核,宿主机 32 核 JDK 默认行为: Runtime.getRuntime().availableProcessors() → 32(宿主机核数) ParallelGCThreads → 32 → 创建 32 个 GC 线程 → 浪费内存 + 上下文切换 解决方案: 1. JDK 10+:自动读取 cgroup CPU 限制 ✅ 2. JDK 8:手动指定 -XX:ParallelGCThreads=2 -XX:ConcGCThreads=1 3. 或者设置 -XX:ActiveProcessorCount=2(JDK 8u191+)参数 说明 建议 -XX:ActiveProcessorCount=N覆盖 JVM 检测到的 CPU 核数 设为容器实际的 CPU limit;JDK 10+ 通常自动正确
容器环境推荐配置 # Docker 容器通用模板(JDK 17+) java \ -XX:+UseContainerSupport \ # 启用容器感知 -XX:MaxRAMPercentage = 75 \ # 堆占容器内存的 75% -XX:InitialRAMPercentage = 75 \ # 初始堆也一样(避免扩容) -XX:+UseG1GC \ -XX:MaxGCPauseMillis = 100 \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath = /tmp/heapdump.hprof\ -Xlog:gc*:file= /var/log/app/gc.log:time,uptime:filecount= 3 ,filesize= 10m\ -jar myapp.jar注意 :MaxRAMPercentage=75意味着堆占 75%,剩下 25% 给堆外内存。容器内存越紧张,这个值越要精确计算。
一、堆内存参数 参数 说明 默认值 建议 -Xms初始堆大小 物理内存的 1/64 设为与-Xmx相同 ,避免运行时动态扩容的开销和 STW-Xmx最大堆大小 物理内存的 1/4 根据服务器可用内存计算(见 tuning.md) -Xmn新生代大小 堆的约 1/3 通常为堆的 1/3~1/2;设太大会挤压老年代 -Xss每个线程的栈大小 1MB (64位) 256k~512k 通常够用;线程多时减小可节省内存
要点 :
-Xms和-Xmx设成一样是最重要的一条规则 — 消除扩容带来的停顿-Xmn和-XX:NewRatio二选一,不要同时设二、分代与晋升参数 参数 说明 默认值 建议 -XX:NewRatio老年代:新生代的比例 2(即老年代占 2/3) 一般不用改;对象创建密集可调为 1 -XX:SurvivorRatioEden:每个 Survivor 的比例 8(即 8:1:1) Survivor 放不小可调小(如 6),让 Survivor 更大 -XX:MaxTenuringThreshold对象在 Survivor 中经历 GC 的次数上限 15 默认 15 通常够用;频繁晋升到老年代可适当调大 -XX:PretenureSizeThreshold超过此大小的对象直接进老年代 0(不启用) 仅 Serial/ParNew 生效;G1 用-XX:G1HeapRegionSize间接控制 -XX:+DisableExplicitGC禁用System.gc() 不禁用 建议加上 ,防止代码或第三方库乱调 GC-XX:+AlwaysPreTouch启动时预分配并触碰所有堆内存页 关闭 生产环境建议开启,避免运行时分配内存页的延迟
Survivor 放不下的判断 :
# 观察 GC 日志中是否有: "Desired survivor size XXX bytes, new threshold N" # 如果 threshold 被自动调低了(如降到 3),说明 Survivor 太小 # 解决:减小 SurvivorRatio(如从 8 改为 6),增大 Survivor 空间三、元空间参数 参数 说明 默认值 建议 -XX:MetaspaceSize元空间初始大小 约 20MB 设为 256m,避免启动时频繁触发 GC 扩容 -XX:MaxMetaspaceSize元空间最大大小 无上限 建议设一个上限(如 512m),防止无限增长
什么时候该关注 :
Spring Boot + 大量 AOP 代理 → 动态生成很多类 热部署 / 频繁重新加载 → 类卸载不及时 OOM: Metaspace → 先排查是否有类加载泄漏,再调大上限 JDK ≤ 7 用户注意:永久代参数-XX:PermSize/-XX:MaxPermSize从 JDK 8 起已被忽略(启动时会打印警告)。 升级到 JDK 8+ 后应替换为上方表格中的-XX:MetaspaceSize/-XX:MaxMetaspaceSize。 完整的新旧参数对照见 已废弃 / 已移除参数对照。
四、GC 收集器选择参数 参数 收集器 适用场景 JDK 要求 -XX:+UseSerialGCSerial + Serial Old 单核 / 嵌入式 / 内存 < 100MB 所有版本 -XX:+UseParallelGCParallel Scavenge + Parallel Old 高吞吐、批处理、不在乎延迟 所有版本(JDK 8 默认) -XX:+UseParNewGCParNew(配合 CMS) [JDK8] CMS 的新生代搭档 JDK 8 -XX:+UseConcMarkSweepGCCMS [JDK8] 低延迟 JDK 8(JDK 14 移除) -XX:+UseG1GCG1 通用首选 ,堆 ≤ 16GJDK 7+(JDK 9+ 默认) -XX:+UseZGCZGC 大堆 + 极低延迟 JDK 15+ 正式发布,JDK 17+ 生产推荐 -XX:+UseShenandoahGCShenandoah 低延迟,类似 ZGC JDK 15+(OpenJDK)
选择决策 :
JDK 版本? ├── JDK ≤ 8 → 延迟敏感? │ ├── 是 → -XX:+UseConcMarkSweepGC(CMS) │ └── 否 → -XX:+UseParallelGC(默认) ├── JDK 9~14 → -XX:+UseG1GC(默认,通用最优) └── JDK 15+ → 延迟要求? ├── 极低(< 1ms)→ -XX:+UseZGC ├── 中等(< 200ms)→ -XX:+UseG1GC(默认) └── 不在乎 → -XX:+UseParallelGC五、GC 行为调优参数 通用参数 参数 说明 建议 -XX:ParallelGCThreads=NGC 并行线程数(STW 阶段) 默认 CPU 核数(≤8 时);8 核以上:8 + (N-8)*5/8 -XX:ConcGCThreads=NGC 并发线程数(不停顿阶段) 默认 ParallelGCThreads 的 1/4 -XX:GCTimeRatio=N吞吐量目标:应用时间 / GC 时间 ≥ N 默认 99(即 GC 时间 ≤ 1%)
G1 专用参数 参数 说明 默认值 建议 -XX:MaxGCPauseMillis=N目标最大 GC 停顿时间 200ms 根据业务 SLA 设定,如 Web 服务 100~200ms -XX:G1HeapRegionSize=NRegion 大小 自动(1~32MB) 堆大时可设为 8m 或 16m -XX:InitiatingHeapOccupancyPercent=N堆占用率达到多少时触发并发标记 45% 默认 45%;频繁 Full GC 可调低(如 35) -XX:G1ReservePercent=N保留多少空间防止 to-space exhausted 10% 默认 10%;大对象多可调高 -XX:G1MixedGCCountTarget=NMixed GC 的轮次目标 8 调大可以分摊每轮的回收量 -XX:G1HeapWastePercent=N老年代可回收垃圾低于此比例时停止 Mixed GC 5% 默认 5%;设低可以继续回收更多垃圾
Parallel GC 专用参数 参数 说明 建议 -XX:MaxGCPauseMillis=N目标最大停顿时间 Parallel GC 会自适应调整 -XX:MaxGCMinorPauseMillis=N目标 Minor GC 最大停顿 同上 -XX:GCTimeRatio=N吞吐量比例目标 默认 99,批处理可调到 19(GC ≤ 5%) -XX:+UseAdaptiveSizePolicy自适应调节新生代大小 默认开启,建议保持
ZGC 专用参数 参数 说明 默认值 建议 -XX:MaxGCPauseMillis=N目标停顿时间 1ms ZGC 默认就是亚毫秒,通常不用调 -XX:SoftMaxHeapSize=N软性堆上限(ZGC 会尽量不超) 无 设成接近-Xmx,帮助 ZGC 更积极地回收 -XX:ZCollectionInterval=N固定 GC 间隔(秒),0=不固定 0 需要定期回收可设(如 120) -XX:+ZGenerational启用分代 ZGC(吞吐+延迟双优) 关闭(JDK 21)/默认开启 (JDK 23+) JDK 21 低延迟场景强烈推荐 (JEP 439,生产特性)
六、GC 日志参数 JDK 9+ 统一日志格式(推荐) # 基础配置:GC 日志输出到文件,滚动保留 5 个文件,每个 10MB -Xlog:gc*:file= /var/log/app/gc.log:time,uptime,level,tags:filecount= 5 ,filesize= 10m# 详细配置:包含 GC、堆、元空间信息 -Xlog:gc*,gc+heap= debug:file= /var/log/app/gc.log:time,uptime,level,tags:filecount= 5 ,filesize= 20m# 打印安全点信息(排查 STW 延迟) -Xlog:gc+safepoint= info:file= /var/log/app/gc.logJDK 8 格式(旧) # 基础配置 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/var/log/app/gc.log# 详细配置 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution # 打印对象晋升年龄分布 -XX:+PrintHeapAtGC # GC 前后堆信息 -XX:+PrintReferenceGC # 引用处理耗时 -Xloggc:/var/log/app/gc.log# 大堆时打印堆区域变化(G1) -XX:+PrintAdaptiveSizePolicy 日志中看什么 # JDK 9+ 格式示例 [2024-01-15T10:30:05.123+0800][12.345s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 209M->45M(2048M) 8.234ms ↑ ↑ ↑ ↑ ↑ 第几次GC 类型 触发原因 前→后 耗时 关键指标: - 频率:Young GC 几分钟一次正常,Full GC 几小时一次以上就有问题 - 耗时:Young GC < 50ms 正常,Full GC < 200ms 可接受 - 回收后使用率:GC 后仍 > 80% → 堆不够或有泄漏七、引用类型与 GC 行为 Java 有四种引用类型,GC 对它们的回收策略完全不同。理解这个对写缓存、避免 ThreadLocal 泄漏至关重要。
引用类型 创建方式 GC 行为 典型用途 强引用 Object o = new Object()只要强引用存在,永远不回收 普通变量,绝大多数场景 软引用 SoftReference<Object> sr = new SoftReference<>(obj)内存不足时才回收 缓存(内存紧张时自动清理) 弱引用 WeakReference<Object> wr = new WeakReference<>(obj)下次 GC 就回收 (不管内存够不够)ThreadLocal、WeakHashMap虚引用 PhantomReference<Object> pr = new PhantomReference<>(obj, queue)随时可回收,无法通过引用获取对象 跟踪对象被回收的时机(资源清理)
GC 回收优先级 :
强引用 → 永不回收(OOM 也不回收) ↓ 失去强引用后 软引用 → 内存不足时回收(GC 会尽量保留) ↓ 失去软引用后 弱引用 → 下次 GC 立刻回收 ↓ 失去弱引用后 虚引用 → 入队通知,对象已死实际影响 :
// 1. 软引用做缓存 — 内存紧张时自动清理 Map < String , SoftReference < byte [ ] >> cache= new HashMap < > ( ) ; cache. put ( "key" , new SoftReference < > ( new byte [ 1024 * 1024 ] ) ) ; // 2. ThreadLocal 用弱引用 — 但 key 弱引用不意味着 value 也弱! // ThreadLocalMap 的 key 是弱引用,value 是强引用 // → 线程不销毁时 value 永远不会被回收 → 内存泄漏! ThreadLocal < Session > session= ThreadLocal . withInitial ( Session :: new ) ; // 必须在使用完后显式 remove() session. remove ( ) ; // 3. WeakHashMap — key 被回收时整个 entry 自动移除 Map < Key , Value > map= new WeakHashMap < > ( ) ; GC 调优相关 :
大量软引用对象 → Minor GC 时需要额外判断"内存是否充足",可能拖慢 GC -XX:SoftRefLRUPolicyMSPerMB=N:控制软引用的存活时间(默认 1000ms/MB 空闲堆),调小可以更积极地回收软引用八、JIT 编译参数 参数 说明 建议 -XX:+TieredCompilation启用分层编译(C1 + C2) JDK 8+ 默认开启,不要关 -XX:CompileThreshold=N方法编译阈值(调用多少次触发 JIT) 默认 10000(分层编译下自动调节,一般不改) -XX:ReservedCodeCacheSize=NJIT 编译后的机器码缓存大小 默认 240MB;大型应用可调到 512m -XX:+PrintCompilation打印 JIT 编译事件 排查编译问题时临时开启 -XX:-UseCompilation禁用 JIT(纯解释执行) 永远不要在生产环境用 ,仅供调试
九、直接内存参数 参数 说明 默认值 建议 -XX:MaxDirectMemorySize=N直接内存最大大小 等于-Xmx 不用 NIO 可设小(如 256m);NIO 密集按需调大
什么时候关注 :
Netty / NIO 应用 → 直接内存用量大 OOM: Direct buffer memory → 检查是否有 buffer 未释放 十、诊断与调试参数 OOM 时自动保存现场 # 发生 OOM 时自动 dump 堆(必加) -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath = /var/log/app/heapdump.hprof# OOM 时直接退出进程(配合 K8s 自动重启,容器环境必加) -XX:+ExitOnOutOfMemoryError # OOM 时执行自定义脚本(如发告警) -XX:OnOutOfMemoryError = "/opt/scripts/oom-alert.sh %p" 参数 说明 建议 -XX:+HeapDumpOnOutOfMemoryErrorOOM 时自动导出堆转储 生产环境必加 -XX:HeapDumpPath=N堆转储文件路径 指向有足够空间的目录 -XX:+ExitOnOutOfMemoryErrorOOM 时直接退出 JVM 容器环境必加 ,配合 K8s 自动重启-XX:OnOutOfMemoryError=cmdOOM 时执行自定义命令 用于发告警或执行清理脚本
JFR(Java Flight Recorder) # JDK 11+ 免费使用(JDK 8 需 Oracle JDK) -XX:StartFlightRecording = duration= 60s,filename= /tmp/recording.jfr-XX:FlightRecorderOptions = stackdepth= 128 常用诊断开关 参数 说明 场景 -XX:+PrintFlagsFinal启动时打印所有 JVM 参数的最终值 确认参数是否生效 -XX:+PrintVMOptions打印启动时的 JVM 选项 排查启动问题 -verbose:gc每次 GC 打印一行摘要 快速观察 GC 行为 -XX:+UnlockDiagnosticVMOptions解锁诊断参数 使用高级诊断参数时需要先加这个 -XX:+PrintSafepointStatistics打印安全点统计 排查 STW 延迟 -XX:NativeMemoryTracking=summary跟踪 JVM 本地内存使用 jcmd <pid> VM.native_memory summary查看
十一、常用 JDK 诊断命令速查 命令 用途 示例 jps -l列出 Java 进程 找到目标进程 PID jstat -gcutil <pid> 1000每秒打印 GC 使用率 实时观察 GC 状态 jstat -gc <pid> 1000 10每秒打印 GC 详情,共 10 次 详细分析 jmap -heap <pid>查看堆配置和使用情况 快速检查堆状态 jmap -dump:format=b,file=heap.hprof <pid>导出堆转储 MAT 分析内存泄漏 jmap -histo <pid>对象数量和大小的直方图 快速找大对象 jstack <pid>线程堆栈快照 排查死锁、CPU 高 jstack -l <pid>线程堆栈 + 锁信息 排查锁竞争 jinfo -flags <pid>查看 JVM 启动参数 确认线上参数 jcmd <pid> VM.flags查看 JVM 参数 同 jinfo jcmd <pid> GC.heap_dump /tmp/dump.hprof堆转储(推荐替代 jmap) 对生产影响更小 jcmd <pid> VM.native_memory summary本地内存概况 需开启 NMT
Arthas(生产环境推荐) # 安装 curl -O https://arthas.aliyun.com/arthas-boot.jarjava -jar arthas-boot.jar# 常用命令 dashboard# 实时仪表盘(线程、内存、GC) thread-n 3 # CPU 最高的 3 个线程 watch com.example.Service getUser'{params, returnObj, throwExp}' -n 5 # 观察方法调用 trace com.example.Service getUser'#cost > 100' # 方法耗时追踪 sc-d com.example.Service# 查看类加载信息 jad com.example.Service# 反编译(确认线上代码版本)