ThreadLocal 内存泄漏问题

ThreadLocal 用于存储线程本地的变量,如果创建了一个 ThtreadLocal 变量,在多线程访问这个变量的时候,每个线程都会在自己线程的本地内存中创建一份变量的副本,从而起到线程隔离的作用。

Thread、ThreadLocal、ThreadLocalMap 之间的关系:

每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程所有的ThreadLocal对象及其对应的值.

ThreadLocalMap由一个个的Entry<key,value>对象构成,Entry继承自weakReference<ThreadLocal<?>>,一个EntryThreadLocal对象和Object构成。

  • Entry 的 key 是ThreadLocal对象,并且是一个弱引用。当指向key的强引用消失后,该key就会被垃圾收集器回收。

  • Entry 的 value 是对应的变量值,Object 对象。

当执行set方法时,ThreadLocal首先会获取当前线程 Thread 对象,然后获取当前线程的ThreadLocalMap对象,再以当前ThreadLocal对象为key,获取对应的 value。

由于每一条线程均含有各自私有的 ThreadLocalMap 对象,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也就无需使用同步机制来保证多条线程访问容器的互斥性。

ThreadLocal 使用场景:

1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传送,打破层次间的约束。

即如果一个User对象需要从Controller层传到Service层再传到Dao层,那么把User放在ThreadLocal中,每次使用ThreadLocal来进行获取即可

2、线程间数据隔离

3、进行事务操作,用于存储线程事务信息

4、数据库连接,Session会话管理

ThreadLocal 的内存泄漏问题:

这里假设将 ThreadLocal 定义为方法中的局部变量,那么当线程进入该方法的时候,就会将 ThreadLocal 的引用给加载到线程的栈 Stack 中。

如上图所示,在线程栈 Stack 中,有两个变量,ThreadLocalRef 和 CurrentThreadRef,分别指向了声明的局部变量 ThreadLocal ,以及当前执行的线程。

而 ThreadLocalMap 中的 key 是弱引用,当线程执行完该方法之后,Stack 线程栈中的 ThreadLocalRef 变量就会被弹出栈,因此 ThreadLocal 变量的强引用消失了,那么 ThreadLocal 变量只有 Entry 中的 key 对他引用,并且还是弱引用,因此这个 ThreadLocal 变量会被回收掉,导致 Entry 中的 key 为 null,而 value 还指向了对 Object 的强引用,因此 value 还一直存在。 ThreadLocalMap 变量中,由于 ThreadLocal 被回收了,无法通过 key 去访问到这个 value,导致这个 value 一直无法被回收,ThreadLocalMap 变量的生命周期是和当前线程的生命周期一样长的,只有在当前线程运行结束之后才会清除掉 value,因此会导致这个 value 一直停留在内存中,导致内存泄漏。

当然 JDK 的开发者想到了这个问题,在使用 set get remove 的时候,会对 key 为 null 的 value 进行清理,使得程序的稳定性提升。

当然,我们要保持良好的编程习惯,在线程对于 ThreadLocal 变量使用的代码块中,在代码块的末尾调用 remove 将 value 的空间释放,防止内存泄露。

ThearLocal 内存泄漏的根源是:

由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏。

ThreadLocal 正确的使用方法:

  • 每次使用完 ThreadLocal 都调用它的 remove() 方法清除数据

  • 将 ThreadLocal 变量定义成 private static final,这样就一直存在 ThreadLocal 的强引用,也能保证任何时候都能通过 ThreadLocal 的弱引用访问到 Entry 的 value 值,进而清除掉。

下面给出 ThreadLocal 的用法:

