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.mzph.cn/news/731936.shtml

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

相关文章

深度学习与机器学习:互补共进,共绘人工智能宏伟蓝图

在人工智能的广阔天地中&#xff0c;深度学习与机器学习如同两支强大的队伍&#xff0c;各自闪耀着独特的光芒&#xff0c;却又携手共进&#xff0c;共同书写着智能的辉煌篇章。尽管深度学习是机器学习的一个分支&#xff0c;但它们在模型构建、特征提取以及应用场景等多个方面…

Kafka | SpringBoot集成Kafka

SpringBoot集成Kafka 一、前言二、项目1. pom2. application.properties4. 消息生产者-测试5. 消息消费者 三、启动测试四、有总结的不对的地方/或者问题 请指正, 我在努力中 一、前言 该文章中主要对SpringBoot 集成Kafka 主要是 application.properties 与 pom坐标就算集成完…

【PHP+代码审计】PHP基础——浮点型和布尔型

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…

Kotlin dist downloading failed

现象&#xff1a; 在使用AndroidStudio编写Flutter项目时总是在工具的右下角提示错误信息 该问题通常在刚刚打开AndroidStudio时报出&#xff0c;但可以正常编译和运行flutter项目即Android项目 分析&#xff1a;Flutter项目组认为这是AndroidStudio工具平台本身的问题非Flut…

【教程】Github环境配置新手指南(超详细)

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 文章目录 一、Github初始设置&#xff08;一&#xff09;登入Github&#xff08;二&#xff09;新建仓库 二、本地Git配置&am…

专家院士共同参编的《数字空间共同体白皮书》正式启动

数字产业化、产业数字化&#xff0c;促进数字技术和实体经济深度融合&#xff0c;成为数字经济高质量发展的重要路径。数字空间与物理空间双向融合&#xff0c;用数字技术提升企业有限资源的有效配置&#xff0c;构建经济双空间增长模式&#xff0c;是形成企业新质生产力的关键…

Docker常见命令使用

Docker命令是使用Docker的基础。这里记录下Docker日常运维过程中经常使用到的一些命令&#xff0c;更全面的命令还请参考Docker官网。 docker用法概述 Docker命令可以通过CLI工具实现与服务器的交互。Docker命令的语法如下&#xff1a; docker [DOCKER-COMMAND] [OPTIONS] […

开发知识点-Apache Struts2框架

Apache Struts2 介绍S2-001S2CVE-2023-22530 介绍 Apache Struts2是一个基于MVC&#xff08;模型-视图-控制器&#xff09;设计模式的Web应用程序框架&#xff0c;它是Apache旗下的一个开源项目&#xff0c;并且是Struts1的下一代产品。Struts2是在Struts1和WebWork的技术基础…

华为北向网管NCE开发教程(1)闭坑选接口协议

华为北向网管NCE开发教程&#xff08;1&#xff09;闭坑选接口协议 华为北向网管NCE开发教程&#xff08;2&#xff09;REST接口开发 华为北向网管NCE开发教程&#xff08;3&#xff09;CORBA协议开发 本文一是记录自己开发华为北向网管遇到的坑&#xff0c;二是给需要的人&…

Androidstudio实现登录按钮按下变色

在activity_main.xml中&#xff0c;写如下代码&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"androi…

简历–工作经历–通用

文章目录 底层逻辑导图要做到&#xff1a;避免出现&#xff1a;爽文模版&#xff1a;逆境努力逆袭&#xff1a;娱乐 底层逻辑 写作底层逻辑&#xff1a; 简历是给面试者/老师看的&#xff0c;要让人家看起来轻松。 工作经历方面&#xff0c;时间一般是倒着写的&#xff08;考官…

基于git推送的ES检索pdf内容优化思路与代码实现

写在前面 在之前的内容中我们已经介绍了创建gitbucket的webHook&#xff0c;使得仓库有更新时自动推送到我们定义的接口&#xff1b;然后Java读取仓库的文件转码写入ES库&#xff0c;这些核心流程已经实现。 1. 实现ES检索pdf等文件内容的插件 2. 基于GitBucket的Hook构建ES…

解决虚拟机静态网址设置后还是变动的的问题

源头就是我的虚拟机静态网址设置好了以后但是网址还是会变动 这是我虚拟机的配置 vi /etc/sysconfig/network-scripts/ifcfg-ens33 这是出现的问题 进入这里 cd /etc/sysconfig/network-scripts/ 然后我去把多余的ens33的文件都删了 然后还不行 后来按照这个图片进行了下 然后…

中国电信提速云计算重庆基地二期项目开建预计2020年底建成

4月3日&#xff0c;中国电信重庆公司消息称&#xff0c;在大力加快5G网络、数据中心等新型基础设施建设的同时&#xff0c;中国电信全面加快云计算重庆基地项目二期建设。 该项目拥有超过20万套。 服务器运营能力预计年内完成。 据重庆电信相关负责人介绍&#xff0c;中国电信水…

YOLOv8-Seg改进:特征融合篇 | GELAN(广义高效层聚合网络)结构来自YOLOv9

🚀🚀🚀本文改进:使用GELAN改进架构引入到YOLOv8 🚀🚀🚀YOLOv8-seg创新专栏:http://t.csdnimg.cn/KLSdv 学姐带你学习YOLOv8,从入门到创新,轻轻松松搞定科研; 1)手把手教你如何训练YOLOv8-seg; 2)模型创新,提升分割性能; 3)独家自研模块助力分割; 1.YO…

Spring学习 基础(二)Bean和AOP

3、Spring Bean Bean 代指的就是那些被 IoC 容器所管理的对象&#xff0c;我们需要告诉 IoC 容器帮助我们管理哪些对象&#xff0c;这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。 Bean的创建方式 1. XML 配置文件&#xff1a; 传统上&am…

打开stable diffusion webui时,提示缺少clip或clip安装不上怎么办

在当前数字化时代&#xff0c;软件工具的应用已经成为人们日常生活和工作中不可或缺的一部分。而在使用各种软件工具的过程中&#xff0c;遇到一些技术性问题也是常有的事情。比如&#xff0c;在打开 Stable Diffusion WebUI 这样一个功能强大的工具时&#xff0c;有时会遇到缺…

python基础篇--学习记录2

1.深浅拷贝 l1 ["张大仙","徐凤年",["李淳刚","邓太阿"]] # 变量名对应的就是内存地址,这里就是将l1的内存地址给了l2 # 现在两个变量指向同一个内存地址,l1变化l2也会变化 l2 l1 现在的需求是l2是l1的拷贝版本,但是两者是完全分割…

基于 HBase Phoenix 构建实时数仓(2)—— HBase 完全分布式安装

目录 一、开启 HDFS 机柜感知 1. 增加 core-site.xml 配置项 2. 创建机柜感知脚本 3. 创建机柜配置信息文件 4. 分发相关文件到其它节点 5. 重启 HDFS 使机柜感知生效 二、主机规划 三、安装配置 HBase 完全分布式集群 1. 在所有节点上配置环境变量 2. 解压、配置环境…

微信小程序onLoad加载定义好的函数

这里小程序开发中容易犯的错误-1写自定义目录标题 给客户做一个程序。需要在页面加载的时候在onLoad(options){}中加载定义好的函数&#xff0c;代码如下 onLoad(options) {get_week_()},运行时老报错 后来修改为正确的代码 onLoad(options) {this.get_week_()//必须加this},再…