ThreadLocal内存泄漏分析

目录

  1. ThreadLocal.set方法源码分析
  2. ThreadLocalMap.set方法源码分析
  3. 内存泄漏分析

set方法源码分析

  1. 获取当前线程的threadLocals(ThreadLocalMap)
  2. 如果ThreadLocalMap为空则创建,否则设置value
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

为什么threadLocals是Map结构?

要解答这个问题需要看一下createMap部分。没错,key是当前ThreadLocal实例,一个线程是可以由多个ThreadLocal实例的,所以使用map结构

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap.set方法源码分析

根据key,value创建Entry,将entry放入key的hash值对应tab表对应下标处

Entry结构实现

可以看到entry的key是对ThreadLocal的弱引用。回忆下,弱引用会在下一次gc时被回收掉。引用API文档中的解释:https://docs.oracle.com/javase/7/docs/api/java/lang/ref/WeakReference.html

Weak reference objects, which do not prevent their referents from being made finalizable, finalized, and then reclaimed. Weak references are most often used to implement canonicalizing mappings.
弱引用对象,弱引用并不会阻止它们引用指向的对象变得可终结、终结,然后被回收。弱引用通常用于实现规范化映射。
Suppose that the garbage collector determines at a certain point in time that an object is weakly reachable. At that time it will atomically clear all weak references to that object and all weak references to any other weakly-reachable objects from which that object is reachable through a chain of strong and soft references. At the same time it will declare all of the formerly weakly-reachable objects to be finalizable. At the same time or at some later time it will enqueue those newly-cleared weak references that are registered with reference queues.

假定垃圾回收器在某刻确定对象是弱可达的。此时,它将原子性的清除弱可达对象的所有弱引用,以及所以其他弱可达对象的所有弱引用,从这些通过强引用和软引用链可访问到的弱可达对象。同时它将声明所有以前的弱可达对象都是为可终结的。同时或以后某个时候它将新清除的弱引用压入使用引用队列注册的弱引用的引用队列中。

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

在这里插入图片描述

内存泄漏分析

如果GC回收了Entry的key对ThreadLocal的弱引用。那么key为null,也就永远不可能再访问到50MB的value。此时便出现了泄漏。那么ThreadLocal既然使用了弱引用会不考虑该问题吗?那么我们来继续分析它的源码

set部分

可以看到set值时,如果发现存在老的entry的key为null会触发遍历tab表清楚掉所有key为null的entry

for (Entry e = tab[i];
     e != null;
     e = tab[i = nextIndex(i, len)]) {
    ThreadLocal<?> k = e.get();

    if (k == key) {
        e.value = value;
        return;
    }

    if (k == null) {
        replaceStaleEntry(key, value, i);
        return;
    }
}

get部分

get部分也会清楚entry的key为null的数据,但是前提是get的key未获取到时:getEntryAfterMiss

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

总结

ThreadLocal内存泄漏须要满足以下条件

  1. 线程没有终结
  2. GC回收掉了弱引用
  3. 没有再次调用ThreadLocal的set、get方法
  4. 没有手工清除数据,即没有调用remove方法
发布了91 篇原创文章 · 获赞 103 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/u010597819/article/details/105612741