什么情况下ThreadLocal会导致内存泄漏(附案例)

如果没有看过ThreadLocal源码的,建议先去看看源码在来看这篇帖子,博主这篇帖子主要是先真实的模拟一下,什么情况下会导致内存泄漏。网上关于ThreadLocal的文档很多,就不过多追溯基础原理了。

每个Thread本身会维护一个threadLocals,这个对象就是一个ThreadLocal.ThreadLocalMap,而ThreadLocal维护一个ThreadLocalMap,这个Map的Key是一个ThreadLocal对象,value就是我们要存放的值。那在什么情况下ThreadLocal会发生内存泄漏呢,那就是在线程本身的生命周期很长,比如线程池(因为线程池创建的线程不会回收),下面是一个内存泄漏例子

jvm参数调整成这样的便于快速触发内存不足
 -XX:+PrintGCDetails  -XX:+PrintGCTimeStamps  -verbose:gc -Xmx50m
 
 public class Test {

    public static class TestThread extends Thread {
        @Override
        public void run() {
            ThreadLocal threadLocal = new ThreadLocal();
            int[] i = new int[1024 * 1024];
            threadLocal.set(i);
            /**
             * 如果去掉下面的注释就可以正常运行
             * 因为remove内部会将ThreadLocalMap中key为null的value清除
             * 这也就解释了为什么key是用弱引用修饰了,当key(ThreadLocal)被废弃的时候,可以回收掉当前ThreadLocalMap的Key
             */
            //threadLocal.remove();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor t = new ThreadPoolExecutor(4, 4, 0, TimeUnit.SECONDS, new ArrayBlockingQueue(1000), new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 100; i++) {
            Thread.sleep(200);
            t.execute(new TestThread());
        }
        t.shutdown();
    }
}
 

总结: 首先线程池中的参数可以保证最多有4个线程执行,并且这4个线程不会被回收,那么接下来看TestThread中的逻辑,每次创建的线程会创建一个ThreadLocal对象,这些对象中会绑定1M的数据,如果正常的情况下,我们都会认为run方法执行完成后,这个作用域中的对象都是可以被回收的,但是ThreadLocal恰恰不会,因为他跟线程有如下的引用关系Thread->ThreadLocalMap->Entry->value,相当于run方法执行完成后,Entry中的Key如果被Gc后,就会变成NULL,但是Value是存在强引用的,这一点可以在程序启动两秒之后,打断点到Run方法中查看threadLocal.set(i)方法,查看Entry中的对象Refrence都是NULL了。

最后如何避免呢,尽量不要在很长生命周期中用ThreadLocal,如果用的话,一定要调用remove方法。

你可能感兴趣的:(java相关)