并发编程原理与实战(三十五)ThreadLocal内存泄露问题剖析与典型使用场景举例

ThreadLocal 内存泄露的实例分析1 问题背景在 Tomcat 中,下面的代码都在 webapp 内,会导致 WebappClassLoaderWebappClassLoader 泄漏,无法被回收。 立即下载

上一篇分析了ThreadLocal的核心API,讲到ThreadLocal就绕不开内存泄露问题,本来就来分析ThreadLocal内存泄漏产生背景以及防护策略。

内存泄漏产生的原因

引用类型

什么是内存泄露?java内存泄漏是指程序中已动态分配的堆内存由于某种原因未能释放,造成内存浪费,最终可能导致程序性能下降或崩溃。java是具有垃圾回收机制的编程语言,内存泄漏通常表现为无意中持有无用对象的引用。

说到无引用的对象,这又得从java的引用开始说起了。Java从1.2版本开始引入了四种引用类型,它们按强度从强到弱依次为:‌强引用(Strong Reference)‌、‌软引用(Soft Reference)‌、‌弱引用(Weak Reference)‌ 和 ‌虚引用(Phantom Reference)‌。

(1)强引用‌是Java程序中最常见、默认的引用类型。当您使用new关键字创建对象时,所获得的引用就是强引用。

  • 特点:被强引用关联的对象永远不会被垃圾回收器回收,即使系统内存不足,JVM宁愿抛出OutOfMemoryError终止程序,也不会回收这些对象。
  • 回收条件:只有当强引用被显式地设置为null,或者超出其作用域时,对象才会变得可回收。

(2)软引用‌用来描述一些有用但非必需的对象。

  • 特点:在内存充足时,软引用对象不会被回收;只有当JVM认为内存不足时(在抛出OutOfMemoryError之前),才会回收这些对象。通过java.lang.ref.SoftReference类实现。

(3)弱引用‌的强度比软引用更弱。

  • 特点‌:‌无论当前内存是否充足,只要发生垃圾回收,弱引用对象就会被回收‌。通过java.lang.ref.WeakReference类实现。
  • 使用场景‌:主要用于‌防止内存泄漏‌。

(4)虚引用‌是最弱的一种引用关系,完全不会影响对象的生命周期。

  • 特点:无法通过虚引用获取对象实例(get()方法总是返回null),它唯一的用途是‌跟踪对象被垃圾回收的状态‌。通过java.lang.ref.PhantomReference类实现,必须与ReferenceQueue联合使用。

ThreadLocal内存泄露分析

前面我们已经知道ThreadLocal内部的哈希表ThreadLocalMap采用Entry数组存储数据,每个Entry使用弱引用指向ThreadLocal对象作为key,实际存储的变量值作为value。

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

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

了解了引用类型的相关概念以及作用后,这里用弱引用类WeakReference指向ThreadLocal,即ThreadLocal对象(Entry的key)设计为弱引用的目的是防止内存泄露,Entry继承弱引用类WeakReference主要用于创建具有弱引用特性的自定义引用类,也就是Entry类具有弱引用的特性,使得Entry对象的key在内存不足的时候可以被垃圾回收,但是Entry对象的value却是一个强引用类型。当key由于是弱引用会被垃圾回收变为null,对应的value因强引用依然存在且无法被访问,但系统长时间运行未及时清理ThreadLocal变量时,就可能导致内存泄漏问题。

内存泄漏示例分析

从前文我们已经知道,ThreadLocal的remove()方法可以从ThreadLocalMap中移除当前线程中此线程局部变量的值,如果程序没有调用该方法及时清除线程的局部变量,就有可能造成内存泄漏。下面我们通过一个例子来重现内存泄露问题

public class MemoryLeakDemo {
    static class LargeObject {
        // 申请内存空间1G
        private byte[] data = new byte[1024 * 1024 * 1024];
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(1);
        for (; ; ) {
            // 提交任务
            executor.submit(() -> {
                // 创建ThreadLocal变量并赋值
                ThreadLocal<LargeObject> threadLocal = new ThreadLocal<>();
                threadLocal.set(new LargeObject());
                // 清理threadLocal变量
                //threadLocal.remove();
            });

            Thread.sleep(1000);
            System.out.println("main thread running...");
        }
    }
}

上面的示例代码中,创建一个只有一个线程的线程池,每秒钟往线程池里面提交一个任务,任务就是创建ThreadLocal变量并赋值,每次赋值需要申请1G的内存空间。运行程序后,用JDK自带的Java进程信息输出工具和JVM统计监控工具jstat ,实时监控Java虚拟机的垃圾回收情况和堆内存使用状况。

