内存泄漏(Memory Leak) 和 内存溢出(Out Of Memory,OOM) 是两个经常一起出现但本质不同的问题
一、内存泄漏是什么
定义
程序中有一些对象:
- 已经不再使用
- 但是仍然被引用着
- 导致 GC(垃圾回收)无法回收
类比生活中抽象的说法:占着茅坑不拉屎,导致后面的人没地方
这些无法释放的内存会不断积累。
这就叫: 该释放的内存没有释放
特征
随着程序运行时间增长:
- JVM 内存占用越来越高
- Full GC 越来越频繁
- 系统越来越卡
- 最终可能导致 OOM
Java 示例
java
public class MemoryLeakDemo {
private static List<Object> list = new ArrayList<>();
public static void main(String[] args) {
while (true) {
list.add(new byte[1024 * 1024]); // 1MB
}
}
}
这里的问题:
list是静态集合- 对象一直被
list引用 - GC 永远无法回收
结果:
- 内存不断增长,导致可用内存逐渐变小
- 最终 OOM
这属于:内存泄漏导致的内存溢出
二、内存溢出(OOM)是什么
定义
程序申请内存时:
- JVM 已经没有足够内存
- 且 GC 后仍无法获得足够空间
于是 JVM 抛出:
java
java.lang.OutOfMemoryError
这个就是:内存溢出
本质
OOM 的本质是: 内存不够用了
三、两者核心区别
| 对比项 | 内存泄漏 | 内存溢出 |
|---|---|---|
| 本质 | 无用对象无法被回收,导致可用内存越来越小 | 内存不足 |
| 是否一定报错 | 不一定 | 一定会报错 |
| 是否会导致 OOM | 可能会 | 本身就是 OOM |
| 问题性质 | 资源管理问题 | 内存容量问题 |
| 发生速度 | 通常逐渐恶化 | 可以瞬间发生 |
| 常见原因 | 引用未释放 | 内存申请过大 |
四、它们之间的关系
内存泄漏 ------> 导致可用内存越来越小 ------> 内存不够用 ------> 触发OOM
也就是说 :内存泄漏通常是 OOM 的原因之一
但是 :OOM 不一定是全是由内存泄漏导致的
五、OOM 不一定是泄漏
例如:
java
byte[] bytes = new byte[1024 * 1024 * 1024];
直接申请 1GB的内存:
- 如果 JVM 堆只有 512MB
- 会立刻 OOM
这里:没有发生 内存泄漏 只是 申请过大
六、Java 中常见的内存泄漏场景
1. 静态集合持有对象
java
static List<User> cache = new ArrayList<>();
生命周期和 JVM 一样
2. ThreadLocal 未 remove
java
threadLocal.set(value);
线程池线程不会销毁
如果不:
java
threadLocal.remove();
可能泄漏。
3. 监听器/回调未注销
例如:
- EventListener
- Observer
- Netty Handler
对象一直被引用着
4. 数据库连接未关闭
Connection conn
导致连接池资源耗尽
5. 缓存无限增长
例如:创建一个大对象 只放不删,一直没有被回收
Map<String, Object> cache = new HashMap<>();
七、总结
内存泄漏
不该活着的(不在使用的)对象还活着,导致可用内存逐渐变小
内存溢出
JVM 已经没内存可用了,新的对象申请不到内存了