JVM 调优完全手册(Java 8 版)

本文是一份作者总结系统化的 JVM 调优参考文档,涵盖内存模型、GC 回收器选型、参数配置、监控工具、问题诊断与调优决策全流程。适用于生产环境故障排查与性能优化场景。

一、JVM 内存模型

1.1 运行时数据区域

#mermaid-svg-JWdO0ns36HSGBIvs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JWdO0ns36HSGBIvs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JWdO0ns36HSGBIvs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JWdO0ns36HSGBIvs .error-icon{fill:#552222;}#mermaid-svg-JWdO0ns36HSGBIvs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JWdO0ns36HSGBIvs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JWdO0ns36HSGBIvs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JWdO0ns36HSGBIvs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JWdO0ns36HSGBIvs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JWdO0ns36HSGBIvs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JWdO0ns36HSGBIvs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JWdO0ns36HSGBIvs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JWdO0ns36HSGBIvs .marker.cross{stroke:#333333;}#mermaid-svg-JWdO0ns36HSGBIvs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JWdO0ns36HSGBIvs p{margin:0;}#mermaid-svg-JWdO0ns36HSGBIvs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-JWdO0ns36HSGBIvs .cluster-label text{fill:#333;}#mermaid-svg-JWdO0ns36HSGBIvs .cluster-label span{color:#333;}#mermaid-svg-JWdO0ns36HSGBIvs .cluster-label span p{background-color:transparent;}#mermaid-svg-JWdO0ns36HSGBIvs .label text,#mermaid-svg-JWdO0ns36HSGBIvs span{fill:#333;color:#333;}#mermaid-svg-JWdO0ns36HSGBIvs .node rect,#mermaid-svg-JWdO0ns36HSGBIvs .node circle,#mermaid-svg-JWdO0ns36HSGBIvs .node ellipse,#mermaid-svg-JWdO0ns36HSGBIvs .node polygon,#mermaid-svg-JWdO0ns36HSGBIvs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JWdO0ns36HSGBIvs .rough-node .label text,#mermaid-svg-JWdO0ns36HSGBIvs .node .label text,#mermaid-svg-JWdO0ns36HSGBIvs .image-shape .label,#mermaid-svg-JWdO0ns36HSGBIvs .icon-shape .label{text-anchor:middle;}#mermaid-svg-JWdO0ns36HSGBIvs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-JWdO0ns36HSGBIvs .rough-node .label,#mermaid-svg-JWdO0ns36HSGBIvs .node .label,#mermaid-svg-JWdO0ns36HSGBIvs .image-shape .label,#mermaid-svg-JWdO0ns36HSGBIvs .icon-shape .label{text-align:center;}#mermaid-svg-JWdO0ns36HSGBIvs .node.clickable{cursor:pointer;}#mermaid-svg-JWdO0ns36HSGBIvs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-JWdO0ns36HSGBIvs .arrowheadPath{fill:#333333;}#mermaid-svg-JWdO0ns36HSGBIvs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-JWdO0ns36HSGBIvs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-JWdO0ns36HSGBIvs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JWdO0ns36HSGBIvs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JWdO0ns36HSGBIvs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JWdO0ns36HSGBIvs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-JWdO0ns36HSGBIvs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JWdO0ns36HSGBIvs .cluster text{fill:#333;}#mermaid-svg-JWdO0ns36HSGBIvs .cluster span{color:#333;}#mermaid-svg-JWdO0ns36HSGBIvs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-JWdO0ns36HSGBIvs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JWdO0ns36HSGBIvs rect.text{fill:none;stroke-width:0;}#mermaid-svg-JWdO0ns36HSGBIvs .icon-shape,#mermaid-svg-JWdO0ns36HSGBIvs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JWdO0ns36HSGBIvs .icon-shape p,#mermaid-svg-JWdO0ns36HSGBIvs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-JWdO0ns36HSGBIvs .icon-shape .label rect,#mermaid-svg-JWdO0ns36HSGBIvs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JWdO0ns36HSGBIvs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JWdO0ns36HSGBIvs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JWdO0ns36HSGBIvs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-JWdO0ns36HSGBIvs .privateStyle>*{fill:#3498DB!important;color:#fff!important;stroke:#1A5276!important;stroke-width:2px!important;font-weight:bold!important;}#mermaid-svg-JWdO0ns36HSGBIvs .privateStyle span{fill:#3498DB!important;color:#fff!important;stroke:#1A5276!important;stroke-width:2px!important;font-weight:bold!important;}#mermaid-svg-JWdO0ns36HSGBIvs .privateStyle tspan{fill:#fff!important;}#mermaid-svg-JWdO0ns36HSGBIvs .sharedStyle>*{fill:#E67E22!important;color:#fff!important;stroke:#7E5109!important;stroke-width:2px!important;font-weight:bold!important;}#mermaid-svg-JWdO0ns36HSGBIvs .sharedStyle span{fill:#E67E22!important;color:#fff!important;stroke:#7E5109!important;stroke-width:2px!important;font-weight:bold!important;}#mermaid-svg-JWdO0ns36HSGBIvs .sharedStyle tspan{fill:#fff!important;}#mermaid-svg-JWdO0ns36HSGBIvs .defaultStyle>*{fill:#ECF0F1!important;color:#2C3E50!important;stroke:#BDC3C7!important;}#mermaid-svg-JWdO0ns36HSGBIvs .defaultStyle span{fill:#ECF0F1!important;color:#2C3E50!important;stroke:#BDC3C7!important;}#mermaid-svg-JWdO0ns36HSGBIvs .defaultStyle tspan{fill:#2C3E50!important;} Java 8 HotSpot JVM 运行时数据区域
🌐 堆内存 (Heap) - 线程共享
新生代 (Young Generation)
YGC 复制 (年龄+1)
反复交换
晋升 (年龄≥阈值)
Eden (80%)
Survivor0 (10%)
Survivor1 (10%)
老年代 (Old Generation)
非堆内存 (Non-Heap)
🌐 线程共享 (Thread-Shared)
元空间 (Metaspace)