C:\Program Files\Java\jdk-21\bin>jps -l
27264 org.jetbrains.jps.cmdline.Launcher
14424 com.demo.concurrent.MemoryLeakDemo
18728 jdk.jcmd/sun.tools.jps.Jps
20668 org.jetbrains.jps.cmdline.Launcher
9420 com.intellij.idea.Main

-l选项‌:输出‌主类或JAR文件的完整包名/路径‌。

#监控Java虚拟机
>jstat -gcutil [进程ID] 1000 

-gcutil:监控选项,表示显示垃圾回收统计信息
[进程ID]:目标Java进程的虚拟机会话ID
1000:采样间隔时间,单位为毫秒
默认会持续输出,直到手动停止

不执行remove()方法时,Java虚拟机的垃圾回收情况和堆内存使用状况如下:
在这里插入图片描述

然后执行remove()方法时Java虚拟机的垃圾回收情况和堆内存使用状况如下:
在这里插入图片描述

jstat命令内存使用百分比指标的详细含义:

指标含义正常范围建议
‌S0‌年轻代中第一个Survivor区已使用容量百分比0-100%
‌S1‌年轻代中第二个Survivor区已使用容量百分比0-100%
‌E‌年轻代中Eden区已使用容量百分比<90%
‌O‌老年代已使用容量百分比<85%
‌M‌元空间(Metaspace)已使用容量百分比<90%
‌CCS‌压缩类空间使用比例<90%

jstat命令垃圾回收统计指标

指标全称含义
‌YGC‌Young GC Count从程序启动到采样时年轻代GC次数
‌YGCT‌Young GC Time年轻代GC总耗时(秒)
‌FGC‌Full GC Count从程序启动到采样时Full GC次数
‌FGCT‌Full GC TimeFull GC总耗时(秒)
‌GCT‌GC Time垃圾回收总时间(秒)

当出现内存泄漏时,通常会有以下特征:

  • ‌老年代使用率(O) 持续上升,即使经过Full GC也不明显下降
  • ‌Full GC次数(FGC) 和 ‌Full GC时间(FGCT) 频繁增加
  • ‌年轻代GC次数(YGC) 异常频繁

而上面不执行remove()方法和执行remove()方法时垃圾回收统计指标最明显的差异是FGC、FGCT这个两个指标,不执行remove()方法时由于不断的内存申请,jvm垃圾回收的次数和时间明显增多,而执行remove()方法时由于自动清理线程局部变量则没有触发jvm垃圾回收。

内存泄漏防护策略

下面来了解下有哪些内存泄漏防护策略。

(1)显示调用remove()方法

在使用完ThreadLocal后,必须手动调用remove()方法来清理当前线程的ThreadLocalMap中的Entry
,推荐使用try-finally块确保清理操作一定会执行。

(2)使用static final修饰ThreadLocal变量。

将ThreadLocal变量声明为static final可以避免重复创建ThreadLocal实例。

(3)给ThreadLocal变量设置合理的初始值。

所以上面的例子,我们可以这样改造。

public class MemoryLeakDemo {
    //ThreadLocal 变量
    private static final ThreadLocal<LargeObject> threadLocal = ThreadLocal.withInitial(() -> new LargeObject(10));

    static class LargeObject {
        // 申请内存空间
        byte[] data;

        protected LargeObject(int size) {
            data = new byte[size];
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(1);
        for (int i = 0; i < 20; i++) {
            // 提交任务
            executor.submit(() -> {
                try {
                    // 创建ThreadLocal变量并赋值
                    threadLocal.set(new LargeObject(10));
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 清理threadLocal变量
                    threadLocal.remove();
                }
            });
            Thread.sleep(1000);
            System.out.println("main thread running...");
        }
        executor.shutdown();
        System.out.println("executor shutdown...");
    }
}

ThreadLocal典型应用场景举例

ThreadLocal常见的典型应用场景,比如对线程不安全工具类‌的封装(如对SimpleDateFormat的线程安全封装),Web框架中的用户身份、语言环境等上下文传递等。下面以对SimpleDateFormat的线程安全封装为例说明ThreadLocal的应用。

我们知道SimpleDateFormat 不是线程安全的,主要原因是其内部使用了一个共享的 Calendar 对象。当多个线程同时调用 format 或 parse 方法时,一个线程可能会覆盖另一个线程设置的日期值,从而导致错误结果或抛出 NumberFormatException。

以下代码模拟了多个线程同时使用同一个 SimpleDateFormat 实例进行日期格式化操作:

public class SimpleDateFormatUnsafeExample {
    //共享SimpleDateFormat实例
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        // 提交10个格式化任务
        for (int i = 0; i < 10; i++) {
            final int index = i;
            new Thread(() -> {
                try {
                    Date date = new Date(System.currentTimeMillis() + index * 1000);
                    // 共享实例,线程不安全
                    String result = sdf.format(date);
                    System.out.println("Thread " + Thread.currentThread().getName() + ": " + date.getTime() + " -> " + result);
                } catch (Exception e) {
                    System.err.println("Thread " + Thread.currentThread().getName() + " error: " + e.getMessage());
                }
            }).start();
        }
    }
}

