JVM · 内存模型 / GC 调优 / 排查

JVM 调优速查

从运行时内存区域、GC 算法与收集器选型,到排查命令、OOM 与 CPU 飙高定位套路、常用参数清单,JVM 问题诊断与调优要点一页收齐。

7速查小节 64速查条目 5大收集器 5类 OOM 场景

📖 速查表

点击展开各小节

🧠 运行时内存区域
内存区域作用异常与要点
堆(Heap) 对象实例主战场:新生代(Eden : S0 : S1 默认 8:1:1)+ 老年代,线程共享 OutOfMemoryError: Java heap space;由 -Xms / -Xmx 控制 最高频 OOM
虚拟机栈 / 本地方法栈 线程私有,方法调用的栈帧(局部变量表 / 操作数栈 / 出口) StackOverflowError:递归过深;线程过多无法建栈时抛 OOM
程序计数器 记录当前线程执行的字节码行号,线程切换后恢复执行位置 唯一不会发生 OOM 的区域
元空间(Metaspace) 类元信息与运行时常量池(JDK 8 取代永久代,使用本地内存) OutOfMemoryError: Metaspace-XX:MaxMetaspaceSize 限上限
直接内存 NIO DirectByteBuffer 堆外内存,不受 -Xmx 约束 OutOfMemoryError: Direct buffer memory-XX:MaxDirectMemorySize 限上限
JVM 内存区域
堆分新生代/老年代,Minor GC 存活对象晋升;栈私有易 StackOverflow
♻️ 垃圾回收基础
判活方式说明
可达性分析 从 GC Roots 出发不可达即判死;Roots 包括:栈帧局部变量、静态变量、常量引用、JNI 引用、活跃线程等,主流 JVM 采用
引用计数 给对象记引用数、归零即回收;无法处理循环引用,主流 JVM 均不采用
引用类型回收时机典型用途
强引用 只要可达,永不回收 普通赋值 Object o = new Object()
软引用 SoftReference 内存不足时才回收 页面 / 图片等内存敏感缓存
弱引用 WeakReference 下次 GC 必然回收 ThreadLocalMap 的 key、WeakHashMap
虚引用 PhantomReference 随时可回收,仅用于回收通知 配合 ReferenceQueue 跟踪堆外内存释放
算法思路优缺点与应用
标记-清除 标记存活对象,统一清除垃圾 简单但产生内存碎片;CMS 老年代采用
标记-复制 存活对象复制到另一半空间,整块回收旧空间 无碎片、适合存活率低的新生代;代价是浪费部分空间
标记-整理 标记后向一端移动,清理边界外内存 无碎片但移动对象成本高;老年代(Parallel Old / G1)
概念一句话
安全点 SafePoint 线程必须跑到安全点才能暂停执行 GC;OopMap 记录引用位置,过长循环会插入安全点计数,避免长时间无法到达安全点
⚙️ 收集器对比
收集器区域算法定位与适用
Serial 新生代 复制 单线程 STW,简单高效;客户端或小内存场景
Parallel(Scavenge / Old) 新生代 / 老年代 复制 / 标记-整理 吞吐量优先,JDK 8 默认组合;后台批处理、允许较长停顿的任务
CMS 老年代 标记-清除 并发收集、低停顿优先;碎片与并发失败风险,JDK 9 废弃、JDK 14 移除 已废弃
G1 整堆 Region 化标记-整理 + 复制 可预测停顿模型,JDK 9+ 默认;大堆服务端首选 主流
ZGC 整堆 染色指针 + 读屏障 停顿亚毫秒级且不随堆增大,JDK 15 起生产可用;超大堆低延迟场景
G1 要点说明
Region 分区 堆划分为约 2048 个等大 Region,Eden / Survivor / Old / Humongous 只是角色标签,可动态切换
停顿预测模型 -XX:MaxGCPauseMillis 为目标,按回收收益挑选 Region 组成回收集合 CSet
Humongous 对象 超过 Region 一半的大对象直接进 Humongous 区域,易引发碎片与回收压力,应尽量避免 注意
概念一句话
ZGC 染色指针 把 GC 状态标记在指针位视图上,配合读屏障实现并发转移,回收过程几乎全程并发
🛠️ 排查命令与工具
命令用途常用示例
jps 列出 Java 进程 jps -lvm
jstat GC 统计实时观测 jstat -gcutil <pid> 1000,关注 O 区占用与 YGC / FGC 次数耗时
jmap 堆快照与对象直方图 jmap -dump:live,format=b,file=heap.hprof <pid>jmap -histo <pid>
jstack 线程快照 jstack <pid>:查死锁、_BLOCKED 与 CPU 高线程栈
jcmd 多功能诊断瑞士军刀 jcmd <pid> VM.flags / GC.heap_info / Thread.print
Arthas 命令用途
dashboard 实时总览:线程、内存、GC 一屏看完
thread -n 3 打印最忙的 3 个线程栈,CPU 飙高第一入口 高频
profiler start / stop 采样生成火焰图(async-profiler),定位热点方法
watch / trace 观测方法出入参与调用链耗时,线上免重启排障
离线工具一句话
MAT 分析 hprof:Leak Suspects 报告 + 支配树定位滞留大对象与泄漏点
GCViewer / GCEasy 解析 GC 日志,看停顿分布、回收频率与吞吐变化趋势
🚨 典型问题套路
OOM 与 CPU 飙高是两大高频事故:先留证(dump / GC 日志),再按套路排查,别上来就重启销毁现场。
OOM 类型常见原因排查处理
Java heap space 内存泄漏、一次性加载大对象、堆配置过小 dump 后 MAT 看支配树,先区分泄漏还是容量不足 最高频
Metaspace 动态生成类失控:CGLIB 代理、反射、热部署反复加载 排查动态类生成源;必要时调大 MaxMetaspaceSize
Direct buffer memory 堆外内存申请后未释放 排查 NIO 使用;显式释放并设置 MaxDirectMemorySize
unable to create new native thread 线程数超出 OS 限制 jstack 统计线程来源,收敛线程池与自建线程
StackOverflowError 递归无出口或循环调用 从栈顶向下找重复出现的栈帧定位调用链
CPU 100% 定位步骤命令与动作
1. 定位进程 top 找到 CPU 最高的 Java 进程 PID
2. 定位线程 top -Hp <pid> 找最耗 CPU 的线程 TID
3. 转 16 进制 printf '%x' <tid> 得到 nid
4. 对号入座 jstack <pid> 中搜索 nid=0x…,定位具体线程栈
5. 定性分析 死循环、频繁 GC、序列化热点;用 Arthas profiler 火焰图佐证
内存泄漏原因典型场景规避方式
静态集合持有对象 static Map 只增不减当缓存用 设上限与过期(Caffeine),或改弱引用
ThreadLocal 未 remove 线程池线程复用残留 Entry finally 中显式 remove()
资源未关闭 流 / 连接 / 游标在异常路径泄漏 try-with-resources 统一托管
监听器 / 回调未注销 注册后生命周期结束不解除 与注册对称地注销
内部类持外部引用 非静态内部类隐式持有外围对象 改静态内部类并显式传参
频繁 Full GC 排查链动作
看趋势 jstat -gcutil 观察 FGC 频率与老年代回收后占用,回收不掉即疑似泄漏
dump 对比 间隔数分钟 dump 两份,MAT 对比增长最快的对象
查触发源 大对象直接晋升、元空间接近上限、System.gc() 显式调用、担保失败
分而治之 泄漏改代码,容量不足调堆或换收集器,参数问题修正配置
🎛️ 常用参数速查
参数含义建议
-Xms / -Xmx 初始 / 最大堆大小 生产两者设为相等,避免堆动态伸缩开销 最佳实践
-Xmn 新生代大小 Parallel 可按需设置;G1 不建议手设,与其自动调优冲突
-XX:MetaspaceSize / MaxMetaspaceSize 元空间初始阈值 / 上限 初始阈值适当调大,减少早期 Full GC
-XX:SurvivorRatio Eden 与两个 Survivor 的比例(默认 8) 短命对象为主的业务保持默认即可
-XX:MaxTenuringThreshold 晋升老年代的年龄阈值(默认 15) 按对象实际生命周期调整,防止过早晋升
-XX:MaxGCPauseMillis G1 目标停顿时间(默认 200ms) 不宜盲目调小,过小导致回收过于频繁
-XX:InitiatingHeapOccupancyPercent G1 触发并发标记的堆占比(默认 45) 适当提前标记,可降低 Full GC 风险
GC 日志参数 JDK 8:-XX:+PrintGCDetails 等;JDK 9+:-Xlog:gc* 生产常开并滚动输出,出问题才有现场
OOM 留证 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=… OOM 时自动生成 hprof 快照,保留第一现场 必配
🚀 一套通用启动参数