(方法区的 HotSpot 实现)
内含: 运行时常量池

(每个类/接口的常量池)
直接内存 (Direct Memory)

(NIO 堆外内存)
CodeCache

(JIT 编译代码缓存)
🧵 线程私有 (Thread-Private)
pc 寄存器

(记录字节码指令地址)
Java 虚拟机栈

(-Xss 控制大小)
本地方法栈

(支持 native 方法)

1.2 各区域说明

以下是完整的分类表格:

🧵 线程私有(Thread-Private)

区域名称 所属范畴 说明
pc 寄存器 JVM 规范 记录当前线程执行的字节码指令地址,线程切换后用于恢复执行位置
Java 虚拟机栈(HotSpot 中即"线程栈") JVM 规范 存储每个方法的栈帧(局部变量、操作数栈、方法返回地址等),由 -Xss 控制大小
本地方法栈 JVM 规范 native 方法服务,在 HotSpot 中与 Java 虚拟机栈合二为一

🌐 线程共享(Thread-Shared)

区域名称 所属范畴 说明
堆(Heap) JVM 规范 存放所有类实例和数组,GC 管理的主要区域(HotSpot 进一步分代为 Eden/S0/S1/老年代)
方法区(Method Area) JVM 规范 存储类结构信息(字段、方法数据、构造器代码等)
运行时常量池 JVM 规范 每个类/接口的常量池,分配在方法区内
元空间(Metaspace) HotSpot 特有(方法区的实现) 使用本地内存存放类的元数据,替代永久代
直接内存(Direct Memory) HotSpot 特有 堆外内存,供 NIO 使用,通过 -XX:MaxDirectMemorySize 限制
CodeCache HotSpot 特有 存储 JIT 编译器生成的本地机器码,供所有线程调用

1.3 对象分配与流转规则

对象分配位置:

