ThreadLocal为什么会导致内存泄露

148 阅读3分钟

1. 什么是内存泄露

内存泄露就是指一个不再被程序使用的对象一直占据在内存中,无法被垃圾回收器回收。

Java 中的内存泄露的情况: 长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短室命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是 Java 中内存泄露的发室场景 ,通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是 java 中可能出现内存泄露的情况。

例如,缓存系统,我们加载了一个对象放在缓存中 (例如放在一个全局map对象中),然后一直不再使用它,这个对象一值被缓存引用, 但却不再被便用。

2.ThreadLocal为什么会导致内存泄露

我们先来看看TreadLocal的引用示意图哈:

a.awebp

TheadLocal对象我们通常会定义成静态变量,所以ThreadLocal对象不会被回收,和应用程序的生命周期一样。

当一个请求过来时,Tomcat会分配一个线程来处理请求,请求结束后回收线程,所以线程对象不回被回收,导致ThreadLocalMap也无法回收,进而导致Object业务对象无法回收。每次请求都会导致一个业务对象无法回收。业务对象无法被回收的最大个数等于Tomcat最大线程数。

4. Key为什么要设计成弱引用呢?强引用不行?

使用弱引用可以避免Key的内存泄露,使用threadlocal.remove()方法可以避免value内存泄露。

下面我们分情况讨论:

  • 如果Key使用强引用:当ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用的话,如果没有手动删除,ThreadLocal就不会被回收,会出现Entry的内存泄漏问题。
  • 如果Key使用弱引用:当ThreadLocal的对象被回收了,因为ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value则在下一次ThreadLocalMap调用remove的时候会被清除。

5.ThreadLocal的应用场景和使用注意点

使用场景:

  • 使用日期工具类,当用到SimpleDateFormat,使用ThreadLocal保证线性安全
  • 全局存储用户信息(用户信息存入ThreadLocal,那么当前线程在任何地方需要时,都可以使用)
  • 保证同一个线程,获取的数据库连接Connection是同一个,使用ThreadLocal来解决线程安全的问题

6.ThreadLocal内存泄露Demo

public class ThreadLocalTestDemo {
​
    private static ThreadLocal<TianLuoClass> tianLuoThreadLocal = new ThreadLocal<>();
​
​
    public static void main(String[] args) throws InterruptedException {
        //-Xms5m -Xmx5m
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>());
​
        for (int i = 0; i < 10; ++i) {
            //不要使用submit
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("创建对象:");
                    TianLuoClass tianLuoClass = new TianLuoClass();
                    tianLuoThreadLocal.set(tianLuoClass);
                    tianLuoClass = null; //将对象设置为 null,表示此对象不在使用了
                    // tianLuoThreadLocal.remove();
                }
            });
            Thread.sleep(1000);
        }
    }
​
    static class TianLuoClass {
        // 1M
        private byte[] bytes = new byte[1 * 1024 * 1024];
    }
}