ThreadLocal内存泄漏分析

news/2024/12/22 9:33:06/

一、ThreadLocal内存泄漏分析

1.1 ThreadLocal实现原理

1.1.1、set(T value)方法

查看ThreadLocal源码的 set(T value)方法,可以发现数据是存在了ThreadLocalMap的静态内部类Entry里面

其中key为使用弱引用的ThreadLocal实例,value为set传入的值。核心源代码:

public void set(T value) {Thread t = Thread.currentThread();// ThreadLocalMap跟当前线程对象绑定,是线程对象中的一个成员属性ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);else// 第一次调用的时候,将当前线程和value往下传。createMap(t, value);
}
​
void createMap(Thread t, T firstValue) {// 当前线程内部的变量 ThreadLocal.ThreadLocalMap threadLocals 设置为新建出来的对象。t.threadLocals = new ThreadLocalMap(this, firstValue);
}
​
static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal<?>> {Object value;
​Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}...
}

1.2 ThreadLocal 内存泄漏的原因

引用原理图如下,实心箭头表示强引用虚线箭头表示弱引用

1、图中所示,当前线程强引用了ThreadLocalMap,而ThreadLocal为ThreadLocalMap的弱引用Key。

2、结合1.2节知识背景,如果ThreadLocal没有被外部强引用,当系统触发GC时,会将ThreadLocal对象回收掉,会导致ThreadLocalMap的Key为null,但是value还是被当前线程强引用,只有当Thread线程退出后,value的强引用链才会断开。

3、如果线程不结束(比如使用了线程池),则引用链(Thread -> ThreadLocalMap -> Entry -> value)一直存在,永远不会被回收,从而造成内存泄漏。