不追求极致调优、先保证「不踩坑、有现场」的生产基线配置:堆上下限一致、G1 + 停顿目标、OOM 自动留证、GC 日志常开。4G 堆的 Spring Boot 服务可直接套用,按内存规格等比缩放。

# 生产基线示例(4G 堆服务,JDK 17+),逐行注释: java -Xms4g -Xmx4g # 初始堆 = 最大堆,避免运行期堆伸缩抖动 必配 -XX:+UseG1GC # G1 收集器:JDK 9+ 默认,大堆低停顿首选 -XX:MaxGCPauseMillis=200 # 停顿目标 200ms;盲目调小会回收过频,先压测再动 -XX:MetaspaceSize=256m # 元空间初始阈值调高,避免启动初期类加载触发 Full GC -XX:MaxMetaspaceSize=512m # 元空间上限兜底,防动态代理类失控打爆本地内存 -XX:+HeapDumpOnOutOfMemoryError # OOM 自动 dump,保留第一现场 必配 -XX:HeapDumpPath=/data/dump/ # dump 输出目录:独立磁盘分区,避免写满应用盘 -Xlog:gc*:file=/data/logs/gc.log:time,uptime:filecount=5,filesize=50m # JDK 9+ GC 日志,滚动 5 份各 50M # 两个容易配错的点: # -Xmn 不要设——G1 靠自动分代调优达成停顿目标,手动设新生代会与其冲突 # 容器环境另加 -XX:MaxRAMPercentage=75.0 替代固定 -Xmx,按容器配额留出元空间与线程栈余量