分配位置 条件
Eden 区 绝大多数对象在此分配
老年代 大对象(超过 -XX:PretenureSizeThreshold)
TLAB(Thread Local Allocation Buffer 线程本地分配缓冲区,位于 Eden 区内部

晋升到老年代的条件:

条件 说明 控制参数
年龄达到阈值 每经历一次 YGC 且存活,年龄 +1 -XX:MaxTenuringThreshold(默认 15)
动态年龄判定 Survivor 中某年龄及以上对象总大小超过 Survivor 一半时,该年龄及以上对象提前晋升 JVM 自动计算
提前晋升 YGC 时存活对象 > Survivor 可用空间,全部直接晋升 -XX:SurvivorRatio 间接控制
大对象直接分配 超过阈值的大小直接在老年代分配 -XX:PretenureSizeThreshold

二、GC 回收器类型与选型指南

2.1 回收器类型总览

Java 8 中可用的四种垃圾回收器,按设计目标分类:

回收器 设计目标 线程模型 核心特点 适用场景
Serial 单线程效率 单线程,STW 实现简单,无线程开销 客户端应用,小内存(< 1GB)
Parallel GC(默认) 高吞吐量 多线程,STW 吞吐量最高,Full GC 扫描全堆 批处理,后台任务,计算密集型
CMS 低延迟 多线程,并发 暂停时间短,存在碎片问题 Web 应用,交互式系统
G1 可预测停顿 多线程,并发+并行 分区回收,停顿可控,内存压缩 大堆内存(> 4GB)

设计目标的权衡关系:

吞吐量和低延迟是相互制约的。追求高吞吐量意味着更长的 STW 暂停;追求低延迟意味着更频繁的并发回收,占用 CPU 资源,牺牲吞吐量。

2.2 各回收器详解

Serial GC(串行回收器)

属性 说明
启用参数 -XX:+UseSerialGC
年轻代回收 Serial(单线程,STW,复制)
老年代回收 Serial Old(单线程,STW,标记-整理)
设计目标 单线程效率
最佳堆大小 < 1GB

回收时只使用一个线程,暂停所有应用线程。现代服务器多为多核 CPU,无法利用多核优势。适用于单核 CPU 或内存小于 1GB 的客户端应用。

Parallel GC(并行回收器)------ Java 8 默认

属性 说明
启用参数 -XX:+UseParallelGC(自动启用 -XX:+UseParallelOldGC)
年轻代回收 Parallel Scavenge(多线程,STW,复制)
老年代回收 Parallel Old(多线程,STW,标记-整理)
设计目标 高吞吐量优先
最佳堆大小 4GB - 8GB

年轻代和老年代回收均使用多线程并行执行,但会暂停所有应用线程。JVM 会根据运行情况自适应调整各区域大小(-XX:+UseAdaptiveSizePolicy),以达到设定的吞吐量目标。

优点:吞吐量最高(GC 时间占比最小);回收后内存压缩,无碎片问题。缺点:Full GC 需扫描整个堆,堆越大停顿越长。

适用于批处理任务、后台作业、对响应时间不敏感的计算密集型应用。不适用于 Web 应用或交互式系统。

CMS(Concurrent Mark Sweep)回收器

属性 说明
启用参数 -XX:+UseConcMarkSweepGC(需配合 -XX:+UseParNewGC)
年轻代回收 ParNew(多线程,STW,复制)
老年代回收 CMS(多线程,并发,标记-清除)
设计目标 低延迟优先
最佳堆大小 4GB - 8GB

老年代回收的大部分阶段(并发标记、并发清理)与应用线程并发执行,不暂停应用。只在初始标记和最终标记阶段短暂 STW。

回收阶段:初始标记(STW,短暂)→ 并发标记(应用运行)→ 最终标记(STW,短暂)→ 并发清理(应用运行)

优点:老年代回收时应用不暂停,GC 停顿时间短(毫秒级)。缺点:存在内存碎片(标记-清除算法不压缩);并发回收占用 CPU 资源;可能发生并发模式失败(回收速度 < 对象分配速度,退化为 Serial Full GC)。

适用于 Web 应用、微服务、对响应时间要求高的交互式系统。不适用于大堆(> 16GB)或批处理任务。

G1(Garbage-First)回收器

属性 说明
启用参数 -XX:+UseG1GC
堆结构 分区(Region),不区分固定年轻代/老年代
设计目标 可预测停顿
最佳堆大小 > 4GB

将整个堆划分为若干个大小相等的 Region(默认约 2048 个)。每个 Region 可以扮演 Eden、Survivor 或老年代的角色。G1 优先回收垃圾最多的 Region(Garbage-First 名称的来源)。

与 Parallel GC/CMS 的本质区别:G1 没有固定的年轻代/老年代物理边界,回收粒度是部分 Region 而非整个代;停顿时间可通过 -XX:MaxGCPauseMillis 控制;分区回收自带内存压缩,无碎片问题。

优点:停顿时间可预测;分区回收不扫描全堆;自带内存压缩无碎片;适应性最强。缺点:Java 8 早期版本(u40 之前)不稳定;相比 Parallel GC,吞吐量略低。

适用于堆大小 > 4GB、对停顿时间有明确要求的应用,是 Java 8+ 的通用推荐(尤其是 Java 8u40 之后)。

2.3 选型决策矩阵

基于堆大小的选型建议:

堆大小 推荐回收器 理由
< 1GB Serial 单线程效率最高,无多线程开销
1GB - 4GB Parallel GC 或 CMS 并行回收吞吐量高;CMS 可降低延迟
4GB - 8GB CMS 或 G1 CMS 成熟稳定;G1 在 Java 8u40 后成熟
> 8GB G1 分区回收避免全堆扫描,停顿可控

基于业务场景的选型建议:

业务场景 推荐回收器 理由
批处理/离线计算 Parallel GC 吞吐量优先
Web 应用(4-8GB) CMS 低延迟优先
Web 应用(> 8GB) G1 可控停顿,无碎片
微服务(容器化) G1 适应性最强
通用推荐(Java 8+) G1 综合最优

选型决策树:

text

复制代码
开始
  │
  ├── 堆内存 < 1GB?
  │     ├── 是 → Serial GC
  │     └── 否 ↓
  │
  ├── 对响应时间要求极高(毫秒级)?
  │     ├── 是 ↓
  │     │     ├── 堆 < 8GB?→ CMS
  │     │     └── 堆 > 8GB?→ G1
  │     └── 否 ↓
  │
  ├── 业务类型为批处理/后台计算?
  │     ├── 是 → Parallel GC
  │     └── 否 ↓
  │
  ├── 堆 > 4GB?
  │     ├── 是 → G1
  │     └── 否 → CMS 或 Parallel GC

2.4 选型约束

约束 说明 影响选型
Java 版本 CMS 在 Java 9 中已废弃,G1 在 Java 9+ 为默认 Java 8 可用 CMS;Java 9+ 推荐 G1
容器环境 容器内存限制需 JVM 识别 Java 8u131 之前需手动设置 -Xmx
物理内存 JVM 堆 + 元空间 + 线程栈 + 直接内存 + 操作系统 堆不能超过物理内存的 70-80%
CPU 核数 并发回收器需要额外的 CPU 资源 低配服务器(< 4 核)慎用 CMS/G1

2.5 选型验证方法

选型不是一次性决策,需要压测验证:

  1. 配置选定的 GC 参数
  2. 使用压测工具(JMeter、Gatling)模拟业务流量
  3. 采集 jstat -gcutil 数据,重点关注:FGC 是否增长、最大 GC 停顿时间是否满足业务要求、GC 吞吐量是否在可接受范围
  4. 如果验证不通过,在当前选型基础上微调参数
  5. 微调仍无效,回到决策矩阵重新选型

三、核心 JVM 参数

3.1 内存容量参数

参数 含义 默认值 建议
-Xms 初始堆大小 物理内存的 1/64 与 -Xmx 一致,避免扩容开销
-Xmx 最大堆大小 物理内存的 1/4 根据业务需求设定,留 20-30% 给系统
-Xmn 年轻代大小 堆的 1/3 根据对象分配速率调整
-XX:NewRatio 老年代:年轻代 2 吞吐量优先可调大(如 3),低延迟可调小(如 1)
-XX:SurvivorRatio Eden:Survivor 8 存活率高时调小(如 4),存活率低时调大(如 16)
-Xss 线程栈大小 平台相关(通常 1MB) 高并发时调小(如 256K)
-XX:MetaspaceSize 元空间触发 GC 阈值 约 20.8MB 根据类加载量设置,避免频繁扩容 FGC
-XX:MaxMetaspaceSize 最大元空间 不限制(物理内存上限) 强烈建议设置,避免物理内存耗尽
-XX:MaxDirectMemorySize 最大直接内存 与 -Xmx 一致 NIO 大量使用时显式设置

关于 MetaspaceSize 的说明: 该参数不是"元空间初始大小",而是"触发 FGC 的阈值"。当元空间使用量达到该值时,JVM 会触发 FGC 来回收类元数据。

3.2 GC 行为调优参数(以 CMS 为例)

参数 含义 建议值
-XX:CMSInitiatingOccupancyFraction CMS 触发阈值(老年代占用百分比) 70-75
-XX:+UseCMSInitiatingOccupancyOnly 固定使用设定阈值 推荐开启
-XX:+CMSClassUnloadingEnabled 并发回收类元数据 推荐开启
-XX:+UseCMSCompactAtFullCollection FGC 时压缩碎片 推荐开启
-XX:CMSFullGCsBeforeCompaction 多少次 FGC 后压缩 0(每次压缩)

3.3 GC 行为调优参数(以 G1 为例)

参数 含义 建议值
-XX:MaxGCPauseMillis 目标最大 GC 停顿时间 200ms
-XX:G1HeapRegionSize 分区大小 自动,可设 1/2/4/8/16/32MB
-XX:G1NewSizePercent 年轻代初始比例 5%
-XX:G1MaxNewSizePercent 年轻代最大比例 60%
-XX:G1ReservePercent 预留空间比例 10%

3.4 诊断与日志参数

参数 作用
-XX:+PrintGCDetails 打印 GC 详细信息
-XX:+PrintGCDateStamps 打印日期时间戳
-XX:+PrintGCTimeStamps 打印 JVM 启动后相对时间
-XX:+PrintGCApplicationStoppedTime 打印 STW 暂停时间
-XX:+PrintTenuringDistribution 打印年龄分布
-Xloggc:/path/to/gc.log GC 日志输出路径
-XX:+UseGCLogFileRotation 启用 GC 日志轮转
-XX:NumberOfGCLogFiles=5 日志文件数量
-XX:GCLogFileSize=100M 单个日志文件大小
-XX:+HeapDumpOnOutOfMemoryError OOM 时导出堆转储
-XX:HeapDumpPath=/path/to/heap.hprof 堆转储路径
-XX:+DisableExplicitGC 禁用 System.gc() 显式调用

关于 GCLogFileRotation: Java 8 中该功能存在已知 bug(某些版本下日志轮转会丢失日志),在 Java 8u242 之后修复。如使用早期版本,建议使用 logrotate 等外部工具替代。

四、监控与诊断工具(自带)

4.1 命令行工具速查

命令 用途 常用选项
jps -l 查看 Java 进程 PID -l(显示完整类名)
jstat -gcutil 5000 GC 使用率实时监控 -gcutil, -gc, -gccause
jmap -heap 查看堆内存配置和摘要 -heap, -histo:live, -dump
jstack -l 查看线程堆栈 -l(打印锁信息)
jcmd VM.flags 查看 JVM 所有参数 VM.flags, GC.heap_info
jcmd GC.heap_dump 导出堆转储 -

注意事项:

  • jmap -histo:live 会触发一次 Full GC,生产环境慎用
  • jmap -heap 在容器环境中可能受限,建议使用 jcmd <pid> GC.heap_info 替代

4.2 jstat 输出详解

jstat -gcutil 输出列:

含义 健康值 异常信号
S0 Survivor 0 使用率 与 S1 交替为 0 S0=S1=0 或持续 100%
S1 Survivor 1 使用率 与 S0 交替为 0 同上
E Eden 使用率 波动,YGC 后降低 持续 > 95% 表示 YGC 频繁
O 老年代使用率 < 80% > 80% 需关注,持续增长可能泄漏
M 元空间使用率 < 80% > 80% 需关注
YGC YGC 次数 随运行增长 增长过快表示 Eden 太小
YGCT YGC 总耗时(秒) 平均 < 50ms 平均 > 100ms 需关注
FGC Full GC 次数 极少 频繁增长是核心问题信号
FGCT Full GC 总耗时(秒) 占比 < 10% > 30% 严重异常
GCT GC 总耗时(秒) - -

4.3 jstat 异常模式速查

现象 可能原因 验证方法 解决方案
FGC↑ O↑ 老年代空间不足 jmap -histo 查看大对象 增大 -Xmx 或排查泄漏
FGC↑ O↓ 晋升失败或 System.gc() 代码审查 + 查看 GC 日志 加 -XX:+DisableExplicitGC;检查 Survivor 配置
FGC↑ M↑ 元空间不足 检查类加载数量 增大 -XX:MetaspaceSize
YGC↑ E↑ Eden 太小 查看 YGC 间隔 增大 -Xmn
YGC↑ O↑ Survivor 太小导致提前晋升 S0/S1 为 0 调整 -XX:SurvivorRatio
S0=S1=0 Survivor 完全失效 查看 GC 日志确认提前晋升 增大 Survivor 或切换 CMS/G1
长时间 STW CMS 并发模式失败 查看 GC 日志 调低 CMSInitiatingOccupancyFraction

4.4 jmap -heap 关键输出解读

bash

复制代码
jmap -heap <pid>

重点关注:

输出项 含义 检查要点
MaxHeapSize -Xmx 是否生效 与配置一致
NewRatio 老年代/年轻代比例 是否符合业务
SurvivorRatio Eden/Survivor 比例 是否过小导致晋升
Eden Space used Eden 实际使用量 接近 100% 表示 YGC 频繁
Old Generation used 老年代实际使用量 持续增长可能内存泄漏

4.5 GC 日志关键信号

日志内容 含义 响应动作
Full GC (Metadata GC Threshold) 元空间触发 FGC 增大 MetaspaceSize
Full GC (Ergonomics) JVM 自适应触发 检查 -Xmx 是否合理
concurrent mode failure CMS 并发回收跟不上 调低 CMSInitiatingOccupancyFraction
Promotion Failed 晋升失败 检查 Survivor 或调大老年代
PSYoungGen: X->0 存活对象全部移出年轻代 提前晋升,检查 Survivor

五、常见问题与调优方案

5.1 Full GC 频繁

现象: FGC 次数快速增长,FGCT 占比高

排查步骤:

text

复制代码
第一步:jstat -gcutil <pid> 5000 确认 FGC 确实频繁
第二步:看 O 列(老年代使用率)
第三步:看 M 列(元空间使用率)
第四步:检查代码中是否有 System.gc()

根因与方案:

根因 特征 方案
老年代空间不足 O > 80%,持续增长 增大 -Xmx;或排查内存泄漏
晋升失败 O < 30%,S0/S1=0 调大 Survivor 或改用 CMS/G1
System.gc() 显式调用 代码审查确认 添加 -XX:+DisableExplicitGC
元空间不足 M > 80% 增大 -XX:MaxMetaspaceSize

5.2 YGC 频繁

现象: YGC 次数增长过快,YGCT 占比高

根因: Eden 区太小,对象分配速率过高

方案:

  • 增大 -Xmn(年轻代大小)
  • 或保持 -Xmn 不变,通过 -XX:NewRatio 降低老年代占比
  • 检查是否存在大量短生命周期对象

5.3 GC 停顿时间过长

现象: 单次 GC 耗时超过阈值,业务卡顿

根因与方案:

根因 方案
Parallel GC Full GC 扫描全堆 切换 CMS 或 G1
堆太大(> 16GB) 考虑 G1 或 ZGC
Survivor 太小导致提前晋升 调大 SurvivorRatio
CMS 并发模式失败 调低 CMSInitiatingOccupancyFraction

5.4 内存泄漏

现象: 老年代使用率持续增长,FGC 后不下降

排查步骤:

text

复制代码
1. jmap -histo:live <pid> | head -20   # 查看存活对象统计
2. 多次采样对比,找到持续增长的类型
3. jmap -dump:live,format=b,file=heap.hprof <pid>  # 导出堆转储
4. 用 VisualVM 或 MAT 分析堆转储,定位泄漏根因

注意事项: jmap -histo:live 会触发 Full GC,生产环境操作需谨慎。

5.5 CPU 飙高

排查步骤:

text

复制代码
1. top -H -p <pid>           # 找到 CPU 最高的线程 ID(十进制)
2. printf "%x\n" <tid>       # 转十六进制
3. jstack <pid> > stack.log  # 导出线程堆栈
4. 在堆栈中搜索十六进制 TID  # 定位具体代码

5.6 线程死锁

排查:

bash

复制代码
jstack -l <pid> | grep "Found one Java-level deadlock" -A 20

方案: 根据堆栈信息修复代码中的锁顺序问题

六、调优决策矩阵

6.1 GC 算法选择

text

复制代码
堆大小 < 4GB?
    ├── 是 → 响应时间要求高?
    │       ├── 是 → CMS
    │       └── 否 → Parallel GC
    └── 否 → G1GC

注:Java 8u40+ 推荐 G1GC 作为默认选项,适应性最强

6.2 内存分配决策

text

复制代码
业务类型判断
    ├── 高并发 Web 应用(大量短生命周期对象)
    │       → 年轻代占比 30-40%,SurvivorRatio=4-8
    ├── 批处理/数据分析(大量长生命周期对象)
    │       → 年轻代占比 20-30%,老年代占比 70-80%
    └── 混合型应用
            → 年轻代占比 33%(默认),观察 GC 数据后调整

6.3 调优优先级

text

复制代码
优先级 1(必做):
    - 设置 -Xms = -Xmx
    - 设置 -XX:+DisableExplicitGC
    - 开启 GC 日志

优先级 2(根据场景):
    - 选择合适的 GC 算法
    - 设置堆大小(不超过物理内存 70%)
    - 设置元空间上限(-XX:MaxMetaspaceSize)

优先级 3(精细调优):
    - 调整年轻代/老年代比例
    - 调整 Eden/Survivor 比例
    - 调整 GC 触发阈值

优先级 4(可观测性):
    - 接入监控告警系统
    - GC 日志自动采集到 ELK
    - 设置关键指标告警阈值

七、日志与监控

7.1 推荐 GC 日志配置

bash

复制代码
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintTenuringDistribution
-Xloggc:/data/logs/gc.log
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=10
-XX:GCLogFileSize=100M

7.2 容器环境特殊配置

容器环境(Docker/K8s)下 JVM 调优需额外注意:

配置项 说明
Java 8u131 之前 不识别容器内存限制,需手动设置 -Xmx
Java 8u191+ 默认开启 -XX:+UseContainerSupport
建议配置 保留 20-30% 内存给非堆区域(元空间、栈、操作系统)

容器环境 GC 日志配置建议使用 logrotate 替代 UseGCLogFileRotation,避免轮转 bug。

7.3 关键告警阈值

指标 告警阈值(黄色) 严重告警(红色)
FGC 频率 > 3 次/小时 > 10 次/小时
Full GC 耗时 > 1s > 3s
GC 停顿占比 > 10% > 30%
老年代使用率 > 80% > 90%
元空间使用率 > 80% > 90%

7.4 健康检查脚本

bash

复制代码
#!/bin/bash
# 快速健康检查脚本

PID=$(jps -l | grep -v Jps | awk '{print $1}')

echo "=== JVM 参数 ==="
jcmd $PID VM.flags

echo ""
echo "=== GC 概况(5秒采样)==="
jstat -gcutil $PID 5000 3

echo ""
echo "=== 堆内存摘要 ==="
jmap -heap $PID | head -30

echo ""
echo "=== 线程状态统计 ==="
jstack $PID | grep "Thread.State" | sort | uniq -c

echo ""
echo "=== 最大对象 TOP 20 ==="
jmap -histo:live $PID | head -20

八、参数速查表

8.1 内存参数

参数 含义 默认值
-Xms 初始堆大小 物理内存 1/64
-Xmx 最大堆大小 物理内存 1/4
-Xmn 年轻代大小 堆的 1/3
-Xss 线程栈大小 平台相关
-XX:NewRatio 老年代:年轻代 2
-XX:SurvivorRatio Eden:Survivor 8
-XX:MaxTenuringThreshold 晋升年龄阈值 15
-XX:MetaspaceSize 元空间 GC 阈值 ~20.8MB
-XX:MaxMetaspaceSize 最大元空间 不限制(物理内存上限)

8.2 GC 参数

参数 含义
-XX:+UseSerialGC 串行回收器
-XX:+UseParallelGC 并行回收器(Java 8 默认,自动启用 Parallel Old)
-XX:+UseConcMarkSweepGC CMS 回收器
-XX:+UseG1GC G1 回收器
-XX:CMSInitiatingOccupancyFraction CMS 触发阈值(默认 -1,即 JVM 自动计算)
-XX:MaxGCPauseMillis G1 目标停顿时间
-XX:+DisableExplicitGC 禁用 System.gc()

8.3 诊断参数

参数 含义
-XX:+PrintGCDetails 详细 GC 日志
-XX:+PrintGCDateStamps 日期时间戳
-XX:+HeapDumpOnOutOfMemoryError OOM 导出堆转储
-XX:+PrintGCApplicationStoppedTime 打印 STW 时间

附录:完整配置模板

A. 生产环境推荐配置(G1GC)

bash

复制代码
# ============================================================
# 通用 Web 应用推荐配置(堆 6-8GB,Java 8u40+)
# ============================================================

# 基础配置
JAVA_OPTS="-Xms8g -Xmx8g -Xss256k"

# 元空间
JAVA_OPTS="$JAVA_OPTS -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1g"

# GC 算法
JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC"
JAVA_OPTS="$JAVA_OPTS -XX:MaxGCPauseMillis=200"

# 禁用显式 GC
JAVA_OPTS="$JAVA_OPTS -XX:+DisableExplicitGC"

# GC 日志
JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
JAVA_OPTS="$JAVA_OPTS -Xloggc:/data/logs/gc.log"
JAVA_OPTS="$JAVA_OPTS -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M"

# OOM 堆转储
JAVA_OPTS="$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError"
JAVA_OPTS="$JAVA_OPTS -XX:HeapDumpPath=/data/logs/heap.hprof"

# 编码
JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8"

B. 生产环境推荐配置(CMS)

bash

复制代码
# ============================================================
# 低延迟 Web 应用推荐配置(堆 4-8GB,Java 8)
# ============================================================

# 基础配置
JAVA_OPTS="-Xms6g -Xmx6g -Xss256k"

# 元空间
JAVA_OPTS="$JAVA_OPTS -XX:PermSize=512m -XX:MaxPermSize=1g"

# GC 算法
JAVA_OPTS="$JAVA_OPTS -XX:+UseConcMarkSweepGC"
JAVA_OPTS="$JAVA_OPTS -XX:+UseParNewGC"
JAVA_OPTS="$JAVA_OPTS -XX:CMSInitiatingOccupancyFraction=70"
JAVA_OPTS="$JAVA_OPTS -XX:+UseCMSInitiatingOccupancyOnly"
JAVA_OPTS="$JAVA_OPTS -XX:+CMSClassUnloadingEnabled"
JAVA_OPTS="$JAVA_OPTS -XX:+UseCMSCompactAtFullCollection"
JAVA_OPTS="$JAVA_OPTS -XX:CMSFullGCsBeforeCompaction=0"

# 禁用显式 GC
JAVA_OPTS="$JAVA_OPTS -XX:+DisableExplicitGC"

# GC 日志
JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
JAVA_OPTS="$JAVA_OPTS -Xloggc:/data/logs/gc.log"

# OOM 堆转储
JAVA_OPTS="$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError"
JAVA_OPTS="$JAVA_OPTS -XX:HeapDumpPath=/data/logs/heap.hprof"

# 编码
JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8"

C. 常用排查命令组合

bash

复制代码
# 1. 找到目标 PID
jps -l

# 2. GC 实时监控(核心命令)
jstat -gcutil <pid> 5000 10

# 3. GC 容量详情
jstat -gc <pid> 5000 5

# 4. 堆内存摘要
jmap -heap <pid>

# 5. 对象分布(定位内存泄漏)
jmap -histo:live <pid> | head -20

# 6. 线程堆栈(定位死锁/卡顿)
jstack -l <pid>

# 7. 确认 JVM 参数
jcmd <pid> VM.flags

# 8. 导出堆转储(离线分析)
jmap -dump:live,format=b,file=heap.hprof <pid>

D. 调优验证清单

完成调优配置后,建议逐项确认:

验证项 预期结果
参数是否生效 jcmd VM.flags 确认
GC 日志是否正常写入 ls -la /data/logs/gc.log
FGC 是否停止增长 jstat -gcutil 监控 24 小时,FGC 基本不涨
Survivor 是否正常交替 S0/S1 交替为 0,不同时为 0
老年代是否平稳波动 O 列在 50-70% 间平稳波动
业务功能是否正常 核心业务流程验证通过

附录二:调优效果评估方法

完成调优配置后,需要量化评估效果:

评估维度 对比方法 核心指标
FGC 频率 对比调优前后的 jstat 数据 FGC 次数增长率
GC 停顿时间 对比 GC 日志中的 STW 时间 平均/最大停顿时间
GC 吞吐量 计算 GC 时间占总时间比例 GCT / (运行时长)
业务响应时间 对比调优前后的业务监控数据 P99 响应时间
业务错误率 对比调优前后的业务错误日志 错误率变化

附录三:常见陷阱与误区

误区 正确理解
堆越大越好 堆过大导致 Full GC 扫描时间长;G1 虽分区回收,但分区数量增多也有开销
CMS 最优秀 CMS 在 Java 9 中已废弃;G1 在 Java 8u40 后更成熟
调优就是一劳永逸 业务流量变化后需要重新评估调优效果,JVM 调优是持续迭代的过程
只看 FGC 次数 FGC 次数少但单次时间长也影响业务,需综合关注 FGCT 和最大停顿时间
所有参数都要调 除 -Xms/-Xmx、GC 算法、元空间上限外,其他参数建议先观察再决定是否调整

扩展阅读:

  • 实战案例:一次业务高峰期行锁堆积故障的 JVM 调优分析
  • Oracle 官方文档:Java Platform, Standard Edition Troubleshooting Guide(
相关推荐
极光代码工作室2 小时前
基于SpringBoot的订单管理系统
java·springboot·web开发·后端开发
xiaoduzi19912 小时前
ConcurrentHashMap学习
java·学习
2zcode2 小时前
基于MATLAB卷积神经网络的口罩佩戴检测系统
开发语言·matlab·cnn
折哥的程序人生 · 物流技术专研2 小时前
《Java 100 天进阶之路》第53篇:volatile与JMM(2026版)
java·volatile·内存屏障·可见性·java内存模型·jmm·java100天进阶
米饭不加菜2 小时前
使用万用表判断三极管(BJT)好坏
java·开发语言
绝世唐门三哥2 小时前
vue3中页面返回时刷新首页的处理方案
开发语言·前端·javascript
nianniannnn2 小时前
c++复习自存--流、异常处理、多线程与C++工程规范
开发语言·c++
geovindu2 小时前
CSharp: Dijkstra Algorithms
开发语言·后端·算法·c#
Maynor9962 小时前
AI Coding 零基础实战教程|第五部分:完整项目案例实操
java·前端·人工智能·claude code·ai coding
XR1234567882 小时前
食品饮料与制药行业GMP合规网络建设:无尘车间的网络不能成为污染源
开发语言·网络·php