最近遇到一个很有意思的问题,值得我学习一下,这里就总结一下大家一块学习,有啥想法可以评论区讨论。
先根据题目思考一下,你能不能get到不同点?
下面揭晓答案了。
一、什么是内存溢出?(Out Of Memory, OOM)
定义:
内存溢出 是指程序在申请内存时,没有足够的内存空间供其使用,JVM 无法再分配所需内存,从而抛出 java.lang.OutOfMemoryError 错误。没错,就是人人皆知的OOM
常见的 OOM 类型(Java 中):(重点啊,吊打面试官的技能啊,必须背下来)
类型
说明
常见原因
OutOfMemoryError: Java heap space
堆内存溢出
对象太多,老年代无法容纳
OutOfMemoryError: Metaspace
元空间溢出
加载的类过多(如动态生成类)
OutOfMemoryError: Unable to create new thread
线程栈溢出
创建线程过多
OutOfMemoryError: GC overhead limit exceeded
GC开销过大
GC频繁但回收效果差,几乎无法释放内存
示例代码(堆溢出):
List<String> list = new ArrayList<>();
while (true) {
list.add("Hello OOM");
}
// 抛出:java.lang.OutOfMemoryError: Java heap space
根本原因:
- 程序申请的内存总量 > JVM 可分配的最大内存
- 内存中存在大量存活对象,GC 无法回收
二、什么是内存泄漏?(Memory Leak)
定义:
内存泄漏 是指程序中已经不再使用的对象,由于某些原因无法被垃圾回收器(GC)回收,导致这些对象一直占用内存,随着时间推移,内存占用越来越高,最终可能引发内存溢出。
🔍 关键点:对象“无用但可达” → GC 无法回收。
常见的内存泄漏场景(Java):
1. 静态集合类持有对象引用
public class MemoryLeakExample {
private static List<Object> cache = new ArrayList<>();
public void addToCache(Object obj) {
cache.add(obj); // 对象加入后永不移除
}
}
→ 缓存无限增长,对象无法回收。
2. 监听器、回调未注销
eventSource.addListener(new Listener() {
public void onEvent(Event e) { ... }
});
→ 如果不显式移除监听器,对象将一直被引用。
3. 内部类持有外部类引用(非静态内部类)
public class Outer {
private Object data = new byte[1024 * 1024]; // 大对象
public void startThread() {
new Thread(new Runnable() {
public void run() {
// 非静态内部类隐式持有Outer.this
// 即使Outer不再使用,也无法被回收
}
}).start();
}
}
4. ThreadLocal 使用不当(这个比较常见,很容易忽略的问题)
private static ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();
public void setBigData() {
threadLocal.set(new byte[1024 * 1024]);
// 忘记调用 threadLocal.remove();
}
5. 资源未关闭(IO、数据库连接等)(上一篇mq消费者消息堆积,不就是资源忘记关闭了吗,这个太常见了,必须牢记)
java深色版本FileInputStream fis = new FileInputStream("largeFile.txt");
// 忘记 fis.close();
→ 文件句柄和缓冲区无法释放
三、内存泄漏 vs 内存溢出:区别与联系
对比项
内存泄漏(Memory Leak)
内存溢出(Memory Overflow)
本质
资源管理问题:无用对象未被释放
资源耗尽问题:内存不足
是否抛异常
不直接抛异常,是渐进过程
直接抛 OutOfMemoryError
发生阶段
长期运行后逐渐发生
可能瞬间发生
可逆性
可通过优化代码修复
通常需重启JVM
因果关系
内存泄漏是内存溢出的常见诱因
内存溢出是结果
类比
水龙头漏水(缓慢流失)
水池满了溢出(突然爆发)
✅ 一句话总结区别:
- 内存泄漏:该释放的内存没释放(浪费了)
- 内存溢出:要申请的内存拿不到(不够用了)
🔗 联系: 内存泄漏积累到一定程度,最终会导致内存溢出。
四、如何检测和解决?
1. 检测工具
- JVM 自带工具:
jstat:查看GC情况jmap:生成堆转储文件(heap dump)jhat/jvisualvm:分析堆文件
- 第三方工具:
- MAT (Memory Analyzer Tool):分析
.hprof文件,定位泄漏对象 - JProfiler、YourKit:可视化监控内存使用
- Arthas(阿里开源):线上诊断神器
- MAT (Memory Analyzer Tool):分析
2. 解决思路
- 使用
WeakReference、SoftReference替代强引用 - 及时清理集合、缓存(如使用
LRUCache) - 避免静态集合长期持有对象
- 正确使用
ThreadLocal,记得remove() - 实现
AutoCloseable,使用 try-with-resources - 合理设置JVM参数(如
-Xmx、-XX:MaxMetaspaceSize)
五、总结
术语
关键理解
开发建议
内存泄漏
无用对象未被回收 → 内存被“悄悄吃掉”
写代码时注意引用生命周期,避免长期持有
内存溢出
内存不够用 → JVM崩溃
合理设置堆大小,监控内存使用,及时排查泄漏
💡 作为Java工程师的忠告:
- 内存泄漏是“慢性病”,内存溢出是“急性病”。
- 多数OOM的根本原因是内存泄漏。
- 上线前务必做压力测试和内存分析,避免生产事故。