public class ThreadLocalExample {private static final ThreadLocal<Integer> counter = new ThreadLocal<Integer>() {@Overrideprotected Integer initialValue() {return 0;}};public static void main(String[] args) {Thread t1 = new Thread(() -> {try {int value = counter.get(); // 获取当前线程的副本值counter.set(value + 1); // 修改副本值System.out.println("Thread " + Thread.currentThread().getName() + " value: " + counter.get());} finally {// 手动移除counter.remove(); // 在线程结束时移除变量}});Thread t2 = new Thread(() -> {try {int value = counter.get();counter.set(value + 1);System.out.println("Thread " + Thread.currentThread().getName() + " value: " + counter.get());} finally {// 手动移除counter.remove();}});t1.start();t2.start();}
}

那么 ThreadLocal 为什么要将 key 设计为弱引用呢?

这里还要看一下具体是如何使用 ThreadLocal 了:

  • 如果定义 ThreadLocal 为局部变量,那么这个 ThreadLocal 对象就会放在堆中,如果不手动 remove() 的话,当线程执行完当前方法退出时,这个局部变量对 ThreadLocal 的强引用就消失了,只剩下 Thread.ThreadLocalMap 中的 key 对 ThreadLocal 的弱引用,因此会将 ThreadLocal 给回收掉,而 value 还存在强引用,而我们没有了 TheadLocal 的引用导致访问不到 value,导致 value 无法回收,因此 JDK 设计者在 ThreadLocal 还添加了清除。 ThreadLocalMap 中 key 为 null 的 value,避免内存泄漏,这是在设计时为了避免内存泄漏而采取的措施,而我们使用的时候要保持良好的编程规范,也要手动去 remove,避免内存泄露的发生。

  • 如果定义 ThreadLocal 为 private static final,那么这个 ThreadLocal 就会在常量池中存储,而不是存储在堆中,这时候要考虑的问题是当前线程在使用完 ThreadLocal 之后要主动 remove 避免出现脏数据(而不是内存泄漏问题,因为我们可以随时通过该 ThreadLocal 去访问到 ThreadLocalMap 中的 value 值,并随时进行回收,因此不会存在内存泄漏),因为在多线程的环境中,如果上一个线程使用完 ThreadLocal 之后并没有 remove,下一个线程来使用时可能会拿到上个线程的数据,产生了脏数据。

总结:

那么这里总结一下,将 ThreadLocal 定义为局部变量,会导致方法执行完之后 ThreadLocal 被回收,而 value 没有被回收,导致无法通过 key 访问到这个 value,导致内存泄漏。

如果规范使用,将 ThreadLocal 定义为 private static final,那么这个 ThreadLocal 不会被回收,可以随时通过这个 ThreadLocal 去访问到 value,随时可以手动回收,因此不会内存泄漏,但是会导致脏数据。

所以在 ThreadLocal 的内存泄漏问题主要是针对将 ThreadLocal 定义为局部变量的时候,如果不手动 remove 可能会导致 ThreadLocalMap 中的 Entry 对象无法回收,一直占用内存导致内存泄漏,直到当前 Thread 结束之后才会被回收。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/272090.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ架构详解

文章目录 概述架构详解核心组件虚拟主机&#xff08;Virtual Host&#xff09;RabbitMQ 有几种广播类型 概述 RabbitMQ是⼀个高可用的消息中间件&#xff0c;支持多种协议和集群扩展。并且支持消息持久化和镜像队列&#xff0c;适用于对消息可靠性较高的场合 官网https://www.…

ShardingSphere-SQL 解析 Issue 处理流程

ShardingSphere-SQL 解析 Issue 处理流程 这是之前给社区写的 SQL 解析 Issue 的处理流程&#xff0c;可以帮助社区用户快速参与到 ShardingSphere-SQL 解析任务当中。 ShardingSphere SQL 解析 issue 列表 Issue 背景说明 当前 Issue 使用自定义的爬虫脚本从对应的数据库官…

数据中台驱动:高效交付之道

如何保证数据中台高效交付&#xff1f; 在数据行业中&#xff0c;项目交付难题尤为突出&#xff0c;尤其在数据中台领域。数据中台项目交付面临诸多挑战&#xff0c;若不妥善解决&#xff0c;将会降低服务质量&#xff0c;影响企业数字化建设的顺利开展&#xff0c;甚至影响项目…

智能合约语言(eDSL)—— proc_macro实现合约init函数

我们通过属性宏来实现合约的init函数&#xff0c;call函数其实和init是类似的&#xff1b; GitHub - XuHugo/xwasm 构建属性宏&#xff0c;要在cargo.toml里面设置一些参数&#xff0c;这是必须的。一般来说&#xff0c;过程宏必须是一个库&#xff0c;或者作为工程的子库&…

桑晋秋:个性化头相关传递函数的研究动态及展望 | 演讲嘉宾公布

一、3D 音频专题论坛 3D 音频专题论坛将于3月27日同期举办&#xff01; 3D音频技术不仅能够提供更加真实、沉浸的虚拟世界体验&#xff0c;跨越时空的限制&#xff0c;探索未知的世界。同时&#xff0c;提供更加丰富、立体的情感表达和交流方式&#xff0c;让人类能够更加深入地…

前端javascript的DOM对象操作技巧,全场景解析

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属的专栏&#xff1a;前端泛海 景天的主页&#xff1a;景天科技苑 文章目录 1.js的DOM介绍2.节点元素层级关系3.通过js修改&#xff0c;清空节点…

第九篇 – 过程发现(Process Discovery)是如何赋能数字化市场营销全过程?- 我为什么要翻译介绍美国人工智能科技巨头IAB公司

IAB平台&#xff0c;使命和功能 IAB成立于1996年&#xff0c;总部位于纽约市。 作为美国的人工智能科技巨头社会媒体和营销专业平台公司&#xff0c;互动广告局&#xff08;IAB- the Interactive Advertising Bureau&#xff09;自1996年成立以来&#xff0c;先后为700多家媒体…

Kubesphere前端项目分析

1 KubeSphere console功能导图 模块&#xff1a; 命令行工具 (kubectl) 日志&#xff08;Logging&#xff09; 平台设置&#xff08;Platform Settings&#xff09; 服务组件&#xff08;Service Components&#xff09; 监控和警报&#xff08;Monitoring & Alerting&…

iOS-系统弹窗调用

代码&#xff1a; UIAlertController *alertViewController [UIAlertController alertControllerWithTitle:"请选择方式" message:nil preferredStyle:UIAlertControllerStyleActionSheet];// style 为 sheet UIAlertAction *cancle [UIAlertAction actionWithTit…

纳斯达克大屏:NASDAQ广告大屏多少钱?

大舍传媒 近期&#xff0c;美国纽约纳斯达克大屏广告&#xff08;NASDAQ广告大屏&#xff09;备受瞩目&#xff0c;众多企业纷纷关注其广告投放效果以及费用。纳斯达克大屏广告的价格究竟是多少呢&#xff1f;下面我们从事件的经过、相关背景信息以及对其影响和意义的分析等方…

革命性创新!AI大模型开发崭新风貌

在当今科技日新月异的时代&#xff0c;人工智能&#xff08;AI&#xff09;作为一项颠覆性的技术&#xff0c;正以革命性的创新改变着我们的生活方式和工作方式。而在AI领域中&#xff0c;大模型的开发更是成为了展示技术实力和提升智能化水平的重要标志。 随着数据量的不断增…

STM32 NAND FLASH知识点

1.NAND FLASH的简介 NAND FLASH 的概念是由东芝公司在 1989 年率先提出&#xff0c;它内部采用非线性宏单元模式&#xff0c;为固态大容量内存的实现提供了廉价有效的解决方案。 NAND FLASH 存储器具有容量较大&#xff0c;改写速度快等优点&#xff0c;适用于大量数据的存储&…

windows关闭copilot预览版

如果用户不想在windows系统当中启用Copilot&#xff0c;可以通过以下三种方式禁用。 第一种&#xff1a;隐藏Copilot 按钮 右键点击任务栏&#xff0c;取消勾选“显示 Copilot&#xff08;预览版&#xff09;按钮”&#xff0c;任务栏则不再显示&#xff0c;用户可以通过快捷键…

外包干了5天,技术退步明显。。。。。

在湖南的一个安静角落&#xff0c;我&#xff0c;一个普通的大专生&#xff0c;开始了我的软件测试之旅。四年的外包生涯&#xff0c;让我在舒适区里逐渐失去了锐气&#xff0c;技术停滞不前&#xff0c;仿佛被时间遗忘。然而&#xff0c;生活的转机总是在不经意间降临。 与女…

Java后端核心——Servlet

目录 一.概述 二.基础实现 1.导入坐标 2.定义实现类 3.注解 4.访问Servlet 三.执行流程 四.生命周期 1.加载和实例化 2.初始化 3.请求处理 4.服务终止 五.方法 1.init 2.service 3.destroy 4.getServletInfo 5.getServletConfig 六.体系结构 七.urlPatter…

Kafka MQ 主题和分区

Kafka MQ 主题和分区 Kafka 的消息通过 主题 进行分类。主题就好比数据库的表&#xff0c;或者文件系统里的文件夹。主题可以被分为若干个 分区 &#xff0c;一个分区就是一个提交日志。消息以追加的方式写入分区&#xff0c;然 后以先入先出的顺序读取。要注意&#xff0c;由…

uniapp 手写 简易 时间轴 组件

一、案例如图 该案例设计条件&#xff1a; 左侧时间 和竖线、点、内容都是居中对其的&#xff0c;上下时间点中间要有一段距离 二、编写逻辑 1. 布局结构&#xff1a;一共三个元素&#xff0c;左侧是时间和黑点&#xff0c;中间是线条&#xff0c;右侧是内容 2. 样式难点&#…

stm32普通定时器脉冲计数(发送固定脉冲个数),控制步进电机驱动器

拨码开关设置驱动器&#xff0c;细分 方法思路&#xff1a;用通用定时器TIM2&#xff0c;1ms产生一次中断&#xff1b;在中断里做IO反转&#xff1b; 发送10个脉冲信号

iOS——【自动引用计数】ARC规则及实现

1.3.3所有权修饰符 所有权修饰符一共有四种&#xff1a; __strong 修饰符__weak 修饰符__undafe_unretained 修饰符__autoreleasing 修饰符 __strong修饰符 _strong修饰符表示对对象的强引用&#xff0c;持有强引用的变量在超出其作用域的时候会被废弃&#xff0c;随着强引…

数据结构之栈详解(C语言手撕)

&#x1f389;个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名乐于分享在学习道路上收获的大二在校生 &#x1f648;个人主页&#x1f389;&#xff1a;GOTXX &#x1f43c;个人WeChat&#xff1a;ILXOXVJE &#x1f43c;本文由GOTXX原创&#xff0c;首发CSDN&…