欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ThreadLocal内存泄漏和弱引用分析

程序员文章站 2022-05-05 21:46:17
...

ThreadLocal内存泄漏和弱引用分析

ThreadLocal实现原理

ThreadLocal实现原理是由每一个Thread维护一个ThreadLocalMap映射表,key是threadLocal对象,并且使用的是弱引用。看一下ThreadLocal的set方法:

	public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

直接是new一个ThreadLocalMap并赋值给当前线程的threadLocals属性。映射表由每一个线程自己维护,从而实现了线程之间的数据隔离。ThreadLocal在Java内存中的引用关系示意图如下:
ThreadLocal内存泄漏和弱引用分析

内存泄漏分析

从上图可以看出,当ThreadLocal没有被GC Roots连接,因为弱引用的特点,不管当前内存空间足够与否,在GC时ThreadLocal都会被回收,这样会导致ThreadLocalMap中出现key为null的Entry,外部将不能获取这些key为null的Entry的value,并且如果当前线程一直存活(如线程池),那么就会导致value对应的Object一直无法被回收,产生内存泄露。
查看源码会发现,ThreadLocal的get、set和remove方法都实现了对所有key为null的value的清除,但仍可能会发生内存泄露,因为可能使用了ThreadLocal的get或set方法后发生GC,此后不调用get、set或remove方法,为null的value就不会被清除。
解决办法是每次使用完ThreadLocal都调用它的remove()方法清除数据,或者按照JDK建议将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉

为什么使用弱引用

从表面上看内存泄漏的根源在于使用了弱引用。网上的文章大多着重分析ThreadLocal使用了弱引用会导致内存泄漏,但是另一个问题也同样值得思考:为什么使用弱引用而不是强引用?

我们先来看看官方文档的说法:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
是为了应对长时间的使用场景,哈希表使用弱引用的 key。
下面我们分两种情况讨论:

  • key 使用强引用:引用的ThreadLocal的对象可以被回收,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
  • key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

相关标签: Java JDK java