一、ThreadLocal\Thread\ThreadLocalMap的内存模型
在研究过程中还思考了几个小问题:
1、线程结束后Thread对象会被释放吗?
Thread对象是线程状态的一个影像,线程状态的变化会反应到Thread.getState()方法上。
线程结束后会将Thread.state置为“TERMINATED”,但是Thread内存对象的回收还是和其它Java对象一样的规则。
线程在未结束之前,Thread对象会一直存在。
2、线程结束后Thread对象中的threadLocals一定会被释放吗?
是的,通过实验发现,线程结束后 Thread对象上的threadLocals一定会被置为null
二、内存是怎么泄漏的?
ThreadLocalMap.Entry持有ThreadLocal对象的弱引用,从而不会影响ThreadLocal对象的GC,对value的引用是强引用,只有当ThreadLocalMap的底层数组不再持有这个Entry时,该Entry才会被GC。也就是说,如果ThreadLocalMap如果不做特殊处理的话,那么即使是ThreadLocal实例都被GC了,但是它们对应的Entry依旧无法被GC,导致实际使用value对象也无法被GC,只是这些Entry引用的ThreadLocal变成null了,这个问题其实就是所谓的内存泄露。
1、Java线程退出时黄色区域会被GC回收,Thread对象按普通Java对象规则回收
2、考虑“线程池”的情况,Java线程不会退出,黄色区域就不会被回收
3、由于key与TheadLocal对象是“弱引用关系”,也就是如果ThreadLocal对象在没有“强引用”的情况下,下一次GC就会被回收,这时key的指向就会变成null,从而导致value无法被再次访问,但是又不释放内存(Entry节点没有被回收,value就不会被回收),这就导致了内存泄漏
不主动调用threadlocal的remove方法和threadlocal强引用丢失是造成 内存泄漏的主要原因
三、ThreadLocalMap做了哪些努力解决泄漏问题?
为了解决这个内存泄露问题(也就是key为null的那些Entry),ThreadLocalMap在扩容和线性探测等操作中,如果发现了持有的thread local已经被GC的Entry,那么就会将这个Entry置为null,使得这个Entry可以被GC,但是即使这样依然无法完全保证出现问题的 entry都能及时的被清理,这个残留的问题就是内存泄露问题。
这个内存泄露问题一般存在于线程池的场景下,因为如果线程本身被销毁,那么thread local map也会销毁,也不存在什么泄露问题。
为了解决这个内存泄露问题,我们在使用到threadlocal时,如果我们不再需要它时,那么就要手动进行remove操作,使得对应的Entry可以被GC。
同时ThreadLocalMap在多种场景中会主动清理坏掉的Entry(key为null的Entry),清理的场景与算法另起一篇文章说明
四、开发者应该采取哪些措施预防泄漏?
1)使用完毕后一定要调用
threadLocal.remove()方法释放线程上绑定的资源
2)threadLocal一般声明为static类型的全局变量,而不是局部变量
3)一般用来传递隐式公共参数,所以一般赋值时机是在第一个前置过滤器或者拦截器中,释放时机一般是在最后一个后置过滤器或者或者拦截器中