本文系统讲解JVM调优的完整知识体系,涵盖垃圾回收算法选择、分代回收策略、JVM参数调优、CPU 100%排查、OOM分析、MAT堆转储分析等实战内容,助你从入门到精通JVM调优。
文章目录
- 一、分代回收的核心原理
-
- [1.1 为什么要分代?](#1.1 为什么要分代?)
- [1.2 分代策略](#1.2 分代策略)
- [1.3 算法选择逻辑](#1.3 算法选择逻辑)
- [1.4 为什么老年代不用复制算法?](#1.4 为什么老年代不用复制算法?)
- [1.5 新生代标记-复制算法详解](#1.5 新生代标记-复制算法详解)
- [1.6 老年代标记-整理算法详解](#1.6 老年代标记-整理算法详解)
- 二、垃圾回收算法与回收器选择
-
- [2.1 垃圾回收算法对比](#2.1 垃圾回收算法对比)
- [2.2 垃圾回收器全览](#2.2 垃圾回收器全览)
- [2.3 垃圾回收器选择决策树](#2.3 垃圾回收器选择决策树)
- [2.4 不同场景推荐配置](#2.4 不同场景推荐配置)
- [2.5 分代回收器组合](#2.5 分代回收器组合)
-
- [组合1:Serial GC](#组合1:Serial GC)
- [组合2:Parallel GC](#组合2:Parallel GC)
- [组合3:G1 GC](#组合3:G1 GC)
- 组合4:ZGC
- 三、JVM参数调优实战
-
- [3.1 JVM参数分类](#3.1 JVM参数分类)
-
- [3.1.1 内存参数](#3.1.1 内存参数)
- [3.1.2 GC参数](#3.1.2 GC参数)
- [3.1.3 性能参数](#3.1.3 性能参数)
- [3.2 常用JVM参数配置模板](#3.2 常用JVM参数配置模板)
-
- [模板1:通用型Web服务(G1 GC)](#模板1:通用型Web服务(G1 GC))
- 模板2:低延迟交易系统(ZGC)
- [模板3:吞吐量优先批处理(Parallel GC)](#模板3:吞吐量优先批处理(Parallel GC))
- [模板4:小型应用(Serial GC)](#模板4:小型应用(Serial GC))
- [3.3 JVM参数调优建议](#3.3 JVM参数调优建议)
- [3.4 JVM参数调优流程](#3.4 JVM参数调优流程)
- [四、CPU 100%问题排查](#四、CPU 100%问题排查)
-
- [4.1 CPU 100%常见原因](#4.1 CPU 100%常见原因)
- [4.2 排查步骤与命令](#4.2 排查步骤与命令)
-
- [步骤1:确认CPU 100%进程](#步骤1:确认CPU 100%进程)
- 步骤2:查看CPU占用最高的线程
- 步骤3:线程ID转换为16进制
- 步骤4:查看线程堆栈
- [4.3 排查命令汇总](#4.3 排查命令汇总)
- [4.4 CPU 100%排查案例](#4.4 CPU 100%排查案例)
-
- [案例1:死循环导致CPU 100%](#案例1:死循环导致CPU 100%)
- [案例2:频繁GC导致CPU 100%](#案例2:频繁GC导致CPU 100%)
- [案例3:正则表达式回溯导致CPU 100%](#案例3:正则表达式回溯导致CPU 100%)
- 五、OOM问题全面分析
-
- [5.1 OOM类型与特征](#5.1 OOM类型与特征)
- [5.2 堆内存溢出(Heap Space)](#5.2 堆内存溢出(Heap Space))
-
- [5.2.1 现象与特征](#5.2.1 现象与特征)
- [5.2.2 排查步骤](#5.2.2 排查步骤)
- [5.2.3 排查案例](#5.2.3 排查案例)
- [5.3 栈溢出(StackOverflow)](#5.3 栈溢出(StackOverflow))
-
- [5.3.1 现象与特征](#5.3.1 现象与特征)
- [5.3.2 排查步骤](#5.3.2 排查步骤)
- [5.3.3 调整栈大小](#5.3.3 调整栈大小)
- [5.4 方法区溢出(Metaspace)](#5.4 方法区溢出(Metaspace))
-
- [5.4.1 现象与特征](#5.4.1 现象与特征)
- [5.4.2 排查步骤](#5.4.2 排查步骤)
- [5.4.3 解决方案](#5.4.3 解决方案)
- [5.5 直接内存溢出(Direct Buffer Memory)](#5.5 直接内存溢出(Direct Buffer Memory))
-
- [5.5.1 现象与特征](#5.5.1 现象与特征)
- [5.5.2 解决方案](#5.5.2 解决方案)
- [5.6 创建线程溢出(Unable to create new native thread)](#5.6 创建线程溢出(Unable to create new native thread))
-
- [5.6.1 现象与特征](#5.6.1 现象与特征)
- [5.6.2 排查步骤](#5.6.2 排查步骤)
- [5.6.3 排查案例](#5.6.3 排查案例)
- 六、MAT堆转储分析实战
-
- [6.1 MAT简介](#6.1 MAT简介)
- [6.2 安装与配置](#6.2 安装与配置)
-
- [6.2.1 下载与安装](#6.2.1 下载与安装)
- [6.2.2 配置内存](#6.2.2 配置内存)
- [6.3 生成堆转储文件](#6.3 生成堆转储文件)
-
- [6.3.1 JVM参数自动生成](#6.3.1 JVM参数自动生成)
- [6.3.2 手动生成堆转储](#6.3.2 手动生成堆转储)
- [6.4 MAT使用流程](#6.4 MAT使用流程)
-
- [6.4.1 打开堆转储文件](#6.4.1 打开堆转储文件)
- [6.4.2 Leak Suspects Report(泄漏嫌疑报告)](#6.4.2 Leak Suspects Report(泄漏嫌疑报告))
- [6.4.3 Dominator Tree(支配树)](#6.4.3 Dominator Tree(支配树))
- [6.5 MAT定位内存泄漏实战](#6.5 MAT定位内存泄漏实战)
- [6.6 MAT常用快捷键](#6.6 MAT常用快捷键)
- 七、JVM监控工具使用
-
- [7.1 jps(Java Virtual Machine Process Status Tool)](#7.1 jps(Java Virtual Machine Process Status Tool))
- [7.2 jstat(JVM Statistics Monitoring Tool)](#7.2 jstat(JVM Statistics Monitoring Tool))
-
- [7.2.1 查看GC情况](#7.2.1 查看GC情况)
- [7.2.2 查看GC使用率](#7.2.2 查看GC使用率)
- [7.3 jstack(Java Stack Trace)](#7.3 jstack(Java Stack Trace))
- [7.4 jmap(Java Memory Map)](#7.4 jmap(Java Memory Map))
-
- [7.4.1 查看堆信息](#7.4.1 查看堆信息)
- [7.4.2 生成堆转储](#7.4.2 生成堆转储)
- [7.5 jcmd(Java Command)](#7.5 jcmd(Java Command))
-
- [7.5.1 查看GC情况](#7.5.1 查看GC情况)
- [7.5.2 生成堆转储](#7.5.2 生成堆转储)
- [7.6 JConsole](#7.6 JConsole)
- [7.7 VisualVM](#7.7 VisualVM)
- 八、实战调优案例
-
- [8.1 案例1:电商服务FullGC频繁](#8.1 案例1:电商服务FullGC频繁)
-
- [8.1.1 问题现象](#8.1.1 问题现象)
- [8.1.2 排查步骤](#8.1.2 排查步骤)
-
- [8.1.3 解决方案](#8.1.3 解决方案)
- [8.2 案例2:交易系统CPU 100%](#8.2 案例2:交易系统CPU 100%)
-
- [8.2.1 问题现象](#8.2.1 问题现象)
- [8.2.2 排查步骤](#8.2.2 排查步骤)
- [8.2.3 解决方案](#8.2.3 解决方案)
- [8.3 案例3:批处理任务OOM](#8.3 案例3:批处理任务OOM)
-
- [8.3.1 问题现象](#8.3.1 问题现象)
- [8.3.2 排查步骤](#8.3.2 排查步骤)
- [8.3.3 解决方案](#8.3.3 解决方案)
- 九、总结与最佳实践
-
- [9.1 JVM调优核心原则](#9.1 JVM调优核心原则)
-
- 原则1:先监控后调优
- [原则2:堆大小 = 物理内存的60-80%](#原则2:堆大小 = 物理内存的60-80%)
- [原则3:初始堆 = 最大堆](#原则3:初始堆 = 最大堆)
- 原则4:选择合适的GC算法
- 原则5:开启GC日志
- [9.2 JVM调优检查清单](#9.2 JVM调优检查清单)
- [9.3 常用命令速查](#9.3 常用命令速查)
- [9.4 JVM参数配置模板](#9.4 JVM参数配置模板)
-
- [模板1:通用型Web服务(G1 GC)](#模板1:通用型Web服务(G1 GC))
- 模板2:低延迟交易系统(ZGC)
- [模板3:吞吐量优先批处理(Parallel GC)](#模板3:吞吐量优先批处理(Parallel GC))
- [9.5 核心记忆口诀](#9.5 核心记忆口诀)
- 结语
一、分代回收的核心原理
1.1 为什么要分代?
核心观察:对象生命周期呈现两极分化
新生对象 → 判断存活时间 → 短期存活(<1分钟) → 新生代
→ 长期存活(>1分钟) → 老年代
数据支撑:
- 新生代对象:95%以上朝生夕死
- 老年代对象:80%以上长期存活
1.2 分代策略
| 区域 | 对象特征 | 存活率 | 适合算法 | 核心思想 |
|---|---|---|---|---|
| 新生代 | 方法局部变量、临时对象 | <5% | 标记-复制 | 只复制少量存活对象 |
| 老年代 | 缓存数据、单例对象 | >80% | 标记-整理 | 原地整理,消除碎片 |
1.3 算法选择逻辑
新生代:95%对象死亡 → 复制算法(只复制5%存活对象,成本低)
老年代:80%对象存活 → 整理算法(原地整理,避免复制开销)
1.4 为什么老年代不用复制算法?
简单计算:
- 新生代复制:复制5%存活对象 → 成本低
- 老年代复制:复制80%存活对象 → 成本极高
结论:复制算法浪费50%空间,老年代空间大,不划算。
1.5 新生代标记-复制算法详解
工作原理:
┌─────────────────┐
│ Eden区 │ 新对象分配
├─────────────────┤
│ Survivor S0 │ 存活对象复制
├─────────────────┤
│ Survivor S1 │ 存活对象复制
└─────────────────┘
GC流程:
1. 扫描Eden+S0,标记存活对象
2. 复制存活对象到S1
3. 清空Eden+S0
4. S0和S1角色互换
优势:
- ✅ 无内存碎片
- ✅ 回收效率高(只复制少量存活对象)
劣势:
- ❌ 内存利用率低(仅50%)
1.6 老年代标记-整理算法详解
工作原理:
┌─────────────────────────────────┐
│ ████ ████ ████████ │
│ 未标记 未标记 存活对象 │
└─────────────────────────────────┘
↓ 标记+整理
┌─────────────────────────────────┐
│ ████████████████████ │
│ 存活对象(紧密排列) │
└─────────────────────────────────┘
优势:
- ✅ 无内存碎片
- ✅ 大对象分配安全
劣势:
- ❌ 需要移动对象,性能开销大
二、垃圾回收算法与回收器选择
2.1 垃圾回收算法对比
| 算法类型 | 工作原理 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|
| 标记-清除 | 标记存活对象,清除未标记 | 不移动对象,实现简单 | 产生内存碎片 | 老年代(CMS) |
| 标记-整理 | 标记存活对象,移动到一端 | 无内存碎片 | 需要移动对象,开销大 | 老年代(Parallel Old、ZGC) |
| 标记-复制 | 复制存活对象到另一区域 | 无碎片,效率高 | 内存利用率低(50%) | 新生代(所有新生代回收器) |
2.2 垃圾回收器全览
| 回收器 | 新生代算法 | 老年代算法 | 线程模型 | 停顿时间 | 适用堆大小 | 推荐场景 |
|---|---|---|---|---|---|---|
| Serial GC | 复制 | 整理 | 单线程 | 长(100ms+) | <100MB | 客户端应用 |
| Parallel GC | 复制 | 整理 | 多线程并行 | 中(50-200ms) | 2-16GB | 批处理、吞吐量优先 |
| CMS GC | 复制 | 清除 | 并发+并行 | 短(<200ms) | 2-10GB | ❌已废弃 |
| G1 GC | 复制 | 整理+复制 | 并发+并行 | 短(<200ms) | 4-64GB | Web服务、通用型 |
| ZGC | 整理 | 整理 | 全并发 | 极短(<10ms) | >32GB | 超大堆、极致低延迟 |
| Shenandoah | 整理 | 整理 | 并发 | 极短(<10ms) | >32GB | 大堆、均衡型 |
2.3 垃圾回收器选择决策树
开始选择垃圾回收器
│
├─ 第一步:确定堆大小
│ ├─ < 100MB ─────────────────────────→ Serial GC
│ ├─ 100MB - 4GB ────────────────────→ Parallel GC
│ ├─ 4GB - 32GB ─────────────────────→ G1 GC
│ └─ > 32GB ────────────────────────→ ZGC
│
├─ 第二步:确定延迟要求
│ ├─ < 10ms ─────────────────────────→ ZGC
│ ├─ 10ms - 200ms ──────────────────→ G1 GC
│ └─ 不敏感 ─────────────────────────→ Parallel GC
│
├─ 第三步:确定业务类型
│ ├─ Web服务/API网关 ────────────────→ G1 GC
│ ├─ 批处理/后台任务 ────────────────→ Parallel GC
│ ├─ 金融交易/实时竞价 ──────────────→ ZGC
│ └─ 桌面应用/小型工具 ─────────────→ Serial GC
│
└─ 第四步:确定JDK版本
├─ JDK 8 ─────────────────────────→ G1 GC
├─ JDK 11 ────────────────────────→ G1 GC / ZGC
└─ JDK 17+ ──────────────────────→ ZGC(生产推荐)
2.4 不同场景推荐配置
场景1:电商Web服务
bash
# 应用类型:高并发Web服务
# 堆大小:8GB
# JDK版本:JDK 17
# 延迟要求:<200ms
# 推荐回收器:G1 GC
-Xms8g -Xmx8g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:InitiatingHeapOccupancyPercent=45 \
-XX:G1HeapRegionSize=16m
场景2:金融交易系统
bash
# 应用类型:低延迟交易系统
# 堆大小:64GB
# JDK版本:JDK 21
# 延迟要求:<10ms
# 推荐回收器:ZGC
-Xms64g -Xmx64g \
-XX:+UseZGC \
-XX:ZGCParallelGCThreads=8 \
-XX:MaxGCPauseMillis=10
场景3:批处理ETL任务
bash
# 应用类型:吞吐量优先批处理
# 堆大小:16GB
# JDK版本:JDK 11
# 延迟要求:不敏感
# 推荐回收器:Parallel GC
-Xms16g -Xmx16g \
-XX:+UseParallelGC \
-XX:ParallelGCThreads=12 \
-XX:GCTimeRatio=95
场景4:桌面应用
bash
# 应用类型:桌面应用
# 堆大小:512MB
# JDK版本:任意
# 延迟要求:不敏感
# 推荐回收器:Serial GC
-Xms512m -Xmx512m \
-XX:+UseSerialGC
2.5 分代回收器组合
组合1:Serial GC
bash
# 配置
-XX:+UseSerialGC
# 算法说明
新生代:Serial(单线程复制)
老年代:Serial Old(单线程整理)
# 适用场景
- 客户端应用
- 小型工具
- 单核CPU
- 堆大小 < 100MB
组合2:Parallel GC
bash
# 配置
-XX:+UseParallelGC
# 算法说明
新生代:Parallel Scavenge(多线程复制)
老年代:Parallel Old(多线程整理)
# 适用场景
- 批处理任务
- 后台计算
- 吞吐量优先
- 堆大小 2-16GB
组合3:G1 GC
bash
# 配置
-XX:+UseG1GC
# 算法说明
新生代:G1 YoungGC(Region化复制)
老年代:G1 MixedGC(Region化整理+复制)
# 适用场景
- Web服务
- API网关
- 企业级应用
- 堆大小 4-64GB
- JDK 9+默认
组合4:ZGC
bash
# 配置
-XX:+UseZGC
# 算法说明
新生代:ZGC(并发标记-整理)
老年代:ZGC(并发标记-整理)
注:ZGC是单代回收器,JDK 21+支持分代
# 适用场景
- 金融交易
- 实时竞价
- 高频请求
- 堆大小 > 32GB
- JDK 17+生产推荐
三、JVM参数调优实战
3.1 JVM参数分类
3.1.1 内存参数
| 参数 | 说明 | 默认值 | 推荐值 |
|---|---|---|---|
-Xms |
堆内存初始大小 | 物理内存的1/64 | 与-Xmx相同 |
-Xmx |
堆内存最大大小 | 物理内存的1/4 | 物理内存的60-80% |
-Xmn |
新生代大小 | 堆的1/3 | 根据对象存活率调整 |
-XX:NewRatio |
新生代/老年代比例 | 2 | 2-4 |
-XX:SurvivorRatio |
Eden/S0/S1比例 | 8 | 8 |
-XX:MetaspaceSize |
元空间初始大小 | 21MB | 256MB |
-XX:MaxMetaspaceSize |
元空间最大大小 | 无限制 | 512MB |
-Xss |
线程栈大小 | 1MB | 256KB-1MB |
3.1.2 GC参数
| 参数 | 说明 | 默认值 | 推荐值 |
|---|---|---|---|
-XX:+UseG1GC |
使用G1回收器 | - | 推荐 |
-XX:+UseZGC |
使用ZGC回收器 | - | JDK 17+推荐 |
-XX:MaxGCPauseMillis |
目标最大停顿时间 | 200ms | 100-500ms |
-XX:InitiatingHeapOccupancyPercent |
触发并发标记阈值 | 45 | 40-50 |
-XX:GCTimeRatio |
GC时间占比 | 99 | 99 |
3.1.3 性能参数
| 参数 | 说明 | 默认值 | 推荐值 |
|---|---|---|---|
-XX:+UseStringDeduplication |
字符串去重 | false | true(大堆) |
-XX:+UseCompressedOops |
压缩指针 | <32GB自动开启 | true |
-XX:+UseCompressedClassPointers |
压缩类指针 | <32GB自动开启 | true |
-XX:+AlwaysPreTouch |
预分配内存 | false | true(大堆) |
3.2 常用JVM参数配置模板
模板1:通用型Web服务(G1 GC)
bash
-Xms4g -Xmx4g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:InitiatingHeapOccupancyPercent=45 \
-XX:G1HeapRegionSize=8m \
-XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=512m \
-XX:+UseStringDeduplication \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/logs/heapdump.hprof \
-Xlog:gc*:file=/logs/gc.log:time,tags:filecount=10,filesize=100m
模板2:低延迟交易系统(ZGC)
bash
-Xms32g -Xmx32g \
-XX:+UseZGC \
-XX:ZGCParallelGCThreads=8 \
-XX:ZGCConcurrentGCThreads=2 \
-XX:MaxGCPauseMillis=10 \
-XX:+AlwaysPreTouch \
-XX:MetaspaceSize=512m \
-XX:MaxMetaspaceSize=1g \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/logs/heapdump.hprof \
-Xlog:gc*:file=/logs/gc.log:time,tags:filecount=10,filesize=100m
模板3:吞吐量优先批处理(Parallel GC)
bash
-Xms16g -Xmx16g \
-XX:+UseParallelGC \
-XX:ParallelGCThreads=12 \
-XX:GCTimeRatio=95 \
-XX:NewRatio=2 \
-XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=512m \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/logs/heapdump.hprof \
-Xlog:gc*:file=/logs/gc.log:time,tags:filecount=10,filesize=100m
模板4:小型应用(Serial GC)
bash
-Xms512m -Xmx512m \
-XX:+UseSerialGC \
-XX:NewRatio=2 \
-XX:MetaspaceSize=128m \
-XX:MaxMetaspaceSize=256m \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/logs/heapdump.hprof
3.3 JVM参数调优建议
建议1:堆大小设置
bash
# ❌ 错误做法
-Xms2g -Xmx8g # 初始和最大不一致,可能导致频繁扩容
# ✅ 正确做法
-Xms8g -Xmx8g # 初始和最大一致,避免动态调整
原因:
- 堆内存动态调整会产生性能抖动
- 初始堆过小会导致频繁GC
- 生产环境建议设置相同
建议2:新生代大小设置
bash
# ❌ 错误做法
-Xmn256m # 新生代过小,频繁YoungGC
# ✅ 正确做法
-XX:NewRatio=2 # 新生代占堆的1/3
调优原则:
- 新生代太小:YoungGC频繁
- 新生代太大:老年代空间不足,触发FullGC
- 推荐比例:新生代占堆的1/3-1/4
建议3:元空间设置
bash
# ❌ 错误做法
-XX:MaxMetaspaceSize=unlimited # 不限制可能导致OOM
# ✅ 正确做法
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m
原因:
- 元空间使用本地内存
- 类加载过多可能导致元空间溢出
- 必须设置最大值限制
建议4:GC日志配置
bash
# JDK 8
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/logs/gc.log
# JDK 11+
-Xlog:gc*:file=/logs/gc.log:time,tags:filecount=10,filesize=100m
建议:
- 开启GC日志便于问题排查
- 日志文件设置滚动策略
- 保留最近10个日志文件
3.4 JVM参数调优流程
开始调优
│
├─ 第一步:确定应用类型
│ ├─ Web服务 ──────────────→ G1 GC
│ ├─ 批处理 ──────────────→ Parallel GC
│ └─ 低延迟交易 ──────────→ ZGC
│
├─ 第二步:确定堆大小
│ └─ 物理内存的60-80%
│
├─ 第三步:设置GC参数
│ ├─ G1 GC: MaxGCPauseMillis=200
│ ├─ ZGC: MaxGCPauseMillis=10
│ └─ Parallel GC: GCTimeRatio=99
│
├─ 第四步:设置元空间
│ └─ MetaspaceSize=256m, MaxMetaspaceSize=512m
│
├─ 第五步:开启GC日志
│ └─ -Xlog:gc*:file=/logs/gc.log
│
└─ 第六步:监控验证
└─ jstat、jvisualvm、jconsole
四、CPU 100%问题排查
4.1 CPU 100%常见原因
| 原因类型 | 典型场景 | 特征 |
|---|---|---|
| 死循环 | 无限循环、逻辑错误 | 单线程100% |
| 频繁GC | 内存泄漏、堆太小 | 多线程波动 |
| 线程阻塞 | 锁竞争、死锁 | 线程处于BLOCKED状态 |
| CPU密集计算 | 大数据处理、加密算法 | 多个线程高CPU |
| 反射调用 | 大量反射使用 | 高CPU开销 |
| 正则表达式 | 复杂正则匹配 | CPU飙升 |
4.2 排查步骤与命令
步骤1:确认CPU 100%进程
bash
# 查看CPU占用最高的进程
top -c
# 或使用htop(交互式)
htop
# 输出示例
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
12345 appuser 20 0 8.0g 4.0g 512m R 100.0 50.0 2:30.15 java -Xms4g -Xmx4g -jar app.jar
关键信息:
- PID:进程ID(如12345)
- %CPU:CPU占用率
- COMMAND:启动命令
步骤2:查看CPU占用最高的线程
bash
# 使用top -H
top -H -p 12345
# 输出示例
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
12345 appuser 20 0 8.0g 4.0g 512m R 100.0 50.0 2:30.15 java
12456 appuser 20 0 8.0g 4.0g 512m R 99.5 50.0 2:15.20 java
关键信息:
- 线程PID:12456(需要转换为16进制)
步骤3:线程ID转换为16进制
bash
# 将线程PID 12456 转换为16进制
printf "%x\n" 12456
# 输出
30a8
结果 :线程16进制ID为 30a8
步骤4:查看线程堆栈
bash
# 使用jstack
jstack 12345 | grep -A 20 "30a8"
# 方法2:保存完整堆栈
jstack 12345 > /tmp/jstack_12345.log
# 查看线程堆栈
grep -A 20 "30a8" /tmp/jstack_12345.log
# 输出示例
"Thread-0" #30a8 prio=5 os_prio=0 tid=0x00007f1234567890 nid=0x30a8 runnable [0x00007f1234000000]
java.lang.Thread.State: RUNNABLE
at com.example.Service.heavyCompute(Service.java:123)
at com.example.Service.run(Service.java:45)
关键信息:
- 线程状态:RUNNABLE
- 代码位置:com.example.Service.heavyCompute:123
4.3 排查命令汇总
| 命令 | 用途 | 示例 |
|---|---|---|
top -c |
查看进程CPU占用 | top -c |
top -H -p <pid> |
查看线程CPU占用 | top -H -p 12345 |
jstack <pid> |
查看线程堆栈 | jstack 12345 > /tmp/jstack.log |
jstat -gc <pid> |
查看GC情况 | jstat -gc 12345 1000 10 |
jstat -gcutil <pid> |
查看GC使用率 | jstat -gcutil 12345 1000 10 |
printf "%x\n" <tid> |
线程ID转16进制 | printf "%x\n" 12456 |
4.4 CPU 100%排查案例
案例1:死循环导致CPU 100%
现象:
bash
# 单线程CPU 100%
top -c
PID %CPU COMMAND
12345 100.0 java -jar app.jar
# 查看线程
top -H -p 12345
PID %CPU COMMAND
12456 99.5 java -jar app.jar
排查:
bash
# 转换线程ID
printf "%x\n" 12456
30a8
# 查看堆栈
jstack 12345 | grep -A 20 "30a8"
"Thread-0" #30a8 runnable
at com.example.Service.heavyCompute(Service.java:123)
定位代码:
java
public void heavyCompute() {
int i = 0;
while (i < 0) { // ❌ 条件永远为false
i++;
}
}
解决方案:修复循环条件
案例2:频繁GC导致CPU 100%
现象:
bash
# 多个线程CPU高,波动大
top -c
PID %CPU COMMAND
12345 80.5 java -jar app.jar
12456 75.3 java -jar app.jar
12457 70.1 java -jar app.jar
排查:
bash
# 查看GC情况
jstat -gcutil 12345 1000 10
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 98.00 80.00 95.00 90.00 50 2.500 5 3.200 5.700
特征:YGC频繁(每秒1次),老年代使用率高
解决方案:
bash
# 增大堆内存
-Xms8g -Xmx8g # 原4g
# 调整新生代比例
-XX:NewRatio=2 # 新生代占堆的1/3
案例3:正则表达式回溯导致CPU 100%
现象:
bash
# 处理日志文件时CPU飙升
top -c
PID %CPU COMMAND
12345 100.0 java -jar log-parser.jar
排查:
bash
# 查看堆栈
jstack 12345 | grep -A 20 "runnable"
"Thread-0" #30a8 runnable
at java.util.regex.Pattern$GroupCurly.match(Pattern.java:4633)
at java.util.regex.Pattern$Loop.match(Pattern.java:4747)
定位代码:
java
// ❌ 复杂正则表达式
Pattern pattern = Pattern.compile("(a|b)*c");
解决方案:
java
// ✅ 使用非贪婪匹配
Pattern pattern = Pattern.compile("(?:a|b)*?c");
五、OOM问题全面分析
5.1 OOM类型与特征
| OOM类型 | 报错信息 | 触发原因 | 典型场景 |
|---|---|---|---|
| 堆内存溢出 | java.lang.OutOfMemoryError: Java heap space |
堆内存不足 | 内存泄漏、堆太小 |
| 方法区溢出 | java.lang.OutOfMemoryError: Metaspace |
元空间不足 | 类加载过多 |
| 栈溢出 | java.lang.StackOverflowError |
栈深度过深 | 递归太深 |
| 直接内存溢出 | java.lang.OutOfMemoryError: Direct buffer memory |
直接内存不足 | NIO堆外内存 |
| 创建线程溢出 | java.lang.OutOfMemoryError: unable to create new native thread |
线程过多 | 线程泄漏 |
5.2 堆内存溢出(Heap Space)
5.2.1 现象与特征
报错信息:
java.lang.OutOfMemoryError: Java heap space
典型场景:
- 内存泄漏(对象无法回收)
- 堆内存设置过小
- 大对象分配失败
5.2.2 排查步骤
步骤1:确认OOM类型
bash
# 查看应用日志
tail -100 /logs/application.log
# 输出示例
java.lang.OutOfMemoryError: Java heap space
at java.util.HashMap.resize(HashMap.java:703)
at java.util.HashMap.putVal(HashMap.java:630)
at com.example.Service.process(Service.java:123)
步骤2:生成堆转储文件
方法1:自动生成
bash
# JVM参数配置
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/logs/heapdump.hprof \
-XX:+PrintGCDetails \
-XX:+PrintGCDateStamps
方法2:手动生成
bash
# 使用jmap生成
jmap -dump:format=b,file=/logs/heapdump.hprof 12345
# 只dump存活对象
jmap -dump:format=b,live,file=/logs/heapdump_live.hprof 12345
5.2.3 排查案例
案例1:HashMap内存泄漏
现象:
java.lang.OutOfMemoryError: Java heap space
排查:
bash
# 1. 生成堆转储
jmap -dump:format=b,file=/logs/heapdump.hprof 12345
# 2. 用MAT分析
# 发现:HashMap占用50%堆内存
定位代码:
java
// ❌ 静态HashMap只增不减
public static Map<String, Object> cache = new HashMap<>();
public void process(String key, Object value) {
cache.put(key, value); // 只增不减,内存泄漏
}
解决方案:
java
// ✅ 使用WeakReference
public static Map<String, Object> cache = new ConcurrentHashMap<>();
// 或使用LRU Cache
public static Map<String, Object> cache = new LinkedHashMap<>(1000, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > 10000;
}
};
5.3 栈溢出(StackOverflow)
5.3.1 现象与特征
报错信息:
java.lang.StackOverflowError
at com.example.Service.recursive(Service.java:123)
at com.example.Service.recursive(Service.java:125)
at com.example.Service.recursive(Service.java:125)
...
典型场景:
- 递归太深
- 循环依赖调用
5.3.2 排查步骤
步骤1:查看堆栈
bash
# 查看应用日志
tail -100 /logs/application.log
# 输出示例
java.lang.StackOverflowError
at com.example.Service.recursive(Service.java:123)
at com.example.Service.recursive(Service.java:125)
at com.example.Service.recursive(Service.java:125)
...
at com.example.Service.recursive(Service.java:125)
特征:同一方法重复调用
步骤2:定位代码
场景1:递归太深
java
// ❌ 无终止条件的递归
public void recursive() {
recursive(); // 无限递归
}
解决方案:
java
// ✅ 添加终止条件
public void recursive(int depth) {
if (depth > 1000) { // 终止条件
return;
}
recursive(depth + 1);
}
5.3.3 调整栈大小
bash
# 增大线程栈大小
-Xss2m # 默认1MB,增大到2MB
# 栈溢出频繁时增大
-Xss4m # 增大到4MB
注意:栈大小不是越大越好,过大会导致可创建线程数减少
5.4 方法区溢出(Metaspace)
5.4.1 现象与特征
报错信息:
java.lang.OutOfMemoryError: Metaspace
典型场景:
- 动态类加载过多
- 反射调用频繁
- 使用CGLIB等字节码增强
5.4.2 排查步骤
步骤1:确认OOM类型
bash
# 查看应用日志
tail -100 /logs/application.log
# 输出示例
java.lang.OutOfMemoryError: Metaspace
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
步骤2:查看元空间使用情况
bash
# 使用jstat查看
jstat -gc 12345 1000 5
# 输出示例
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
8704.0 8704.0 0.0 8704.0 69632.0 65536.0 139776.0 125952.0 51200.0 51200.0 5120.0 4864.0 5 0.125 0 0.000 0.125
关键字段:
- MC:元空间容量(Metaspace Capacity)
- MU:元空间使用量(Metaspace Used)
特征:MU接近MC,元空间即将溢出
5.4.3 解决方案
bash
# 增大元空间大小
-XX:MetaspaceSize=512m \
-XX:MaxMetaspaceSize=1g
5.5 直接内存溢出(Direct Buffer Memory)
5.5.1 现象与特征
报错信息:
java.lang.OutOfMemoryError: Direct buffer memory
典型场景:
- NIO堆外内存使用过多
- Netty堆外内存泄漏
- ByteBuffer.allocateDirect使用过多
5.5.2 解决方案
bash
# 增大直接内存限制
-XX:MaxDirectMemorySize=2g
# 或使用堆内存
-XX:-UseFastAccessorMethods
5.6 创建线程溢出(Unable to create new native thread)
5.6.1 现象与特征
报错信息:
java.lang.OutOfMemoryError: unable to create new native thread
典型场景:
- 线程泄漏
- 线程池配置不当
- 创建线程过多
5.6.2 排查步骤
步骤1:查看线程数
bash
# 查看线程数
ps -eLf | grep java | wc -l
# 查看Java线程数
jstack 12345 | grep "java.lang.Thread.State" | wc -l
5.6.3 排查案例
案例1:线程池线程泄漏
现象:
java.lang.OutOfMemoryError: unable to create new native thread
定位代码:
java
// ❌ 每次请求创建新线程
public void processRequest() {
new Thread(() -> {
// 处理逻辑
}).start();
}
解决方案:
java
// ✅ 使用线程池
private static final ExecutorService executor = Executors.newFixedThreadPool(100);
public void processRequest() {
executor.submit(() -> {
// 处理逻辑
});
}
六、MAT堆转储分析实战
6.1 MAT简介
Eclipse Memory Analyzer Tool (MAT) 是一个强大的Java堆内存分析工具,用于分析堆转储文件,定位内存泄漏和大对象。
下载地址:https://www.eclipse.org/mat/downloads/
6.2 安装与配置
6.2.1 下载与安装
bash
# 下载MAT
wget https://www.eclipse.org/downloads/download.php?file=/mat/1.14.0/rcp/MemoryAnalyzer-1.14.0.20230223-macosx.cocoa.x86_64.dmg
# Windows下载
# https://www.eclipse.org/downloads/download.php?file=/mat/1.14.0/rcp/MemoryAnalyzer-1.14.0.20230223-win32.win32.x86_64.zip
# 解压后直接运行
./MemoryAnalyzer
6.2.2 配置内存
bash
# 编辑MemoryAnalyzer.ini
vim MemoryAnalyzer.ini
# 修改堆内存
-vmargs
-Xmx8g # MAT自身堆内存,至少是dump文件大小的1.5倍
6.3 生成堆转储文件
6.3.1 JVM参数自动生成
bash
# JVM启动参数
-Xms4g -Xmx4g \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/logs/heapdump.hprof \
-XX:+PrintGCDetails \
-XX:+PrintGCDateStamps
说明:
- 当发生OOM时,自动生成堆转储文件
- 文件路径:
/logs/heapdump.hprof
6.3.2 手动生成堆转储
方法1:使用jmap
bash
# 生成堆转储
jmap -dump:format=b,file=/logs/heapdump.hprof 12345
# 只dump存活对象(推荐)
jmap -dump:format=b,live,file=/logs/heapdump_live.hprof 12345
方法2:使用jcmd
bash
# 生成堆转储
jcmd 12345 GC.heap_dump /logs/heapdump.hprof
6.4 MAT使用流程
6.4.1 打开堆转储文件
1. 启动MAT
2. File -> Open Heap Dump
3. 选择堆转储文件(heapdump.hprof)
4. 选择解析方式
- Leak Suspects Report(泄漏嫌疑报告,推荐)
- Component Report(组件报告)
5. 等待解析完成
6.4.2 Leak Suspects Report(泄漏嫌疑报告)
页面说明:
┌─────────────────────────────────────────┐
│ Leak Suspects Report │
├─────────────────────────────────────────┤
│ Problem Suspect 1 │
│ - Description: Class java.util.HashMap │
│ - Size: 1.2 GB (60% of heap) │
│ - Suspects: │
│ • com.example.Service.cache │
│ • com.example.Service.process │
├─────────────────────────────────────────┤
│ Problem Suspect 2 │
│ - Description: Class java.lang.String[]│
│ - Size: 500 MB (25% of heap) │
├─────────────────────────────────────────┤
│ Problem Suspect 3 │
│ - Description: Class byte[] │
│ - Size: 200 MB (10% of heap) │
└─────────────────────────────────────────┘
6.4.3 Dominator Tree(支配树)
页面说明:
显示堆中所有对象及其占用的内存,按大小排序。
Retained Heap(保留堆):如果该对象被回收,可以释放的内存总量。
示例输出:
┌──────────────────────────────────────────────┐
│ Class Name │ Shallow Heap │ Retained Heap │
├──────────────────────────────────────────────┤
│ HashMap │ 128 Bytes │ 1.2 GB │
│ String[] │ 24 Bytes │ 500 MB │
│ byte[] │ 16 Bytes │ 200 MB │
└──────────────────────────────────────────────┘
使用步骤:
1. 点击 Dominator Tree 标签
2. 按Retained Heap排序
3. 右键大对象 -> Path to GC Roots -> exclude all phantom/weak/soft etc. references
4. 查看引用链,定位泄漏点
6.5 MAT定位内存泄漏实战
案例1:HashMap内存泄漏
现象:
java.lang.OutOfMemoryError: Java heap space
步骤1:打开堆转储
File -> Open Heap Dump -> heapdump.hprof -> Leak Suspects Report
步骤2:查看泄漏嫌疑
Problem Suspect 1:
- Class: java.util.HashMap
- Size: 1.2 GB (60% of heap)
- Suspects:
• com.example.Service.cache
步骤3:查看支配树
Dominator Tree -> 按Retained Heap排序
HashMap (1.2 GB)
└─ com.example.Service.cache
└─ 1000000 entries
步骤4:查看引用链
右键HashMap -> Path to GC Roots -> exclude all phantom/weak/soft etc. references
结果:
com.example.Service.cache -> HashMap (1.2 GB)
步骤5:定位代码
java
// ❌ 静态HashMap只增不减
public static Map<String, Object> cache = new HashMap<>();
public void process(String key, Object value) {
cache.put(key, value); // 只增不减
}
解决方案:
java
// ✅ 使用LRU Cache
public static Map<String, Object> cache = new LinkedHashMap<>(1000, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > 10000;
}
};
案例2:ThreadLocal内存泄漏
现象:
java.lang.OutOfMemoryError: Java heap space
步骤1:打开堆转储
File -> Open Heap Dump -> heapdump.hprof -> Leak Suspects Report
步骤2:查看泄漏嫌疑
Problem Suspect 1:
- Class: java.lang.ThreadLocal
- Size: 800 MB (40% of heap)
- Suspects:
• com.example.Service.userCache
步骤3:查看支配树
Dominator Tree -> 按Retained Heap排序
ThreadLocal (800 MB)
└─ com.example.Service.userCache
└─ 10000 threads
步骤4:查看引用链
右键ThreadLocal -> Path to GC Roots -> exclude all phantom/weak/soft etc. references
结果:
Thread -> ThreadLocalMap -> ThreadLocal (800 MB)
步骤5:定位代码
java
// ❌ ThreadLocal未清理
private static final ThreadLocal<User> userCache = new ThreadLocal<>();
public void process(User user) {
userCache.set(user);
// 忘记清理
}
解决方案:
java
// ✅ 使用try-finally清理
private static final ThreadLocal<User> userCache = new ThreadLocal<>();
public void process(User user) {
try {
userCache.set(user);
// 处理逻辑
} finally {
userCache.remove(); // 清理
}
}
6.6 MAT常用快捷键
| 快捷键 | 功能 |
|---|---|
Ctrl + Shift + H |
打开Histogram |
Ctrl + Shift + D |
打开Dominator Tree |
Ctrl + Shift + L |
打开Leak Suspects Report |
Ctrl + Shift + O |
打开OQL查询 |
七、JVM监控工具使用
7.1 jps(Java Virtual Machine Process Status Tool)
功能:查看Java进程
bash
# 查看所有Java进程
jps
# 输出示例
12345 MainService
12346 WorkerService
# 查看详细参数
jps -l
# 输出示例
12345 com.example.MainService
12346 com.example.WorkerService
# 查看JVM参数
jps -v
# 输出示例
12345 MainService -Xms4g -Xmx4g -XX:+UseG1GC
7.2 jstat(JVM Statistics Monitoring Tool)
功能:查看JVM统计信息
7.2.1 查看GC情况
bash
# 查看GC统计(每秒刷新1次,共10次)
jstat -gc 12345 1000 10
# 输出示例
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
8704.0 8704.0 0.0 8704.0 69632.0 65536.0 139776.0 125952.0 51200.0 51200.0 5120.0 4864.0 5 0.125 0 0.000 0.125
字段说明:
- S0C/S1C:S0/S1区容量
- S0U/S1U:S0/S1区使用量
- EC:Eden区容量
- EU:Eden区使用量
- OC:老年代容量
- OU:老年代使用量
- MC:元空间容量
- MU:元空间使用量
- YGC:Young GC次数
- YGCT:Young GC总时间
- FGC:Full GC次数
- FGCT:Full GC总时间
- GCT:GC总时间
7.2.2 查看GC使用率
bash
# 查看GC使用率(每秒刷新1次,共10次)
jstat -gcutil 12345 1000 10
# 输出示例
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 50.00 90.00 75.00 95.00 90.00 50 2.500 5 3.200 5.700
字段说明:
- S0/S1/E/O/M/CCS:各区域使用率(百分比)
- YGC/FGC:GC次数
- YGCT/FGCT:GC总时间
- GCT:GC总时间
7.3 jstack(Java Stack Trace)
功能:查看线程堆栈
bash
# 查看线程堆栈
jstack 12345
# 输出示例
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.171-b11 mixed mode):
"Attach Listener" #13 daemon prio=9 os_prio=0 tid=0x00007f1234567890 nid=0x30a8 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Thread-0" #12 prio=5 os_prio=0 tid=0x00007f1234567890 nid=0x30a9 runnable [0x00007f1234000000]
java.lang.Thread.State: RUNNABLE
at com.example.Service.process(Service.java:123)
at com.example.Service.run(Service.java:45)
字段说明:
- 线程名称
- 线程状态(RUNNABLE/BLOCKED/WAITING)
- 代码位置
7.4 jmap(Java Memory Map)
功能:查看内存信息
7.4.1 查看堆信息
bash
# 查看堆信息
jmap -heap 12345
# 输出示例
Attaching to process ID 12345, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.171-b11
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 4194304000 (4000.0MB)
NewSize = 1398144000 (1333.125MB)
MaxNewSize = 1398144000 (1333.125MB)
OldSize = 2796288000 (2666.25MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21810376 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
Heap Usage:
PS Young Generation
Eden Space:
capacity = 1111490560 (1060.3125MB)
used = 987654320 (942.0MB)
free = 123836240 (118.3125MB)
88.84% used
From Space:
capacity = 139814400 (133.3125MB)
used = 0 (0.0MB)
free = 139814400 (133.3125MB)
0.0% used
To Space:
capacity = 139814400 (133.3125MB)
used = 0 (0.0MB)
free = 139814400 (133.3125MB)
0.0% used
PS Old Generation
capacity = 2796288000 (2666.25MB)
used = 1234567890 (1177.5MB)
free = 1561720110 (1488.75MB)
44.17% used
7.4.2 生成堆转储
bash
# 生成堆转储
jmap -dump:format=b,file=/logs/heapdump.hprof 12345
# 只dump存活对象
jmap -dump:format=b,live,file=/logs/heapdump_live.hprof 12345
7.5 jcmd(Java Command)
功能:多功能JVM诊断工具
7.5.1 查看GC情况
bash
# 查看GC情况
jcmd 12345 GC.heap_info
# 输出示例
12345:
garbage-first heap total 4096000K, used 2048000K [0x00000006c0000000, 0x00000006c0000000, 0x0000000800000000)
region size 16384K, 15 young (245760K), 3 survivors (49152K)
[0x00000006c0000000, 0x00000006c0000000, 0x00000006c0000000) young eden space
7.5.2 生成堆转储
bash
# 生成堆转储
jcmd 12345 GC.heap_dump /logs/heapdump.hprof
7.6 JConsole
功能:图形化JVM监控工具
启动方式:
bash
jconsole
使用步骤:
1. 启动jconsole
2. 连接到Java进程
3. 查看监控指标:
- 内存(Memory)
- 线程(Threads)
- 类(Classes)
- GC(GC)
- VM(VM Summary)
7.7 VisualVM
功能:全能JVM诊断工具
启动方式:
bash
jvisualvm
使用步骤:
1. 启动VisualVM
2. 连接到Java进程
3. 查看监控指标:
- 内存(Memory)
- 线程(Threads)
- GC(GC)
- 堆转储(Heap Dump)
- 线程转储(Thread Dump)
八、实战调优案例
8.1 案例1:电商服务FullGC频繁
8.1.1 问题现象
监控数据:
bash
# GC日志
[2025-02-24T16:00:00.123+0800] [0.123s] [gc,start] GC(0) Pause Young (G1 Evacuation Pause) 56M->45M(512M) 20.123ms
[2025-02-24T16:00:01.456+0800] [1.456s] [gc,start] GC(1) Pause Young (G1 Evacuation Pause) 56M->45M(512M) 18.234ms
...
[2025-02-24T16:05:00.789+0800] [300.789s] [gc,start] GC(100) Pause Full (Allocation Failure) 512M->512M(512M) 5000.123ms
特征:
- YoungGC频繁(每秒1次)
- FullGC频繁(每5分钟1次)
- FullGC停顿时间长(5秒)
8.1.2 排查步骤
步骤1:查看堆使用情况
bash
jstat -gcutil 12345 1000 10
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 50.00 98.00 95.00 95.00 90.00 100 2.500 10 50.000 52.500
分析:老年代使用率95%,接近溢出
步骤2:生成堆转储
bash
jmap -dump:format=b,live,file=/logs/heapdump.hprof 12345
步骤3:MAT分析
Leak Suspects Report:
Problem Suspect 1:
- Class: java.util.concurrent.ConcurrentHashMap
- Size: 300 MB (60% of old gen)
- Suspects:
• com.example.Service.orderCache
步骤4:定位代码
java
// ❌ 未限制大小的缓存
public static Map<String, Order> orderCache = new ConcurrentHashMap<>();
public void cacheOrder(String orderId, Order order) {
orderCache.put(orderId, order); // 无大小限制
}
8.1.3 解决方案
java
// ✅ 使用LRU Cache
public static Map<String, Order> orderCache = new LinkedHashMap<>(1000, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > 10000;
}
};
优化效果:
FullGC频率: 每5分钟1次 → 每24小时1次
FullGC停顿: 5秒 → 2秒
老年代使用率: 95% → 60%
8.2 案例2:交易系统CPU 100%
8.2.1 问题现象
监控数据:
bash
top -c
PID %CPU COMMAND
12345 100.0 java -jar trading-system.jar
top -H -p 12345
PID %CPU COMMAND
12456 99.5 java -jar trading-system.jar
特征:单线程CPU 100%
8.2.2 排查步骤
步骤1:转换线程ID
bash
printf "%x\n" 12456
30a8
步骤2:查看线程堆栈
bash
jstack 12345 | grep -A 20 "30a8"
"Thread-0" #30a8 runnable
at java.util.regex.Pattern$GroupCurly.match(Pattern.java:4633)
at java.util.regex.Pattern$Loop.match(Pattern.java:4747)
步骤3:定位代码
java
// ❌ 复杂正则表达式
Pattern pattern = Pattern.compile("(a|b)*c");
public void parse(String input) {
Matcher matcher = pattern.matcher(input);
matcher.matches();
}
8.2.3 解决方案
java
// ✅ 使用非贪婪匹配
Pattern pattern = Pattern.compile("(?:a|b)*?c");
优化效果:
CPU占用: 100% → 5%
响应时间: 2秒 → 50ms
8.3 案例3:批处理任务OOM
8.3.1 问题现象
报错信息:
java.lang.OutOfMemoryError: Java heap space
at java.util.HashMap.resize(HashMap.java:703)
8.3.2 排查步骤
步骤1:生成堆转储
bash
jmap -dump:format=b,file=/logs/heapdump.hprof 12345
步骤2:MAT分析
Leak Suspects Report:
Problem Suspect 1:
- Class: java.util.HashMap
- Size: 3.5 GB (90% of heap)
- Suspects:
• com.example.Service.batchData
步骤3:定位代码
java
// ❌ 一次性加载所有数据
public List<Data> loadBatchData() {
List<Data> data = new ArrayList<>();
ResultSet rs = statement.executeQuery("SELECT * FROM data");
while (rs.next()) {
data.add(new Data(rs)); // 加载100万条数据
}
return data;
}
8.3.3 解决方案
java
// ✅ 分批处理
public void processBatchData() {
int offset = 0;
int batchSize = 1000;
while (true) {
List<Data> data = loadData(offset, batchSize);
if (data.isEmpty()) break;
processData(data);
offset += batchSize;
}
}
private List<Data> loadData(int offset, int batchSize) {
List<Data> data = new ArrayList<>();
ResultSet rs = statement.executeQuery(
"SELECT * FROM data LIMIT " + batchSize + " OFFSET " + offset
);
while (rs.next()) {
data.add(new Data(rs));
}
return data;
}
优化效果:
堆内存: 4GB → 2GB
处理时间: 10分钟 → 8分钟
OOM次数: 每天1次 → 0次
九、总结与最佳实践
9.1 JVM调优核心原则
原则1:先监控后调优
监控指标 → 定位问题 → 制定方案 → 验证效果
关键指标:
- GC频率
- GC停顿时间
- 堆内存使用率
- CPU占用率
- 线程数
原则2:堆大小 = 物理内存的60-80%
bash
# ❌ 错误做法
-Xms16g -Xmx16g # 物理内存只有16GB,占用100%
# ✅ 正确做法
-Xms12g -Xmx12g # 物理内存16GB,占用75%
原则3:初始堆 = 最大堆
bash
# ❌ 错误做法
-Xms4g -Xmx8g # 初始和最大不一致
# ✅ 正确做法
-Xms8g -Xmx8g # 初始和最大一致
原则4:选择合适的GC算法
堆大小 < 4GB → Parallel GC
堆大小 4-32GB → G1 GC
堆大小 > 32GB → ZGC
原则5:开启GC日志
bash
# JDK 8
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/logs/gc.log
# JDK 11+
-Xlog:gc*:file=/logs/gc.log:time,tags:filecount=10,filesize=100m
9.2 JVM调优检查清单
启动前检查
- 堆大小设置为物理内存的60-80%
- 初始堆大小等于最大堆大小
- 选择合适的GC算法
- 开启GC日志
- 设置元空间大小
- 开启OOM自动dump
运行时检查
- 定期查看GC日志
- 监控堆内存使用率
- 监控GC频率和停顿时间
- 监控CPU占用率
- 监控线程数
故障排查检查
- CPU 100%:查看线程堆栈
- 频繁GC:查看堆使用情况
- OOM:生成堆转储,使用MAT分析
- 栈溢出:查看递归深度
- 线程泄漏:查看线程数
9.3 常用命令速查
| 命令 | 用途 |
|---|---|
jps |
查看Java进程 |
jstat -gcutil <pid> |
查看GC使用率 |
jstack <pid> |
查看线程堆栈 |
jmap -heap <pid> |
查看堆信息 |
jmap -dump:format=b,file=/tmp/heap.hprof <pid> |
生成堆转储 |
jinfo <pid> |
查看JVM参数 |
jcmd <pid> GC.heap_info |
查看GC信息 |
jcmd <pid> GC.heap_dump /tmp/heap.hprof |
生成堆转储 |
top -c |
查看CPU占用 |
top -H -p <pid> |
查看线程CPU占用 |
9.4 JVM参数配置模板
模板1:通用型Web服务(G1 GC)
bash
-Xms8g -Xmx8g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:InitiatingHeapOccupancyPercent=45 \
-XX:G1HeapRegionSize=16m \
-XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=512m \
-XX:+UseStringDeduplication \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/logs/heapdump.hprof \
-Xlog:gc*:file=/logs/gc.log:time,tags:filecount=10,filesize=100m
模板2:低延迟交易系统(ZGC)
bash
-Xms32g -Xmx32g \
-XX:+UseZGC \
-XX:ZGCParallelGCThreads=8 \
-XX:ZGCConcurrentGCThreads=2 \
-XX:MaxGCPauseMillis=10 \
-XX:+AlwaysPreTouch \
-XX:MetaspaceSize=512m \
-XX:MaxMetaspaceSize=1g \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/logs/heapdump.hprof \
-Xlog:gc*:file=/logs/gc.log:time,tags:filecount=10,filesize=100m
模板3:吞吐量优先批处理(Parallel GC)
bash
-Xms16g -Xmx16g \
-XX:+UseParallelGC \
-XX:ParallelGCThreads=12 \
-XX:GCTimeRatio=99 \
-XX:NewRatio=2 \
-XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=512m \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/logs/heapdump.hprof \
-Xlog:gc*:file=/logs/gc.log:time,tags:filecount=10,filesize=100m
9.5 核心记忆口诀
JVM调优要记牢,堆大小设60-80%
初始最大要一致,GC算法选对路
小堆用Parallel,中堆用G1GC
大堆优先ZGC,延迟要求定输赢
CPU 100看线程,jstack堆栈找原因
死循环最常见,频繁GC也不差
线程阻塞看BLOCKED,锁竞争要优化
OOM分五类型,堆栈方法要分清
堆溢出生成dump,MAT分析找泄漏
栈溢出看递归,元空间看类加载
直接内存看NIO,线程数看线程池
监控工具要会用,jstatjstackjmap
JConsole和VisualVM,MAT分析堆转储
先监控后调优,数据说话不瞎搞
结语
JVM调优是一个系统工程,需要从垃圾回收、CPU、OOM、内存泄漏等多个维度综合考虑。本文涵盖了JVM调优的核心知识点和实战技巧,从理论到实践,从工具到案例,帮助你全面掌握JVM调优。
核心要点:
- 选择合适的垃圾回收器
- 合理配置JVM参数
- 熟练使用监控工具
- 掌握问题排查方法
- 积累实战经验