ThreadLocal - 内存泄漏

需求

看到有人说:Threadlocal的错误使用,会导致内存溢出,所以来分析一下源码。

结论

简而言之,就是通过 ThreadLocal 添加的 value 与 Thread 是强引用关系,在 Thread 无法正常销毁的场景下,有内存泄漏的隐患,例如:线程池。

解决方案:代码执行完成之后,要注意调用 remove() 函数清除数据。

反例

import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

class Test{
	

    /**
     * 代码功能:通过 ThreadLocal 设值,使用结束之后,未清除数据,后续所有的循环都能读到这个值。
     *
     * 代码分析:正常的业务中,运行结束之后,线程被回收,处于闲置状态,但是这个值依然保存在内存中,这显然不合适。
     *
     * @param args -
     */
    public static void main(String[] args) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(1);
        executor.setMaxPoolSize(1);
        executor.setThreadNamePrefix("test-");
        executor.initialize();

        ThreadLocal<String> local = new ThreadLocal<>();

        int cnt = 100;
        while (cnt-- > 0) {
            int finalCnt = cnt;
            executor.execute(() -> {
                System.out.println(finalCnt);
                if (local.get() != null) {
                    System.out.println("已经存在值:" + local.get());
                } else {
                    local.set("cnt: " + finalCnt);
                }
            });
        }
    }
}

分析

只需要关注 ThreadLocal 的 set() 函数即可,

观察 set() 函数,就能发现这样一条引用链:Thread > ThreadLocalMap > Entry > value。

线程是特殊的对象,它是可回收的,还会出现永久锁死的情况,因此需要关注内存泄漏的问题。

其它注意点:

  1. Entry 对象虽然继承自 WeakReference(弱引用),但是只与 key 值是弱引用关系,与 value 值是强引用关系;
  2. 引用链不包含 ThreadLocal 本身,ThreadLocal 只是一个用于存取值的工具;
  3. 因为引用链不包含 ThreadLocal,因此将 ThreadLocal 声明成 static 变量,也是可以的;
/**
 * Thread 关键源码
 */
public class Thread implements Runnable {

	// 每一个线程内部都有一个 Map,很明显存在隐患:如果未清理,数据会越堆越多。
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

/**
 * ThreadLocal 关键源码
 */
public class ThreadLocal<T> {

	// 设值函数,过程不涉及成员变量,因此 ThreadLocal 本身没有泄漏的风险
    public void set(T value) {
		// Thread 与 ThreadLocalMap 之间是强引用关系
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

	// ThreadLocalMap 关键代码
    static class ThreadLocalMap {
		
		// 拥有 Entry 的引用
		private Entry[] table;

        static class Entry extends WeakReference<ThreadLocal<?>> {
            // 注意:这里是强引用关系
            Object value;

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

posted on   疯狂的妞妞  阅读(169)  评论(0编辑  收藏  举报

(评论功能已被禁用)
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示