内存泄露

基本概念参考:https://www.cnblogs.com/0201zcr/p/4805920.html

内存泄露 vs 内存溢出

  • 内存溢出(OOM):程序申请内存时,没有足够的空间供其使用
  • 内存泄露:程序申请内存成功,使用完后,这部分空间无法被GC回收,导致可用内存减少,一次内存泄露危害可以忽略,但内存泄露堆积会导致内存溢出

内存泄露本质

垃圾回收机制需要通过可达性分析标记垃圾对象,没有被标记的对象不会被回收,内存泄漏是使用完后之后不再被使用的对象由于依然被引用(一般是强引用),导致GC未将其标记为垃圾导致没有被回收。

  • 强引用:无论内存是否充足,都不会被回收的对象引用
  • 软引用:在内存空间不足时会被回收,内存充足的情况下不会被回收
  • 弱引用:在垃圾回收时,只要发现弱引用,直接被回收
  • 虚引用:跟踪对象的回收,清理被销毁对象的相关资源,无法通过get获取对象,直接返回null

这里简单介绍下四种引用类型的应用场景:

  • 强引用:几乎所有常规对象都使用强引用,确保对象在程序运行期间始终存在
  • 软引用:内存敏感的缓存,这里的缓存是指由JVM管理的内存,redis这种独立于JVM存在的缓存不在考虑范围内
  • 弱引用:临时缓存,只要数据不被强引用持有了,GC立即回收(ThreadLocal)
  • 虚引用:跟踪对象被回收后执行某些清理操作,不能通过虚引用访问某个对象

内存泄露现象

发生内存泄露时,存在一些异常现象,如下:

  1. 内存占用率持续缓慢上升,而非正常的锯齿状
  2. GC频率和耗时持续增长,CPU利用率飙升
  3. 各类性能指标异常:吞吐率、接口响应时间、系统负载等

内存泄露排查思路

  1. 观察指标,如果内存持续增长,且GC效益很低,基本可以确定是内存泄漏;
    • 内存缓慢增长:内存泄漏
    • 内存瞬间溢出:内存溢出
  2. top查看进程各项指标,重点关注CPU利用率(随着内存持续泄露,一般垃圾回收线程利用率会飙到异常高)
  3. 进一步分析GC日志,如果GC频率很高,基本可以确定发生内存泄露
  4. 通过内存快照分析工具分析内存快照,查看内存空间具体的使用情况,定位内存空间占用率异常高的类,追溯代码逻辑

发生内存泄露时,重启可以暂时解决问题,线上发生内存泄漏,转储内存快照并重启机器,之后分析内存快照,尝试在测试环境复现,追溯泄露源头

常见内存泄露场景

  1. 静态集合类
1
2
3
4
5
6
7
8
9
10
11
public class CacheManager {
// 静态集合没有过期策略需要手动回收,且生命周期一直存活到系统关闭
private static final Map<String, Object> CACHE = new HashMap<>();
public static void putCache(String key, Object value) {
CACHE.put(key, value);
}
public static Object getCache(String key) {
return CACHE.get(key);
}
// 实例不主动清除的情况下非常容易发生内存泄漏
}
  1. ThreadLocal:ThreadLocal的key为弱引用,value为强引用
  2. 资源未关闭
1
2
3
4
5
6
7
8
public void readFile(String path) throws IOException {
FileInputStream fis = new FileInputStream(path);
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
// 处理数据
}
}

内存泄露
http://xuxiusheng.github.io/2025/12/30/内存泄露/
作者
Xuxiusheng
发布于
2025年12月30日
许可协议