🧠 运行时内存区域
| 内存区域 | 作用 | 异常与要点 |
| 堆(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 限上限 |
堆分新生代/老年代,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,按容器配额留出元空间与线程栈余量