ThreadLocal内存泄漏问题解析

素颜马尾好姑娘i 2022-09-09 06:51 310阅读 0赞

关于内存泄漏这个问题需要从ThreadLocal的设计开始讲起

1. ThreadLocal对于脏entry的定义及处理

下面是remove方法

  1. private void remove(ThreadLocal<?> key) {
  2. Entry[] tab = table;
  3. int len = tab.length;
  4. int i = key.threadLocalHashCode & (len-1);
  5. for (Entry e = tab[i];
  6. e != null;
  7. e = tab[i = nextIndex(i, len)]) {
  8. if (e.get() == key) {
  9. e.clear();
  10. expungeStaleEntry(i);
  11. return;
  12. }
  13. }
  14. }

该方法逻辑很简单,通过往后环形查找到与指定 key 相同的 entry 后,先通过 clear 方法将 key 置为 null 后,使其转换为一个脏 entry,然后调用 expungeStaleEntry() 将其 value 置为 null,以便垃圾回收时能够清理,同时将 table[i]置为 null。从上面的分析可以知道ThreadLocal是通过remove方法将key置为null后进行清理。

2. key设置为弱引用的原因

下面是ThreadLocalMap.Entry的源码

  1. static class Entry extends WeakReference<ThreadLocal<?>> {
  2. Object value;
  3. Entry(ThreadLocal<?> k, Object v) {
  4. super(k);
  5. this.value = v;
  6. }
  7. }

可以看到这个哈希表以ThreadLocal对象为key,且这个引用是一个弱引用,那么这个地方为什么要设计成弱引用呢?下面用反证法来分析这个问题:

  • 假设这个引用是强引用,那么对于一个Threadlocal对象存在如下强引用链:Thread->ThreadLocal.ThreadLocalMap->Entry->key(ThreadLocal)、value。另外使用ThreadLocal的对象还持有当前ThreadLocal对象的强引用,假设现在使用ThreadLocal的对象被回收掉了,那么ThreadLocal对象还是被强引用,导致ThreadLocal对象无法被回收引发内存泄漏

解决上述问题的办法就是将Entry对key(ThreadLocal)的引用改为弱引用,这样在使用ThreadLocal的对象被回收后,ThreadLocal也就不存在被强引用,ThreadLocal对象被回收后该entry就变成了一个脏entry,而且在get、set、resize等方法中遇到脏entry都有调用expungeStaleEntry()进行处理。

那么为什么不把Entry或value也设计弱引用呢,这样在gc时直接就把Entry或value回收掉了

  • 由于使用对象一般只会持有key的引用,如果把Entry或value也设计弱引用,可能会导致Entry或value在使用中被回收
3. 内存泄漏原因

经过上面的分析我们知道了key设计为弱引用的原因以及ThreadLoacal对于脏entry的处理,可以看到Josh Bloch和Doug Lea为了防止内存泄漏做了很多努力,但是为什么还会内存泄漏呢?

  • 由于长时间没有调用get、set、resize、remove方法,导致脏entry无法及时被回收
4. 总结

为了防止内存泄漏,一定要在使用完ThreadLocal后及时调用remove方法进行清理,否则不但会发生内存泄漏,在线程池环境下线程复用还可能导致业务逻辑出现问题。

发表评论

表情:
评论列表 (有 0 条评论,310人围观)

还没有评论,来说两句吧...

相关阅读