内存泄漏以及ThreadLocal问题

  • 内存泄漏:程序申请内存后,无法释放已经申请的内存,导致内存持续增长,引起系统性能下降。
    核心问题:该释放的内存,无法释放。

    常见内存泄漏场景:

  1. 强引用一直未解除,当对象被强引用时,即使不再需要,也无法被GC回收。比如集合不断添加元素但未清理。
public class LeakyService {
    private List<byte[]> cache = new ArrayList<>();      
    public void process() {
            byte[] data = new byte[1024 * 1024]; // 几乎无用的大对象       
             cache.add(data); // 不清理 cache,导致内存泄漏  
     }
  }
  1. ThreadLocal 未清除,ThreadLocal中键为弱引用在内存不足时可以被GC回收,而ThreadLocal中值为强引用,若不手动清理remove()可能导致其长时间占用内存,引起内存泄漏。比如:线程池中的 ThreadLocal 泄漏
// 在共享线程池中执行任务
ExecutorService pool = Executors.newFixedThreadPool(10);

pool.submit(() -> {
    ThreadLocal<byte[]> tl = new ThreadLocal<>();
    tl.set(new byte[1024 * 1024]); // 任务结束后未 remove()
});

线程池中的线程复用后,ThreadLocal 的值未被清理,长期占用内存。

  1. 静态变量引用临时对象。静态变量的生命周期与程序一致,若它引用了临时对象(如 static List),则对象无法被回收。
    示例:
public class LeakyClass {
    private static List<MyObject> instances = new ArrayList<>();
    
    public static void process(MyObject obj) {
        instances.add(obj); // obj 可能已无用,但被静态引用持续持有
    }
}
  1. 未关闭的资源 如数据库连接,未关闭的流(Stream)文件句柄等,可能导致资源泄露
public void readFile() {
    File file = new File(...);    FileInputStream fis = new FileInputStream(file); // 未调用 fis.close()}
  1. 对象引用形成循环 A引用b,b引用a且互相强引用,外部无法访问时。GC无法回收导致内存泄漏
public class A {
    private B b;
}
public class B {
    private A a;
}

// 创建循环引用
A a = new A();
B b = new B();
a.b = b;
b.a = a;

a = null;
b = null; // 此时 a 和 b 彼此引用,无法被回收

内存泄漏的表现:

  • 通过查看jvm各代的内存情况发现程序无明显增长逻辑,但内存持续增长。
  • 频繁的gc
  • oom溢出

问题分析

  • 堆转储分析
    • 生成 Heap Dump:
jmap -dump:live,format=b,file=heapdump.hprof 

使用 MAT 分析:

  • 打开 Heap Dump,找到 Dominator Tree 查看内存占用最大的对象。
  • 使用 Leak Suspects 插件分析潜在泄漏点。
  • 检查对象引用链:
  • 找到对象无法被回收的路径(如 LeakyService → cache → MyObject)。

常见问题

  • 内存泄漏必须表现为 OutOfMemoryError → 不准确,长期占用内存也属于泄漏。
  • 只出现一次的内存增长不算泄漏 → 错误,任何未释放的内存增长都需排查。
  • 使用 System.gc() 可以解决泄漏 → 无效,GC 无法回收被强引用持有的对象

题外话

threadlocal的value为什么要用强引用而key用弱引用呢?

  1. value为强引用是为了保证其存储的副本变量不可丢失,只要有key就要有value。并且防止弱引用在内存不足时清理value。
  2. key为弱引用,若为强引用,ThreadLocalMap中的Entry将直接引用ThreadLocal实例。当外部对ThreadLocal强引用消失后ThreadLocal仍被Entry持有无法GC
    问题:当key被回收后,value还是强引用,不会内存泄露吗?
    ThreadLocal内部有一个自动回收机制,当弱引用的key被清理后,在后续的put(),set(),扩容时会检查key是否为空,若为空则清理value。

你可能感兴趣的:(java,jvm)