Java

JVM 学习路线与故障排查

·12 分钟阅读·4524 字

运维工程师 JVM 学习路线——内存结构、GC、参数配置、线上排查命令、高频故障处理

📋 目录

JVM 学习路线与故障排查

面向运维工程师的 JVM 学习路线,从内存结构到线上故障排查,循序渐进。 核心聚焦:内存模型、参数配置、监控指标、GC 日志、故障排查、线上调优、常见OOM/卡顿问题

一、整体学习路线(由浅到深,按优先级排序)

  1. JVM 内存结构(运行时数据区)
  2. 堆内存划分 + 分代模型(新生代/老年代/元空间)
  3. 常见垃圾收集器、GC 算法、GC 日志解读
  4. 核心 JVM 启动参数(生产必配)
  5. 线上监控工具 & 排查命令(jps/jstat/jmap/jhat/jstack/jinfo
  6. 线上典型故障:OOM、内存泄漏、CPU 飙升、线程死锁、应用卡顿
  7. 堆转储 dump 分析、MAT 工具使用
  8. 线上常规调优思路(非极致性能调优)
  9. 容器环境下 JVM 坑(Docker + JVM 内存限制)

二、分模块详细学习内容(运维重点标注)

一、JVM 运行时数据区(必学,理解内存从哪来)

1. 五大区域作用 + 线程私有/共享

  • 程序计数器(线程私有):记录当前执行行号,唯一无 OOM 区域
  • 虚拟机栈 / 本地方法栈(线程私有):方法调用、栈帧,对应 栈溢出 StackOverflowError
  • 堆 Heap(线程共享):所有对象实例、数组存放地,运维 80% 问题都出在这里
  • 方法区 / 元空间 Metaspace(线程共享):类元数据、常量、方法信息;JDK8 彻底移除永久代,改用元空间(直接使用宿主机物理内存)

运维关注点

  1. 区分:栈内存溢出 vs 堆内存溢出 现象不同、排查方向不同
  2. 元空间溢出场景:动态生成类、热部署、大量反射、CGLIB 代理

二、堆内存分代模型(重中之重,GC 基础)

1. 分代划分(JDK8 默认)

  1. 新生代 Young
  • Eden 伊甸区
  • Survivor 幸存区:S0 / S1(又称 From / To,永远一块空、一块用
  • 对象特点:生命周期短、回收频繁
  1. 老年代 Old
  • 存放长期存活对象、大对象
  • 回收频率低、速度慢

2. 对象流转规则(面试+排查必背)

  1. 新对象优先分配在 Eden
  2. Eden 满 → 触发 Minor GC(新生代GC)
  3. 存活对象进入 Survivor,年龄 +1
  4. 对象年龄达到阈值(默认15)→ 晋升老年代
  5. 大对象直接进入老年代(避免新生代复制开销)
  6. 老年代空间不足 → 触发 Major GC / Full GC

运维关注点

  • 频繁 Minor GC、频繁 Full GC 分别代表什么问题
  • 动态年龄判断、对象动态晋升规则(了解即可)
  • 大对象直接进老年代带来的隐患

三、垃圾回收 GC 算法 & 垃圾收集器(核心)

1. 基础 GC 算法(理解原理,不用手写)

  • 标记-清除:产生内存碎片
  • 复制算法:新生代主流,无碎片,占用双倍空间
  • 标记-整理:老年代主流,压缩整理,无碎片

2. 主流垃圾收集器(运维只记特点、适用场景、搭配

按使用频率排序:

  1. G1(Garbage-First)
  • JDK9+ 默认,JDK8 生产首选
  • 整堆分区划分,可预测停顿时间
  • 兼顾吞吐量 + 低延迟,线上业务服务标配
  1. Parallel GC(Parallel Scavenge + Parallel Old)
  • JDK8 默认,侧重吞吐量
  • 适合:后台批处理、报表、离线任务,对停顿不敏感
  1. CMS(Concurrent Mark Sweep)
  • 并发低延迟,老年代收集器
  • 缺点:内存碎片、并发浮动垃圾、CPU 占用高
  • 现在逐步被 G1 替代
  1. ZGC / Shenandoah(低延迟神器)
  • JDK11+ 新收集器,毫秒级停顿
  • 高并发、低延迟核心服务使用

运维必须掌握

  1. 区分:Minor GC / Major GC / Full GC 触发条件 & 影响
  2. GC 日志解读(线上排障第一手依据)
  • 看懂:内存使用、回收前后大小、停顿时间、GC 类型
  1. 不同收集器对应的日志格式差异

四、JVM 核心启动参数(生产必背、日常配置)

分三类:内存参数、GC参数、排查辅助参数

1. 内存相关(最常用)

# 堆初始内存、最大堆内存
-Xms  # 初始堆,生产建议 = -Xmx,避免运行期扩容卡顿
-Xmx  # 最大堆内存

# 新生代大小 / 比例
-Xmn       # 新生代总大小
-XX:SurvivorRatio  # Eden / Survivor 比例

# 元空间(JDK8+)
-XX:MetaspaceSize
-XX:MaxMetaspaceSize

# 栈内存
-Xss  # 单个线程栈大小

# 大对象阈值
-XX:PretenureSizeThreshold

2. GC 收集器指定

# G1 开启
-XX:+UseG1GC

# 并行收集器(JDK8默认)
-XX:+UseParallelGC

# CMS
-XX:+UseConcMarkSweepGC

3. 故障排查必备参数(生产强制加上)

# 打印GC详细日志
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps

# OOM 时自动生成堆转储文件(最重要!)
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/xxx/heap.hprof

# OOM 执行脚本(可选,发告警、重启)
-XX:OnOutOfMemoryError="sh /xxx/restart.sh"

运维底线:所有 Java 应用必须开启 OOM dump + GC 日志


五、线上排查命令集(运维每日在用,必须熟练)

1. 基础进程查看

  • jps:查看 Java 进程、PID、启动参数
  • jinfo pid:查看/动态修改 JVM 参数

2. 实时监控 GC & 内存

  • jstat -gc pid 1000每秒打印GC统计(线上首选,轻量不影响性能)
    重点看:YGC、YGCT、FGC、FGCT、堆各区域使用率

3. 线程排查(CPU 高、卡顿、死锁)

  • jstack pid:打印线程栈
    用途:

  • 线程死锁

  • 死循环导致 CPU 100%

  • 线程阻塞、任务堆积

4. 堆内存快照(OOM、内存泄漏)

  • jmap -dump:format=b,file=xxx.hprof pid:手动生成堆dump
  • jmap -heap pid:查看当前堆配置、各代使用情况

5. 分析工具

  • MAT(Memory Analyzer Tool):解析 hprof 文件,找大对象、内存泄漏、可疑类
  • JVisualVM:图形化监控、远程连接、抽样分析

运维标准排查流程(背下来)

  1. top / htop 找到高 CPU / 高内存 Java PID
  2. jstat 观察 GC 频率,判断是频繁GC还是内存溢出
  3. jstack 查线程:死锁、死循环、阻塞
  4. 若怀疑内存泄漏/OOM:分析 GC 日志 + 下载 heap.hprof 用 MAT 分析

六、线上高频故障(运维核心考点,场景+现象+原因+处理)

1. 内存溢出 OOM(java.lang.OutOfMemoryError)

细分 4 种常见类型:

  1. Java heap space 堆溢出
  • 现象:老年代占满,Full GC 频繁且回收效果差
  • 原因:内存泄漏、堆太小、业务峰值流量大
  1. Metaspace 元空间溢出
  • 原因:动态类、热部署、大量反射、动态代理
  1. Unable to create new native thread 无法创建新线程
  • 原因:线程数打满、单个栈-Xss设置过大、系统线程数上限低
  1. GC overhead limit exceeded
  • JVM 花 98% 时间 GC,只回收 2% 内存,直接报错退出
  • 典型:严重内存泄漏

2. 应用卡顿、响应慢

  • 现象:接口超时、RT 飙升、吞吐量下降
  • 根因:
  1. 频繁 Full GC / CMS GC 停顿
  2. 大量线程阻塞、锁等待
  3. 死循环 CPU 跑满

3. CPU 占用 100%

排查步骤:

  1. top 找到 PID
  2. top -Hp pid 找到高占用线程 TID
  3. TID 转十六进制
  4. jstack pid | grep 十六进制TID 定位死循环代码行

4. 内存泄漏(最隐蔽)

  • 现象:内存缓慢上涨,Full GC 越来越频繁,最终 OOM
  • 排查:GC 日志 + heap dump + MAT 找无法被回收的大对象

5. 线程死锁

  • 现象:服务卡住、请求不返回、线程池耗尽
  • 排查:jstack 直接搜索 Deadlock

七、容器环境 JVM 专属坑(现在运维必学:Docker + Java)

经典问题

Docker 内部 Java 进程识别宿主机全量内存,不识别容器内存限制,导致:

  • 容器被 OOM kill(内核杀死进程)
  • JVM 堆设太大,超出容器限额

解决方案

  1. JDK8 u191+ / JDK10+ 支持容器内存感知
  2. 启动参数增加:
-XX:+UnlockExperimentalVMOptions
-XX:+UseCGroupMemoryLimitForHeap
  1. 规范:容器内 -Xmx 一定要小于容器 memory limit,预留系统/堆外/元空间/栈内存

八、运维 vs 开发 JVM 学习边界(分清重点,不做无用功)

✅ 运维必须深耕

  • 内存分区、分代模型
  • GC 类型、收集器特点、GC 日志解读
  • 全套 jps/jstat/jmap/jstack 命令实操
  • OOM、内存泄漏、CPU高、死锁 全套排查流程
  • 生产 JVM 参数配置、调优思路
  • Docker + JVM 适配问题
  • MAT 分析堆快照

❌ 运维简单了解即可(不用深挖)

  • 类加载器双亲委派模型(知道概念就行,不用源码)
  • 字节码、指令集、javac 编译
  • JIT 即时编译底层、逃逸分析(了解概念)
  • 各种 GC 算法数学推导、收集器源码

九、学习顺序建议(循序渐进)

  1. 先搞懂 运行时数据区 → 知道内存分哪几块
  2. 再学 堆分代 + 对象流转 → 理解 GC 为什么发生
  3. 掌握 GC 收集器选型 + GC 日志
  4. 熟记 常用 JVM 参数,能独立编写启动脚本
  5. 练熟 jps/jstat/jmap/jstack 四大命令,模拟排障
  6. 学习 MAT 分析 dump 文件
  7. 逐个吃透线上经典故障场景
  8. 补充 Docker + JVM 容器坑
  9. 简单了解调优思路(非开发级深度调优)

十、面试高频 JVM 题(运维岗)

  1. 简述 JVM 运行时数据区,哪些线程共享、哪些私有?
  2. 新生代、老年代 GC 分别是什么?触发条件?
  3. G1 和 Parallel、CMS 收集器区别,生产如何选型?
  4. 线上应用频繁 Full GC 怎么排查?
  5. 发生 OOM 你如何一步步定位问题?
  6. CPU 100% 如何排查?
  7. 内存泄漏和内存溢出区别?怎么排查内存泄漏?
  8. Docker 中部署 Java 应用有哪些 JVM 坑?如何解决?

关联文档