JVM
- [内存泄漏(Memory Leak)](#内存泄漏(Memory Leak))
- 内存溢出(OOM)
-
- [1. 扩容堆内存](#1. 扩容堆内存)
- [2. 优化内存使用](#2. 优化内存使用)
- [3. 优化 GC 策略](#3. 优化 GC 策略)
- [4. 处理非堆 OOM](#4. 处理非堆 OOM)
- [5. 兜底方案](#5. 兜底方案)
- 问题对比与综合预防
- 通用最佳实践
-
- [1. 监控预警:](#1. 监控预警:)
- [2. 压测验证:](#2. 压测验证:)
- [3. 代码层面:](#3. 代码层面:)
- [4. JVM 调优:](#4. JVM 调优:)
内存泄漏(Memory Leak)和内存溢出(Memory Overflow/OOM)是两类不同的内存问题,其根本原因和解决方案有显著区别
内存泄漏(Memory Leak)
问题本质 : 对象已不再被使用,但因错误引用无法被 GC 回收,导致内存被持续占用,最终可能引发 OOM
核心解决思路 :定位并切断无效引用链
解决方案
1.定位泄漏点(关键步骤)
工具:
jmap
+ MAT(Memory Analyzer Tool):生成堆转储(Heap Dump),分析对象引用链。- VisualVM:实时监控堆内存,跟踪对象增长趋势。
- Arthas :在线诊断,执行
heapdump
命令动态生成 Dump。
操作 :
-
对比多次 Heap Dump,找到 持续增长且未被回收的对象 。
-
在 MAT 中查看 GC Roots 引用链,找到意外持有对象的根源(如静态集合、线程池)
2.修复代码:切断无效引用
2.1.静态集合类 :使用 WeakHashMap
或定期清理(如 remove()
)
java
// 错误示例:静态List持有对象
private static List<Object> cache = new ArrayList<>();
// 修复:改用弱引用或添加清理逻辑
private static Map<Object, WeakReference<Object>> weakCache = new WeakHashMap<>();
2.2.监听器/回调 :及时注销(如 removeEventListener()
)
2.3.资源对象 :确保 close()
(用 try-with-resources 语法)
java
// 正确关闭资源
try (InputStream is = new FileInputStream("file.txt")) {
// 使用资源
} // 自动调用 is.close()
2.4.避免生命周期错配 :
非静态内部类隐式持有外部类 → 改为 静态内部类
3.预防措施
- 代码规范:避免在长生命周期对象(如静态集合)中持有短生命周期对象。
- 代码审查:重点关注集合操作、资源关闭、监听器注册。
- 自动化测试:用 LeakCanary(Android) 或 JMH + 内存分析 检测泄漏。
内存溢出(OOM)
问题本质 : JVM 堆内存不足 或 非堆内存耗尽,无法分配新对象。
核心解决思路 :扩容内存 或 减少内存消耗
解决方案
1. 扩容堆内存
- 调整 JVM 参数(临时方案):
bash
-Xms4g -Xmx4g # 初始堆=最大堆(避免动态扩容开销)
-XX:MaxMetaspaceSize=512m # 限制元空间大小
- 风险 :单次 GC 时间变长(STW 暂停时间增加):单次 GC 时间变长是指在应用程序运行过程中,垃圾回收(Garbage Collection,简称GC)所消耗的时间相比于之前变得更长,
意思就是回收效率越来越慢
2. 优化内存使用
- 减少对象创建 :
- 重用对象:使用 对象池(如 Apache Commons Pool)。
- 避免创建大对象:如大数组 → 改用分块处理。
- 调整数据结构 :
- 用
ArrayList
替代LinkedList
(内存更紧凑)。 - 原始类型集合:用 FastUtil 或 Eclipse Collections 避免装箱。
- 用
- 缓存优化 :
- 限制缓存大小:LRU 策略(如
Guava Cache
的maximumSize()
)。 - 软引用/弱引用缓存(
SoftReference
在内存不足时自动释放)。
- 限制缓存大小:LRU 策略(如
3. 优化 GC 策略
- 选择低停顿收集器(如 G1 、ZGC):
bash
-XX:+UseG1GC # G1 收集器(JDK9+ 默认)
-XX:+UseZGC # ZGC(JDK15+ 生产可用)
- 调整分代比例(针对分代收集器):
bash
-XX:NewRatio=2 # 老年代:新生代=2:1
-XX:SurvivorRatio=8 # Eden:Survivor=8:1:1
4. 处理非堆 OOM
- Metaspace OOM :
- 原因:加载过多类(如动态生成类、重复加载)。
- 解决:
- 增加元空间:
-XX:MaxMetaspaceSize=256m
- 排查类加载泄漏:用
jcmd <pid> VM.classloader_stats
查看类加载器。
- 增加元空间:
- 栈溢出(StackOverflowError) :
- 原因:递归过深或循环调用。
- 解决:优化递归为循环;增大栈空间
-Xss2m
(谨慎使用)
5. 兜底方案
- 捕获 OOM 并优雅降级:
java
try {
// 可能触发 OOM 的操作
} catch (OutOfMemoryError e) {
System.gc(); // 尝试紧急回收(不保证生效)
fallbackOperation(); // 执行降级逻辑(如清理缓存、返回默认值)
}
- 自动 Dump 内存快照:
bash
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path/to/dump.hprof
问题对比与综合预防
维度 | 内存泄漏 | 内存溢出 |
---|---|---|
根本原因 | 对象无法回收(引用未释放) | 内存需求超过 JVM 上限 |
解决重点 | 定位无效引用链 + 修复代码 | 扩容内存 + 减少消耗 + GC 调优 |
工具 | MAT、VisualVM、Arthas | JVM 参数、GC 日志分析器(GCeasy) |
预防手段 | 代码规范、LeakCanary、代码审查 | 容量规划、压测、限流、降级策略 |
紧急恢复 | 重启应用(临时) | 扩容实例、重启 + 内存参数调整 |
通用最佳实践
1. 监控预警:
- 使用 Prometheus + Grafana 监控堆内存、GC 次数、Metaspace 使用率。
2. 压测验证:
- 通过 JMeter/Siege 模拟流量,观察内存增长是否稳定。
3. 代码层面:
- 避免
static
滥用,及时解引用 - 使用
-Xlint:unchecked
编译选项检测集合操作警告。
4. JVM 调优:
- 定期分析 GC 日志(
-Xlog:gc*
):
bash
java -Xlog:gc*:file=gc.log -jar app.jar
- 使用 GCeasy 等工具自动化分析 GC 日志
关键总结:
- 内存泄漏是程序 Bug,必须修复代码;
- 内存溢出是资源问题,需结合扩容和优化;
- 二者可能相互转化:长期泄漏必导致 OOM,而 OOM 可能暴露泄漏点。
通过 监控 + 分析工具 + 代码规范 可预防 90% 的内存问题。