likes
comments
collection
share

分析ThreadLocal内存泄漏问题

作者站长头像
站长
· 阅读数 8

前言

ThreadLocal的作用是提供线程内的局部变量,这种变量在线程生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量传递的复杂度。但是如果滥用ThreadLocal可能会导致内存泄漏,下面将围绕三个方面来分析ThreadLocal内存泄漏的问题。

  • ThreadLocal实现原理
  • ThreadLocal为什么会出现内存泄漏
  • ThreadLocal的最佳实践

ThreadLocal的实现原理

ThreadLocal的实现:

每一个Thread内部维护一个ThreadLocalMap映射表,这个映射表的keyThreadLocal实例本身,value是真正需要存储的Object

也就是说ThreadLocal本身不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value的。但是ThreadLocalMap是使用ThreadLocal的弱引用作为key的,弱引用的对象在GC时会被回收。

ThreadLocal为什么会内存泄漏

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统GC的时候,这个ThreadLocal会被回收,这样一来,ThreadLocalMap中会出现keynullEntry,这样就没有办法访问keynullEntryvalue,如果当前线程迟迟不结束,这些keynullEntryvalue就会存在一条强引用链,永远无法回收,造成内存泄漏。

其实ThreadLocal的设计中已经考虑到了这种情况,也加上了一些预防措施,在调用getsetremove方法的时候,会清楚线程ThreadLocalMap里所有keynullvalue

但是这些被动的预防措施并不能保证不会内存泄漏:

  • 使用staticThreadLocal,延长了ThreadLocal的生命周期,可能导致的内存泄漏。
  • 分配使用了ThreadLocal又不再调用get() ,set() ,remove() 方法,那么就会导致内存泄漏。

为什么使用弱引用

从表面上看内存泄漏的根本原因是使用了弱引用,那么为什么使用弱引用而不使用强引用呢?下面看看官方文档的说法:

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.

翻译过来就是:为了应对非常大和长时间的用途,哈希表使用弱引用。

下面我们分两种情况讨论:

  • key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
  • key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,getremove的时候会被清除。

比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用 ThreadLocal 不会内存泄漏,对应的 value 在下一次 ThreadLocalMap 调用 set , get , remove 的时候会被清除

因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

ThreadLocal最佳实践

综合上面的分析,我们可以理解ThreadLocal内存泄漏的前因后果,那么怎么避免内存泄漏呢?

  • 每次使用完ThreadLocal,都调用它的remove() 方法,清除数据。

在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就清理。