运行结果:

Thread Thread-0: 1762161139684 -> 2025-11-03 17:12:19
Thread Thread-8: 1762161147686 -> 2025-11-03 17:12:27
Thread Thread-9: 1762161148686 -> 2025-11-03 17:12:28
Thread Thread-7: 1762161146686 -> 2025-11-03 17:12:26
Thread Thread-6: 1762161145685 -> 2025-11-03 17:12:25
Thread Thread-5: 1762161144685 -> 2025-11-03 17:12:24
Thread Thread-4: 1762161143685 -> 2025-11-03 17:12:21
Thread Thread-2: 1762161141684 -> 2025-11-03 17:12:21
Thread Thread-3: 1762161142684 -> 2025-11-03 17:12:22
Thread Thread-1: 1762161140684 -> 2025-11-03 17:12:20

从运行结果可以看出,Thread-4和Thread-2传入不同的时间戳,但是却得到了相同的格式化结果,这就是共享SimpleDateFormat对象线程不安全导致的。那么如何改进这问题呢?有以下几种方案:

(1)推荐使用 Java 8 的 DateTimeFormatter。

(2)每次使用时都创建新的 SimpleDateFormat 实例,避免共享。

(3)通过对共享的 SimpleDateFormat 实例加锁,确保同一时刻只有一个线程使用它。

(4)采用ThreadLocal存储SimpleDateFormat 实例,为每个线程提供独立的 SimpleDateFormat 实例,既保证了线程安全,又避免了频繁创建对象。

以采用ThreadLocal存储SimpleDateFormat 实例,对上述代码进行改造:

public class SimpleDateFormatUnsafeExample {
    //线程SimpleDateFormat实例
    private static final ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal =
            ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public static void main(String[] args) {
        // 提交10个格式化任务
        for (int i = 0; i < 10; i++) {
            final int index = i;
            new Thread(() -> {
                try {
                    Date date = new Date(System.currentTimeMillis() + index * 1000);
                    // 线程SimpleDateFormat实例,线程安全
                    String result = simpleDateFormatThreadLocal.get().format(date);
                    System.out.println("Thread " + Thread.currentThread().getName() + ": " + date.getTime() + " -> " + result);
                } catch (Exception e) {
                    System.err.println("Thread " + Thread.currentThread().getName() + " error: " + e.getMessage());
                }finally {
                    //内存清理
                    simpleDateFormatThreadLocal.remove();
                }
            }).start();
        }
    }
}

运行结果:

Thread Thread-6: 1762161768972 -> 2025-11-03 17:22:48
Thread Thread-0: 1762161762971 -> 2025-11-03 17:22:42
Thread Thread-5: 1762161767972 -> 2025-11-03 17:22:47
Thread Thread-1: 1762161763972 -> 2025-11-03 17:22:43
Thread Thread-7: 1762161769973 -> 2025-11-03 17:22:49
Thread Thread-3: 1762161765972 -> 2025-11-03 17:22:45
Thread Thread-9: 1762161771973 -> 2025-11-03 17:22:51
Thread Thread-8: 1762161770973 -> 2025-11-03 17:22:50
Thread Thread-4: 1762161766972 -> 2025-11-03 17:22:46
Thread Thread-2: 1762161764972 -> 2025-11-03 17:22:44

从运行结果可以看出,不再出现不同的时间戳,但是却得到了相同的格式化结果的情况。

