Java ThreadLocal可能引起的内存泄漏

内存泄漏:内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未释放或者无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至

系统崩溃等严重后果。

ThreadLocal提供了线程独有的局部变量,可以在整个线程存活的过程中随机取用,极大地方便了一些逻辑的实现。常见的ThreadLocal方法有:

-存储单个线程上下文信息。比如存储id等

-使变量线程安全。变量既然成为了每个线程的局部变量,自然就不会存在并发问题。

-减少参数传递。比如做一个trace工具,能够输出工程从开始到结束的升格一次处理过程中的所有信息,从而方便debug。由于在工程各处随时取用,可放入ThreadLocal。

threadLocal里面使用了一个存在弱引用map,当释放掉threadlocal的强引用之后,map里面的value没有被回收,而这块value永远不会被访问到了,所以存在着内存泄漏。最好的做法就是调用threadlocal的remove方法。

在threadlocal的生命周期中,都存在这些引用。看下图:实线代表强引用,虚线代表弱引用


每个thread中都存在一个map,map的类型的ThreadLocal,ThreadLocalMap,Map中的key为一个threadlocal的实例,这个map的确使用了弱引用,不过弱引用只是针对key,每个key都弱引用指向threadlocal。当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal会被gc回收。但是,我们的value却不能被回收,因为存在一条从current thread连接过来的强引用。只有当前thread结束以后,current thread就不会存在栈中,强引用断开。Current Thread,Map,value将全部被gc回收。

所以得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄漏,但是在ThreadLocal设为null和线程结束这段时间不会被回收的,就发生我们认为的内存泄漏。其实这是一个对概念理解的不一致,也没什么好争论的。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄漏。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄漏。

PS.Java为了最小化减少内存泄漏的可能性和影响,在ThreadLocal的get,set的时候都会清除线程Map里所有key为null的value。所以最怕的情况就是threadlocal对象设null了,开始发生内存泄漏,然后使用线程池。这个线程结束,线程放回线程池中不销毁,这个线程一直不被使用,或者分配使用了又不再调用get,set方法,那么这个期间就会发生真正的内存泄漏。

总结

1、JVM利用设置ThreadLocalMap的Key为弱引用,来避免内存泄露。

2、JVM利用调用remove、get、set方法的时候,回收弱引用。

3、当ThreadLocal存储很多Key为null的Entry的时候,而不再去调用remove、get、set方法,那么将导致内存泄漏。

4、当使用static ThreadLocal的时候,延长ThreadLocal的生命周期,那也可能导致内存泄漏。因为,static变量在类未加载的时候,它就已经加载,当线程结束的时候,static变量不一定会回收。那么,比起普通成员变量使用的时候才加载,static的生命周期加长将更容易导致内存泄漏危机

你可能感兴趣的:(Java ThreadLocal可能引起的内存泄漏)