ThreadLocal内存泄漏的核心原因在于其使用了Thread类中的ThreadLocal.ThreadLocalMap数据结构,而这个数据结构的生命周期与线程一致。理解这种泄漏首先需要了解ThreadLocal和ThreadLocalMap的工作原理。
ThreadLocal 工作原理:
-
ThreadLocal对象本身:当你创建一个ThreadLocal变量时,并不是为每个线程各自存储一个独立的值,而是提供一个访问该线程局部变量的键。
-
ThreadLocal.ThreadLocalMap:实际的值储存在每个线程的ThreadLocalMap中,这是一个特殊类型的映射,只有线程自己能访问。键是ThreadLocal对象的引用,值是线程所保存的实际对象。
内存泄漏发生的情境:
-
ThreadLocalMap的生命周期:ThreadLocalMap的生命周期与对应的线程相同,如果线程不死亡,那么ThreadLocalMap及其所有的Entry(即键值对)也不会被GC回收。
-
ThreadLocal对象消失:即使ThreadLocal对象已经没有任何强引用指向它,比如你的代码中已经没有任何地方引用这个ThreadLocal变量了,它的entry在ThreadLocalMap中仍然存在,这个entry的键是ThreadLocal对象的引用(虽然这是一个弱引用,但值是一个强引用),除非线程结束,否则这些entry(键值对)就不会被清理。
-
长生命周期的线程:在使用线程池等含有长生命周期线程的场景下,由于线程不会销毁,它的ThreadLocalMap也不会销毁,因此即便是没有任何外部引用的ThreadLocal对象,其在ThreadLocalMap中的entry也不会被GC自动回收。
由于ThreadLocalMap使用ThreadLocal的弱引用作为键,理论上当ThreadLocal实例被回收后,在ThreadLocalMap中相应的Entry的键会变成null,然而值却仍是一个强引用。如果线程继续存活,并且我们没有手动删除对应的值,这个值就会一直留在ThreadLocalMap中,导致内存泄漏。
防止内存泄漏的措施:
为了避免发生内存泄漏,开发者需要确保:
-
显式清理:最好的做法是在使用完ThreadLocal存储的对象之后,显式调用ThreadLocal.remove()方法来删除ThreadLocalMap中对应的Entry。
-
限制ThreadLocal对象的生命周期:确保ThreadLocal的生命周期不超过使用它的线程的生命周期。
-
谨慎使用线程池:线程池中的线程生命周期很长,它们会复用ThreadLocal变量,这可能导致内存泄漏问题。
综上所述,ThreadLocal造成内存泄漏的根源在于ThreadLocalMap的生命周期与线程一样长,如果不进行适当的清理操作,那么随着线程的持续运行,失去外部引用的ThreadLocal对象所占用的内存将不能被GC回收。
本篇文章来源于微信公众号: 互联网面试小帮手
微信扫描下方的二维码阅读本文

Comments NOTHING