threadLocal内存泄漏原理分析

在使用线程池时,可以减少线程的重复创建,实现线程复用,但是再进行复用时如果使用了threadLocal可能会出现内存泄漏问题,下面一起学习为什么会出现内存泄漏,以及如何处理这类问题。

首先用代码展示同一个线程中的不同threadLocal实例共享同一个ThreadLocalMap。

public class WeakReferenceDemo {
    public static void main(String[] args) {
        Thread t = Thread.currentThread();
        ThreadLocal<String> tl = new ThreadLocal<>();
        tl.set("value1"); // 创建Entry: WeakRef(tl) → "value"
        ThreadLocal<String> t2 = new ThreadLocal<>();
        t2.set("value2");

        tl = null; // 没有外部强引用了
        System.gc(); // 触发GC   
    }
}

开启debug后,观察变量t中的threadLocal,可以发现每次调用set方法,threadLocal的size都会自增1,同时在tabel中新增一个Entry对象,新增的entry对象的referent类型是ThreadLocal,value是set方法中设置的值。

同一个线程共用threadLocal

在手动触发CG后,观察对应的entry,发现referent的值设置为null,而value保持不变。

执行GC后,弱引用的可以被回收,value仍然保留

如果在手动触发remove方法,就可以清除entry,这样就不会出现value无法清除的问题了

手动调用remove清除entry

线程执行过程中可能会出现异常,为了保证remove方法一定能够被执行,需要使用try-finally块确保remove被执行

使用finally保证remove方法正确执行

为了降低程序响应时间,通常会使用多线程,在threadLocal中经常会存一些重要的参数信息,便于后续程序的使用,但是ThreadLocal是线程隔离的,子线程无法访问父线程的ThreadLocal值,InheritableThreadLocal可以在创建子线程时传递值,但是在使用线程池时,由于线程复用,这个方法不适用,TTL可以解决线程池中上下文传递问题。 TTl工作的原理: TtlRunnable实现了Runnable接口,构造方法中传入Runnable,同时使用AtomicReference获取当前上下文的ThreadLocal,完成对原有Runnable的包装。 TtlRunnable重写了run方法,在调用原有Runnable方法前,取出之前保存的ThreadLocal, 调用replay()方法保存当前线程的上下文,同时重新设置threadLocal。finally块中执行restore() ,完成上下文的恢复。

Leave a Reply

Your email address will not be published. Required fields are marked *

赣ICP备2025059670号