ThreadLocal 内存泄漏问题
ThreadLocal 内存泄漏问题
这是一道面试中可以深挖的问题, 涉及到了 gc, 各种引用, 线程池, ThreadLocal 等知识点.
结论
使用不当可能导致内存泄漏, 但及时 remove 就可以避免.
导致内存泄漏的原因
Thread 对象有一个类似于哈希表的属性叫 threadLocals. 这里面存储着该线程私有的变量, 起到了和其他线程隔离数据的作用.
虽然这个哈希表的 Entry 结构和 HashMap有所不同, 但是我们还是可以用哈希表的思想去理解: 这个哈希表的 key 是 ThreadLocal 对象的弱引用, value 是我们储存的数据.
这样, 同一个 ThreadLocal 对象, 在不同线程的 threadLocals 中对应着不同的 value, 实现了数据隔离.
这里最奇怪的是, 为什么 key 是弱引用而不是 ThreadLocal 本身?
想象这样一个场景: 在一个 web 服务器中, 用于处理请求的线程往往都是池化的, 它们的生命周期会非常久, 如果一直有新的 ThreadLocal 对象被添加到了线程的 threadLocals 中, 而且没有 remove. 那么线程的 threadLocals 占用的内存就会越来越大. 而且由于哈希表中始终持有这个 ThreadLocal 对象的强引用, 这些键值对永远都不会被回收, 直到 OOM.
如果我们把 key 设为 ThreadLocal 对象的弱引用, 当一个 ThreadLocal 对象在其他地方没有强引用时, 它就会被 gc 回收. 当 ThreadLocalMap 执行删改查及扩容时会自动清理这些 ThreadLocal 对应的键值对. (增的时候会复用这种键值对)
ThreadLocal 的设计者已经为我们规避了这种内存泄露的情况了, 但是我们如果使用不当仍然有可能出现内存泄漏:
我们一直在某个地方持有 ThreadLocal 对象的强引用, 而且没有在使用完以后 remove. 这样 gc 无法回收 ThreadLocal 对象, 而且线程的 threadLocals 会一直膨胀.
下面这段代码可以很快地触发 oom. 如果你去掉threadLocals.add(threadLocal);
或者使用完及时 remove, 就不会 oom
// jvm 参数: -Xms20m -Xmx20m 限制堆内存为 20mb, 快速触发 oom
public class ThreadLocalMemoryLeakTest {
public static void main(String[] args) {
List<ThreadLocal> threadLocals = new ArrayList<>();
while (true) {
ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();
threadLocal.set(new byte[1024 * 1024]);
// 一直持有 ThreadLocal 对象的强引用
threadLocals.add(threadLocal);
}
}
}
避免内存泄漏的方式
除非你的线程不久后就要销毁, 否则使用 ThreadLocal 时一定要及时 remove(使用 finally 是个好办法),