MFC C++ 中开发 PLC 通信客户端 OPC UA | 工业标准化通信 | `open62541`(开源OPC UA库)或 `UA-.NET` 封装 | 跨平台、安全可靠,支持复杂数据类型 |- 连接句柄(`modbus_t*`/`UA_Client*`/`TS7Client*`)不可跨线程共享,需为每个线程创建独立连接。Modbus 是工业常用协议,分 **RTU**(串口)和 **TCP**(以太网)两种模式,以下以 Modbus TCP 为例。s=Tag`),复杂类型需解析扩展对象。 阅读详情

相关推荐

RAG——RAG 索引(文档解析技术)

本文介绍了RAG系统中文档解析的关键技术,重点分析了如何通过LangChain的Document Loaders处理多种格式文档。文章指出高质量文档解析对RAG系统效果的决定性作用,并详细讲解了PDF、TXT、Word等常见格式的解析方法。作者提供了代码示例和Gitee开源项目链接,特别强调了PDF解析的深入方案,同时介绍了相关依赖库的安装配置。文章还展示了如何定义通用文档加载方法,为开发者提供了实用的技术参考。

小志的博客 751

系列六、ThreadLocal内存泄漏案例

【代码】系列六、ThreadLocal内存泄露案例。

仰望星空,脚踏实地!安全感是自己给的,努力才是一辈子的安全感。 737

2023东华大学OJ机试题22-70

东华大学计算机考研复试机试22 约瑟夫环223、整除的尾数24、回文质数25、汽水瓶26、阶乘最后的非0位27、算菜价28、 水果价格29、求奇数的乘积30、求最晚和最早日期31、素数32、计算e33 数字之和34 繁殖问题35 奇妙的数字36 整除的尾数37 黑色星期五38 树39 约瑟夫环40 最大与最小41 环42 求数列项43 最高频率44 三艘船45 回文数46 特殊四位数47 最大值48 数列149 修理牛棚50 按要求输出序列51 部落人乘法52 序列53 双重回文数54 等差数列55 人见人

m0_49263811的博客 2597

ThreadLocal原理,内存泄露到底是怎么回事,干货满满

ThreadLocal它提供了线程局部变量的功能。3. 当通过ThreadLocal对象调用get()方法获取变量值时,实际上是获取当前线程的ThreadLocalMap,再以以当前的ThreadLocal对象为键获取对应的值。通过这种方式,ThreadLocal实现了线程间的数据隔离,每个线程都可以独立地访问和修改自己的局部变量值,而不会影响其他线程的变量。这在多线程编程中非常有用,可以避免线程间的数据竞争和冲突,提高程序的并发性能和可靠性。

m0_73311735的博客 1905

最邻近点对问题(Closest-Pair Problem):二维的分治解法详解

低维时,最邻近点对问题可以利用分治求解。但是网络上相关资料良莠不齐,对于3维甚至难有有价值的参考。最近恰巧梳理了123维最邻近点对问题的相关知识,故在此分享。这是该系列的第二篇,介绍二维情况的分治解法。

Carl_Rabbit的博客 1万+

别再手动配IP了!用NI-USRP Configuration Utility快速搞定USRP 2954与LabVIEW连接

本文详细介绍了如何使用NI-USRP Configuration Utility快速配置USRP 2954与LabVIEW的连接,解决传统手动IP配置的繁琐问题。通过图形化工具自动完成设备发现、IP设置和连接验证,大幅提升软件无线电实验效率,特别适合信号收发应用场景。

weixin_27102543的博客 292

JAVA系列---内存泄露示例

这绝大概率就是内存泄漏了啊。但是在生产上的内存走势图完全看不出来这个趋势,我前面说了,主要因为 GC 情况的数据只会保留一周时间,所以就算把整个图放出来也不是那么直观。我再带着你看看另外一个视角,这是我真正定位到问题的视角。就是分析内存 Dump 文件。分析内存 Dump 文件的工具以及相关的文章非常的多,我就不赘述了,你随便找个工具玩一玩就行。我这里主要是分享一个思路,所以就直接使用 idea 里面的 Profiler 插件了,方便。

lipviolet的博客 1308

java写一个多人线上聊天室

课程作业谔谔

weixin_51614848的博客 1135

ThreadLocal内存泄漏案例分析实战

用代码实战,彻底搞清楚ThreadLocal发生内存泄漏的情况。很多文章讲的模棱两可,在和群友的沟通中,基本弄清楚了ThreadLocal到底是什么回事,解决大多数文章都无法把知识点和实际使用结合起来讲。 先写个小例子 /** * 测试threadLocal内存泄漏 * 01:固定6个线程,每个线程持有一个变量 * 按理来说会有 6 * 5 = 30M内存无法回收,其余的在set方法中覆盖了。 */ public class ThreadLocalOutOfMemoryTest { s

蝉之洞 1075

基于Simulink的新能源汽车热管理系统设计与仿真详细介绍

新能源汽车(如纯电动汽车和混合动力汽车)的热管理系统是确保电池、电机和电控系统高效运行的关键。通过合理设计热管理系统,可以提高车辆的续航里程、延长电池寿命并提升整体性能。以下是如何在 MATLAB 和 Simulink 中设计并仿真一个新能源汽车热管理系统的详细步骤。通过上述步骤,我们成功设计并实现了基于 Simulink 的新能源汽车热管理系统。该系统能够根据目标温度和实际反馈,通过 PID 控制器或模糊控制器调整冷却液流量、风扇转速等参数,实现对电池、电机和车内的精确温度控制。

xiaoheshang_123的博客 383

HBase学习二:RegionServer详解

RegionServer是HBase系统中最核心的组件,主要负责用户数据写入、读取等基础操作。RegionServer组件实际上是一个综合体系,包含多个各司其职的核心模块:HLog、MemStore、HFile以及BlockCache。一个RegionServer由一个(或多个)HLog、一个BlockCache以及多个Region组成。其中,HLog用来保证数据写入的可靠性;BlockCache可以将数据块缓存在内存中以提升数据读取性能;

qwerty1372431588的博客 747

ThreadLocal的内存泄露问题

在上述所说的问题中,即使ThreadLocalMap中存在key为null的Entry,但是该Entry的value值并不会因为GC而被回收(value存本身就存着一个强引用的对象),所以就导致了该对象不会被回收掉而出现了内存泄露。假设如果存储的强引用,我们断开ThreadLocal Reference —> ThreadLocal的引用,会发现key强引用了ThreadLocal,导致该对象永远无法被GC。学习的过程中想到了一个问题,弱引用会不会导致运行过程中GC清除key,导致找不到对应的value?

sunao1106的博客 4759

国科大学习资料--VLSI测试与可测试设计--2020期末考试试题

国科大学习资料–VLSI测试与可测试设计–2020期末考试试题 国科大学习资料–VLSI测试与可测试设计–2020期末考试试题 国科大学习资料–VLSI测试与可测试设计–2020期末考试试题 一、概念题 1简述可测试性测度与两个主要的测度指标的含义; 2以March C-算法为例,说明March算法的构成,以及March C-算法的故障覆盖率; 3“效果-原因”诊断法和“原因效果诊断法”的不同; 4列举四条扫描设计时出现的问 题及解决办法; 5最少写出1149.1与1500的五点不同; = = = = =

无名何许人的博客 1150

【Java 开发日记】我们来说说 ThreadLocal 的原理,使用场景及内存泄漏问题

我是小假 期待与你的下一次相遇 ~

纵有疾风起 人生不言弃 3821

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

ThreadLocal原理剖析

ThreadLocal知识汇总,从源码核心方法分析入手,剖析哈希算法、黄金分割数核心算法,分析探测式清理、启发式清理两种内存清理方式,也分析了ThreadLocal的缺点、场景问题汇总、最后给出实战应用场景。

大摩羯先生 3809

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

深度剖析 ThreadLocal 内存泄露问题及解决方案

提供了一种在多线程环境下保存线程私有变量的机制,它允许每个线程都拥有一份独立的变量副本,互不影响。这在某些场景下非常有用,比如实现线程安全的单例模式、跨层级传递数据等。提供了一种在多线程环境下保存线程私有变量的机制,它允许每个线程都拥有一份独立的变量副本,互不影响。这在某些场景下非常有用,比如实现线程安全的单例模式、跨层级传递数据等。是一个强大的多线程编程工具,但在使用时需要格外小心,以避免引发内存泄露等问题。通过及时清理、使用弱引用以及注意线程池中的潜在问题,我们可以更安全地使用。

龚小帅的博客 3134

深入ThreadLocal原理剖析

分享一下最近看的ThreadLocal的源码的一些体会。

ATFWUS的博客 5676

ThreadLocal的原理剖析和使用场景

ThreadLocal 主要是做数据隔离,它是线程的局部变量, 是每一个线程所单独持有的,其他线程不能对其进行访问,相对隔离的。当使用ThreadLocal维护变量的时候为每一个使用该变量的线程提供一个独立的变量副本,即每个线程内部都会有一个该变量,这样同时多个线程访问该变量并不会彼此相互影响,因此他们使用的都是自己从内存中拷贝过来的变量的副本,这样就不存在线程安全问题,也不会影响程序的执行性能。

程序员云帆哥的博客 812

ThreadLocal的使用场景及实现原理

1. 什么是ThreadLocal? 线程局部变量(通常,ThreadLocal变量是private static修饰的,此时ThreadLocal变量相当于成为了线程内部的全局变量) 2. 使用场景 变量在线程内部共享,线程间无关 再具体点,可以分为两类: 单例的对象中static属性,线程内共享,线程间无关; 工具类属性,线程内共享,线程间无关。 为什么这么说呢?下面看4个问题:...

dhu86380的博客 836

深入理解 ThreadLocal:原理、实战与优化指南

生命周期管理必须配套使用 try-finally 清理资源线程池环境必须显式调用 remove()避免在静态字段中存储大对象设计规范// 推荐封装工具类性能优化对高频访问的 ThreadLocal 使用 @Contended 避免伪共享大量使用场景考虑使用 FastThreadLocal(Netty 实现)定期审计内存使用情况。

时小雨的专栏 959

java面试:了解ThreadLocal吗?ThreadLocal的内存泄露问题发生的原因是什么?

ThreadLocal 是 Java 中一个特殊的类,它会为每个线程提供局部变量,这些变量与普通变量的区别在于,每个访问该变量的线程都有自己独立初始化的副本,互不干扰。通过设置独立变量这种方式来实现线程之间的数据隔离来保证互斥性。通过以上分析,我们得知了无论如何ThreadLocal都会造成内存泄漏的情况,key为强引用就会导致ThreadLocal的内存泄漏,而key为弱引用下会导致强引用的Value产生内存泄漏。

2301_80939853的博客 1157

分析Threadlocal内部实现原理,并解决Threadlocal的ThreadLocalMap的hash冲突与内存泄露

前言 ThreadLocal 的经典使用场景是数据库连接、session 管理、多线程等…… 比如在Spring中,发挥着巨大的作用,在管理Request作用域中的Bean、事务管理、任务调度、AOP等模块都不同程度使用了ThreadLocal 。 Spring中绝大部分Bean,都可以声明成Singleton作用域,采用ThreadLocal进行封装,因此有状态的Bean,就能够以singleton的方式,在多线程中正常工作。 知道Threadlocal怎么用,但是不知道为什么要这样用?底层原理是.

阿啄debugIT 2212

从源码角度分析导致 JVM 内存泄露的 ThreadLocal

摘要: ThreadLocal用于实现线程局部变量,解决多线程环境下变量共享问题。在Spring事务管理中,ThreadLocal确保同一线程内的数据库连接保持一致,避免事务失效。其核心原理是为每个线程维护独立的变量副本,通过ThreadLocalMap存储,键为ThreadLocal实例,值为用户数据。ThreadLocalMap采用线性探测法解决哈希冲突。 ThreadLocal可能引发内存泄漏问题,主要因ThreadLocalMap的键为弱引用,但值仍为强引用。若线程长期存活,可能导致值无法回收。建议

m0_60963435的博客 1122

深入剖析大厂经典面试题之ThreadLocal原理(涉及斐波拉契散列、线性探测、扩容以及内存泄露问题)

1、简介 ThreadLocal也称线程变量,它是一个以ThreadLocal对象为键、任意对象为值的存储结构(ThreadLocal中ThreadLocalMap的Entry结构),这个结构会被附带在线程上,以此来做线程数据的隔离。ThreadLocal是维持线程的封闭性的一种规范,它提供set()/get()等方法维护和访问线程中存储的私有副本,ThreadLocal通常用于防止对可变的单实例变量或者全局变量进行共享。 ThreadLocal和synchronized两者经常会被拿出来一起讨论,虽然二者

Java框架、并发编程、分布式、微服务、Redis、HarmonyOS、中间件等技术 1266

深入解析ThreadLocal:原理、使用场景与内存泄漏防范

ThreadLocal是Java提供的线程本地变量机制,通过ThreadLocalMap为每个线程维护独立变量副本,实现线程隔离。其核心设计采用弱引用Key和强引用Value,每个Thread拥有专属ThreadLocalMap。典型应用场景包括上下文信息传递、数据库连接管理和日期格式化。

m0_66884848的博客 1395
上一篇: SpringBoot + iTextPDF + Acrobat 构建动态PDF表单的完整实践
下一篇: 开发避坑指南(68):Java Stream 统计List中元素某个字段值不同的次数实战指南
帧栈
博客等级 码龄2年 335粉丝 120原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帧栈

您的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值