文章目录
- 前置知识
- 为什么会产生内存泄漏问题?
- 如何解决内存泄露问题?
- 为什么要使用弱引用?
前置知识
讲解ThreadLocal的内存泄漏问题之前,首先得先知道什么是内存泄漏。
Memory overflow:内存溢出,没有足够的内存提供申请者使用。
Memory leak:内存泄漏是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。内存泄漏的堆积终将导致内存溢出。
一文讲清强软弱虚四种引用类型
为什么会产生内存泄漏问题?
下面是大致的结构图,在栈中存放当前线程对象的引用以及ThreadLocal的引用。
他们指向了堆中的对象实例。
我们知道ThreadLocalMap(下文统称为Map)中的Entry的key使用的是ThreadLocal对象。
那么如果这里是一个强引用,那么如果我们设置ThreadLocal的引用为null,那么此时由于Map中的的Entry强引用了ThreadLocal作为键,因此此时会造成ThreadLocal无法被回收,在没有手动删除这个Entry或者当前线程仍然在运行的情况下,始终有强引用链 Thread的引用-》Thread对象-》Map-》Entry-》Key(ThreadLocal)和value-》内存。 此时就造成了内存泄漏。
因此如果ThreadLocalMap中的key使用了强引用,是完全无法避免内存泄漏的。
那么我们知道,ThreadLocalMap中的Entry继承了一个弱引用。也就是情况如下:
和上面不同的地方在于,在ThreadLocal的引用被回收之后,没有任何一个强引用指向ThreadLocal对象了,那么此时ThreadLocal就会被gc回收。因此此时Entry中的key为null。
但是,在没有手动删除这个Entry以及当前线程依旧运行的情况下,还是存在强引用链
Thread的引用-》Thread对象-》Map-》Entry-》Key(null)和value-》内存,value依旧不会被回收,而且这块value永远不会被访问到了,导致内存泄漏。
也就是说即使使用了弱引用,还是会导致内存泄漏。
如何解决内存泄露问题?
你可能很疑惑,都已经使用了弱引用了,为什么还是内存泄漏啊?
因为弱引用本身就不是为了解决这个问题而使用的。那么内存泄漏的真正原因是说明?
其实无非两个原因:
- Entry没有被删除
- 外部线程依旧在运行
第一点好理解,只要在使用完毕ThreadLocal之后调用remove方法删除Entry就可以避免内存泄漏。
第二点比较复杂,由于ThreadLocalMap是Thread的一个属性,并且被当前线程所引用,他的生命周期和Thread一样长,那么在使用完毕ThreadLocal的时候,Thread也随之结束,那么ThreadLocalMap自然也会被gc回收,也就从根源上避免了内存泄漏问题。
综上,内存泄露问题的根源是:由于ThreadLocalMap的生命周期和Thread一样长,如果没有手动删除对应数据,而只是把ThreadLocal设定为空,就会导致内存泄漏。
为什么要使用弱引用?
根据刚才的分析我们知道了:无论ThreadLocalMap中的key使用哪种类型引用都无法完全避免内存泄漏,跟使用弱引用没有关系。
要避免内存泄漏有两种方式:
- 使用完ThreadLocal,调用其remove方法删除对应的Entry
- 使用完ThreadLocal,当前Thread也随之运行结束
相对第一种方式,第二种方式显然更不好控制,特别是使用线程池的时候,线程结束是不会销毁的。
也就是说,只要记得在使用完ThreadLocal及时的调用remove,无论key是强引用还是弱引用都不会有问题。
那么为什么key要用弱引用呢?
事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null (也即是ThreadLocal为null )进行判断,如果为null的话,那么是会对value置为null的。
这就意味着使用完ThreadLocal , 当前线程依然运行的前提下,就算忘记调用remove方法,弱引用比强引可以多一层保障∶弱引用的ThreadLocal会被回收,对应的value在下一次ThreadLocalMap调用set,get,remove中的任一方法的时候会被清除,从而避免内存泄漏。