1.2.1 代码演示内存泄漏
public class ThreadLocalTest {public static void main(String[] args) {//二、TheadLocal内存泄漏//2.1、局部代码块中创建ThreadLocal后不引用它。createThreadLocal();//2.2、让GC回收不再被强引用,只有弱引用的TheadLocal对象System.gc();//2.3、查看线程成员属性ThreadLocalMap中 存入的键值对(key为null,而value还在,出现内存泄漏问题)Thread thread = Thread.currentThread();}
​public static ThreadLocal<String> createThreadLocal(){ThreadLocal<String> threadLocal = new ThreadLocal<>();threadLocal.set("zs");return threadLocal;}
}
​

debug运行代码,查看当前线程的ThreadLocalMap中的数据,可以发现引用的Key已经被GC回收了,造成了内存泄漏

1.3 key为什么使用弱引用

即使没有手动删除key和value,ThreadLocal在没有被引用的时候也会被回收。即ThreadLocalMap的key为null,下一次ThreadLocalMap调用set()、get()、remove()方法的时候会清除没被回收的value。

1.3.1 代码演示清除没被回收的value
package com.adolesce.server.mutithread;
​
/*** @author Administrator* @version 1.0* @description: TODO* @date 2023/7/1 9:52*/
public class ThreadLocalTest {public static void main(String[] args) {//三、TheadLocal没被强引用后,触发System.gc(),将key回收,设为null//3.1、创建ThreadLocal对象ThreadLocal threadLocal = createThreadLocal();//3.2、模拟threadLocal没被引用:断点时手动将thread中ThreadLocalMap value为【zs】对应的key设为nullThread thread = Thread.currentThread();//3.3、测试get、set、remove方法将key为null的value清除threadLocal.get();//3.4、查看ThreadLocalMap中value是否为null了thread = Thread.currentThread();}
​public static ThreadLocal<String> createThreadLocal(){ThreadLocal<String> threadLocal = new ThreadLocal<>();threadLocal.set("zs");return threadLocal;}
}

调用get方法的时候,由于map中的value对应的key为null,通过当前ThreadLocal对象去获取是获取value是获取不到Entry,于是调用初始化value的方法,清除原来的value,如get源码:

public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) { // 为null跳出判断@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue(); // 调用初始化value的方法,清除原来的value
}

二、ThreadLocal的正确使用方法

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

2、将ThreadLocal变量定义成 private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能

通过 ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。

package com.adolesce.server.mutithread;
​
/*** @author Administrator* @version 1.0* @description: TODO* @date 2023/7/1 9:52*/
public class ThreadLocalTest {private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
​public static void main(String[] args) {//一、TheadLocal使用System.out.println("主线程开启,线程ID:" + Thread.currentThread().getId());//1.1、向ThreadLocalMap中存入键值对setData();//1.2、从ThreadLocal中获取数据getData();//1.3、清除(通常在拦截器的afterCompletion()方法中进行清除)removeData();}
​private static void setData() {threadLocal.set("zhangsan");System.out.println("在线程"+Thread.currentThread().getId()+"中向ThreadLocal中存入了姓名");}
​private static void getData() {String name = threadLocal.get();System.out.println("在线程"+Thread.currentThread().getId()+"中从ThreadLocal中取了姓名:" + name);}private static void removeData() {threadLocal.remove();}
}
​

三、总结

1、内存泄漏原因:我们使用ThreadLocal过程中,如果ThreadLocal对象强引用断掉后,只剩弱引用,ThreadLocal对象会被回收,此时ThreadLocal中的key会变为null,而value没有被回收,同时又由于ThreadLocalMap是Thread中的成员属性,与Thread对象的生命周期是一样长,如果当前线程一直未被销毁,又没有手动删除对应key,这样就会导致value内存泄漏。

2、使用弱引用可以多一层value回收的保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set()、get()、remove()的时候会被清除。

3、因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。


http://www.ppmy.cn/news/1535001.html

相关文章

【AI知识点】负对数似然损失函数(Negative Log-Likelihood Loss,NLL)

负对数似然损失函数&#xff08;Negative Log-Likelihood Loss&#xff0c;NLL&#xff09; 是机器学习&#xff0c;尤其是分类问题中常用的一种损失函数。它用于衡量模型预测的概率分布与真实标签之间的差异。负对数似然损失函数的目标是最大化正确类别的预测概率&#xff0c;…

C#通用文档识别挂接示例、手写体识别接口

通用文档识别服务接口支持扫描文档、长微博、自然场景下的各种印刷体文字、手写体文字、繁体文字、英文、阿拉伯文等文字的识别&#xff0c;提供免费测试与在线体验服务 部署方式灵活多样&#xff0c;支持标准化HTTP、WebService接口集成&#xff0c;支持私有化部署&#xff0c…

用SpringBoot搭建高效校园资料分享系统

1系统概述 1.1 研究背景 如今互联网高速发展&#xff0c;网络遍布全球&#xff0c;通过互联网发布的消息能快而方便的传播到世界每个角落&#xff0c;并且互联网上能传播的信息也很广&#xff0c;比如文字、图片、声音、视频等。从而&#xff0c;这种种好处使得互联网成了信息传…

怎么提取视频里的音频?非常简单的提取音频方法

怎么提取视频里的音频&#xff1f;在现代数字媒体环境中&#xff0c;视频和音频的结合已成为信息传播和创作的重要手段。随着互联网的发展&#xff0c;视频内容日益丰富&#xff0c;从社交媒体短视频到在线课程&#xff0c;再到电影和纪录片&#xff0c;音频在这些内容中的角色…

Vue+NestJS项目实操(图书管理后台)

一、项目搭建 前端基于vben进行二次开发 在Github下载vben框架&#xff0c;搜索vben即可 下载地址&#xff1a;https://github.com/vbenjs/vue-vben-admin 下载完成后&#xff0c;进行安装依赖&#xff0c;使用命令&#xff1a; // 下载依赖 pnpm install// 运行项目 pnpm …

【Docker从入门到进阶】04.高效实践

4. 高效实践 在现代软件开发中&#xff0c;Docker和容器技术使得应用程序的开发、部署和管理变得更为高效。然而&#xff0c;伴随而来的也是一些挑战&#xff0c;比如镜像优化、性能调优、安全性管理以及持续集成和持续交付&#xff08;CI/CD&#xff09;的集成等。以下是一些…

分布式事务的解决方案(如两阶段提交、TCC、SAGA)。Spring的核心概念(如IOC、AOP)。

分布式事务的解决方案&#xff08;如两阶段提交、TCC、SAGA&#xff09;。 分布式事务是指发生在多个数据节点之间的事务&#xff0c;它比单机事务要复杂得多。以下是几种常见的分布式事务解决方案&#xff1a; 一、两阶段提交&#xff08;2PC&#xff09; 两阶段提交协议是…

Kafka学习笔记(一)Kafka基准测试、幂等性和事务、Java编程操作Kafka

文章目录 前言4 Kafka基准测试4.1 基于1个分区1个副本的基准测试4.2 基于3个分区1个副本的基准测试4.3 基于1个分区3个副本的基准测试 5 Java编程操作Kafka5.1 引入依赖5.2 向Kafka发送消息5.3 从Kafka消费消息5.4 异步使用带有回调函数的生产消息 6 幂等性6.1 幂等性介绍6.2 K…