ThreadLocal内存泄漏与解决

目录

什么是Threadlocal?

Threadlocal的基本使用

ThreadLocal的内存泄漏举例

场景1

场景2

场景3

场景4

内存泄漏原因分析

总结


什么是Threadlocal?

   ThreadLocal 是 Java 中的一个类,它提供了线程本地变量的支持。线程本地变量是指被线程拥有并独立于其他线程的变量。每个线程都可以独立地改变自己的副本,而不会影响其他线程的副本。ThreadLocal 主要用于在多线程环境下保持变量的线程封闭性,以实现线程安全。

Threadlocal的基本使用

1. set(T value)

用于设置当前线程的线程本地变量的值。

参数 value 是要设置的值。

ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("Hello, ThreadLocal!");

 2. get()

用于获取当前线程的线程本地变量的值。

ThreadLocal<String> threadLocal = new ThreadLocal<>();
String value = threadLocal.get();

3. remove()

用于移除当前线程的线程本地变量。

在一些情况下,手动调用 remove() 可以帮助避免内存泄漏。

ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.remove();

 4. initialValue()

该方法是一个 protected 方法,可以被子类重写以提供线程本地变量的初始值。默认情况下,initialValue() 返回 null。

通常情况下,我们会通过 ThreadLocal 的子类并重写 initialValue() 方法来设定线程本地变量的初始值。

public class MyThreadLocal extends ThreadLocal<String> {@Overrideprotected String initialValue() {return "Default Value";}
}// 使用自定义的 MyThreadLocal
MyThreadLocal myThreadLocal = new MyThreadLocal();
String value = myThreadLocal.get();  // 返回 "Default Value"

 上述方法综合使用如下:

public class ThreadLocalExample {// 创建一个 ThreadLocal 实例private static final ThreadLocal<String> threadLocalValue = new ThreadLocal<>();public static void main(String[] args) {// 在主线程设置值threadLocalValue.set("Main Thread Value");// 创建两个子线程Thread thread1 = new Thread(() -> {// 在子线程1获取值String value = threadLocalValue.get();System.out.println("Thread 1: " + value); // 输出:Thread 1: null});Thread thread2 = new Thread(() -> {// 在子线程2设置值threadLocalValue.set("Thread 2 Value");// 在子线程2获取值String value = threadLocalValue.get();System.out.println("Thread 2: " + value); // 输出:Thread 2: Thread 2 Value});// 启动子线程thread1.start();thread2.start();// 在主线程获取值String mainThreadValue = threadLocalValue.get();System.out.println("Main Thread: " + mainThreadValue); // 输出:Main Thread: Main Thread Value// 清理主线程的值threadLocalValue.remove();}
}

       在上述案例中可以看出,每个线程中的Threadlocal变量都是独立的副本。ThreadLocal 提供了一种在多线程环境下安全地存储和访问线程本地变量的机制。每个线程都可以独立地对其进行操作,互不干扰。


ThreadLocal的内存泄漏举例

场景1

任务中不执行任何有意义的代码

//  将堆内存大小设置为-Xmx256m
public class ThreadLocalMemoryLeak {private static final int TASK_LOOP_SIZE = 500;/*线程池*/final static ThreadPoolExecutor poolExecutor= new ThreadPoolExecutor(5, 5, 1,TimeUnit.MINUTES,new LinkedBlockingQueue<>());static class LocalVariable {private byte[] a = new byte[1024*1024*5];/*5M大小的数组*/}ThreadLocal<LocalVariable> threadLocalLV;public static void main(String[] args) throws InterruptedException {for (int i = 0; i < TASK_LOOP_SIZE; ++i) {poolExecutor.execute(new Runnable() {public void run() {
//                    LocalVariable localVariable = new LocalVariable();
//                    ThreadLocalMemoryLeak oom = new ThreadLocalMemoryLeak();
//                    oom.threadLocalLV = new ThreadLocal<>();
//                    oom.threadLocalLV.set(new LocalVariable());
//                    oom.threadLocalLV.remove();System.out.println("use local varaible");}});}System.out.println("pool execute over");}}

执行后,我们通过cmd输入jvisualvm启动java性能监控工具(jdk自带),查看当前的性能消耗情况。

查看内存会发现内存消耗稳定在25MB左右。

场景2

在每个任务中new出一个数组,执行完成后我们可以看见,内存占用基本和场景1同

public class ThreadLocalMemoryLeak {private static final int TASK_LOOP_SIZE = 500;/*线程池*/final static ThreadPoolExecutor poolExecutor= new ThreadPoolExecutor(5, 5, 1,TimeUnit.MINUTES,new LinkedBlockingQueue<>());static class LocalVariable {private byte[] a = new byte[1024*1024*5];/*5M大小的数组*/}ThreadLocal<LocalVariable> threadLocalLV;public static void main(String[] args) throws InterruptedException {for (int i = 0; i < TASK_LOOP_SIZE; ++i) {poolExecutor.execute(new Runnable() {public void run() {LocalVariable localVariable = new LocalVariable();
//                    ThreadLocalMemoryLeak oom = new ThreadLocalMemoryLeak();
//                    oom.threadLocalLV = new ThreadLocal<>();
//                    oom.threadLocalLV.set(new LocalVariable());
//                    oom.threadLocalLV.remove();System.out.println("use local varaible");}});}System.out.println("pool execute over");}}

启动后内存使用情况如下

 我们可以看到有毛刺现象,这是因为GC造成的,但是没有出现内存泄漏的情况。

场景3

启用Threadlocal

public class ThreadLocalMemoryLeak {private static final int TASK_LOOP_SIZE = 500;/*线程池*/final static ThreadPoolExecutor poolExecutor= new ThreadPoolExecutor(5, 5, 1,TimeUnit.MINUTES,new LinkedBlockingQueue<>());static class LocalVariable {private byte[] a = new byte[1024*1024*5];/*5M大小的数组*/}ThreadLocal<LocalVariable> threadLocalLV;public static void main(String[] args) throws InterruptedException {for (int i = 0; i < TASK_LOOP_SIZE; ++i) {poolExecutor.execute(new Runnable() {public void run() {
//                    LocalVariable localVariable = new LocalVariable();ThreadLocalMemoryLeak oom = new ThreadLocalMemoryLeak();oom.threadLocalLV = new ThreadLocal<>();oom.threadLocalLV.set(new LocalVariable());
//                    oom.threadLocalLV.remove();System.out.println("use local varaible");}});Thread.sleep(100);}System.out.println("pool execute over");}}

启动后内存使用情况如下

占用内存情况在100MB-125MB左右。

场景4

手动调用remove清除threadlocal,执行查看内存情况。

public class ThreadLocalMemoryLeak {private static final int TASK_LOOP_SIZE = 500;/*线程池*/final static ThreadPoolExecutor poolExecutor= new ThreadPoolExecutor(5, 5, 1,TimeUnit.MINUTES,new LinkedBlockingQueue<>());static class LocalVariable {private byte[] a = new byte[1024*1024*5];/*5M大小的数组*/}ThreadLocal<LocalVariable> threadLocalLV;public static void main(String[] args) throws InterruptedException {for (int i = 0; i < TASK_LOOP_SIZE; ++i) {poolExecutor.execute(new Runnable() {public void run() {
//                    LocalVariable localVariable = new LocalVariable();ThreadLocalMemoryLeak oom = new ThreadLocalMemoryLeak();oom.threadLocalLV = new ThreadLocal<>();oom.threadLocalLV.set(new LocalVariable());oom.threadLocalLV.remove();System.out.println("use local varaible");}});Thread.sleep(100);}System.out.println("pool execute over");}}

启动后内存使用情况如下

此时内存使用情况跟场景1相同。 

总结:通过以上场景3,我们可以得出在启用了threadlocal以后发生了内存泄漏。


内存泄漏原因分析

        每一个Thread线程都维护了一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal 实例本身,value 是真正需 要存储的Object,也就是说ThreadLocal本身并不存储值,它只是作为一个 key来让线程从ThreadLocalMap获取value。ThreadLocalMap是使用ThreadLocal的弱引用作为Key的,弱引用的对象在GC 时会被回收。

        图中虚线表示弱引用。

        当把 threadlocal 变量置为 null 以后,没有任何强引用指向 threadlocal 实例,所以 threadlocal将会被gc回收。这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前 线程再迟迟不结束的话,这些key为null的Entry的 value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,而这块value永远不会被访问到了,所以存在着内存泄露。

       只有当前线程结束后,线程就不会在栈中,强引用断开,才会被GC回收掉。解决内存泄漏做法是当不使用线程中threadlocal变量时及时remove清除数据。

       场景3中,虽然线程池里面的任务执行完毕了,但是线程池里面的5个线程会一直存在直到JVM退出,我们set了线程的localVariable变量后没有调用 localVariable.remove()方法,导致线程池里面的5个线程的threadLocals变量里面的new LocalVariable()实例没有被释放。当我们手动再每次都调用remove清除数据时,内存正常。

      ThreadLocal的实现中,无论是get()、set()在某些时候,调用了expungeStaleEntry方法用来清除Entry中Key为null的Value,但是这是不及时的,也不是每次都会执行的,所以一些情况下还是会发生内存泄露。 只有 remove()方法中显式调用了expungeStaleEntry 方法。

总结

       JVM利用设置ThreadLocalMap的Key为弱引用,来避免内存泄露。

       JVM 利用调用remove、get、set方法的时候,回收弱引用。

       当ThreadLocal存储很多Key为 null的Entry的时候,而不再去调用 remove、 get、set 方法,那么将导致内存泄漏。

        使用线程池+ ThreadLocal 时要多注意,这种情况下,线程是一直在不断的重复运行的,从而也就造成了value可能造成累积的情况。

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

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

相关文章

Win10子系统Ubuntu实战(一)

在 Windows 10 中安装 Ubuntu 子系统&#xff08;Windows Subsystem for Linux&#xff0c;简称 WSL&#xff09;有几个主要的用途和好处&#xff1a;Linux 环境的支持、跨平台开发、命令行工具、测试和验证、教育用途。总体而言&#xff0c;WSL 提供了一种将 Windows 和 Linux…

Python库中关于时间的常见操作

目录 导入所需的库 获取当前时间 格式化日期和时间 解析日期和时间字符串 时间戳操作 获取当前时间戳&#xff1a; 将时间戳转换为日期和时间&#xff1a; 时间差操作 时间日期的时区处理 时间日期的随机生成 注意事项 总结 在Python中&#xff0c;时间处理是一个重…

Low Poly Cartoon House Interiors

400个独特的低多边形预制件的集合,可以轻松创建高质量的室内场景。所有模型都已准备好放入场景中,并使用一个纹理创建,以提高性能!包含演示场景! 模型分类: - 墙壁(79件) - 地板(28块) - 浴室(33个) - 厨房(36件) - 厨房道具(68件) - 房间道具(85件) - 灯具(…

《亚太教育》是什么级别的期刊?是正规期刊吗?能评职称吗?

《亚太教育》主要发表教育理论研究、教育教学实践、学校管理、学科教育、科研管理等学术论文以及其他与教育教学相关的学术论文和研究成果,现征集教育管理以及各学科优秀论文。欢迎.各位教师、教育工作者及高校学生踊跃投稿。 收录情况&#xff1a;知网万方维普收录 投稿方式&a…

vscode使用npm安装element-UI并添加router路由

npm安装vue&#xff0c;添加淘宝镜像-CSDN博客 elementUI安装与配置 安装可以看我上一篇文章 vscode控制台输入指令 npm i element-ui -S 安装完成后在目录结构打开下图文件 可以看到多了一行elementui就代表安装成功了 下面是项目常用的结构 安装完成后需要启用elementU…

Java项目:114SSM图书管理系统

博主主页&#xff1a;Java旅途 简介&#xff1a;分享计算机知识、学习路线、系统源码及教程 文末获取源码 一、项目介绍 图书管理系统基于SpringSpringMVCMybatis开发&#xff0c;系统主要实现了图书馆借书还书功能&#xff0c;系统分为管理员和读者两种角色。 管理员功能如下…

20240105移远的4G模块EC20在Ubuntu 20.04.6 LTS下使用联通5G卡上网的步骤

20240105移远的4G模块EC20在Ubuntu 20.04.6 LTS下使用联通5G卡上网的步骤 2024/1/5 10:11 缘起&#xff1a;需要在Firefly的AIO-3399J开发板上调试移远的4G模块EC20&#xff08;Android10/11/12&#xff09;&#xff0c;需要现在先测试EC20的好坏&#xff01; 陶老板告诉我找一…

多链混沌:Layer2 格局演变与跨链流动性的新探索

点击查看原文&#xff1a;多链混沌&#xff1a;Layer2 格局演变与跨链流动性的新探索 如今的 Crypto 是一个由多链构成的混沌世界。曾经&#xff0c;以太坊聚集了加密世界绝大多数的流动性与 DeFi 应用&#xff0c;但现在其 TVL 占比已经降到 60% 以下&#xff0c;并仍处于下降…

【51单片机系列】串口通信模块

文章目录 一、计算机串行通信基础二、串行通信的基本概念2.1、异步通信与同步通信2.2、串行通信的传输方向2.3、串行通信常见的错误校验2.4、传输速率 三、串行通信接口标准3.1、RS-232C接口3.2、RS-422A3.3、RS-485接口 四、80C51的串行口4.1、80C51串行口的控制寄存器4.2、80…

Python解析参数的三种方法

今天我们分享的主要目的就是通过在 Python 中使用命令行和配置文件来提高代码的效率 Let’s go! 我们以机器学习当中的调参过程来进行实践&#xff0c;有三种方式可供选择。第一个选项是使用 argparse&#xff0c;它是一个流行的 Python 模块&#xff0c;专门用于命令行解析&…

Spring boot 3 集成rocketmq-spring-boot-starter解决版本不一致问题

安装RocketMQ根据上篇文章使用Docker安装RocketMQ并启动之后&#xff0c;有个隐患详情见下文 Spring Boot集成 <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.2…

在线表白网页制作源码,无需数据库,自带网站会员付费

源码介绍 通过在线表白&#xff0c;人们可以以一种浪漫的方式表达自己的感情&#xff0c;只需要填写一些基本信息&#xff0c;就能自动生成精美的表白页面。 为了增加网站的收入&#xff0c;用户可以购买网站会员&#xff0c;以使用指定的网页制作模板。 这个系统无需数据库…

计算机网络 —— 物理层

物理层 2.1 物理层的基本概念 物理层考虑的是怎样才能在连接各种计算机的传输媒体上传输数据比特流。 物理层为数据链路层屏蔽了各种传输媒体的差异&#xff0c;使数据链路层只需要考虑如何完成本层的协议和服务&#xff0c;而不必考虑网络具体的传输媒体是什么 2.2 物理层下…

pyqtgraph 教程

pyqtgraph 教程 简介 PyQtGraph 是一个用于科学和工程数据可视化的开源库&#xff0c;基于 PyQt 和 NumPy 构建而成。它提供了丰富的绘图工具和交互功能&#xff0c;可以用于创建高性能的实时数据图表、图像显示和信号处理应用。 以下是 PyQtGraph 的一些特点和功能&#xf…

python 多线程 守护线程

daemon线程&#xff1a;守护线程&#xff0c;优先级别最低&#xff0c;一般为其它线程提供服务。通常&#xff0c;daemon线程体是一个无限循环。如果所有的非daemon线程(主线程以及子线程&#xff09;都结束了&#xff0c;daemon线程自动就会终止。t.daemon 属性&#xff0c;设…

大模型机器人发展史:从VoxPoser、RT2到斯坦福Mobile ALOHA、Google机器人

前言 23年7月&#xff0c;我在朋友圈评估Google的RT2说道&#xff1a; “大模型正在革新一切领域啊&#xff0c;超帅&#xff0c;通过大模型不仅能理解“人话”&#xff0c;还能对“人话”进行推理&#xff0c;并转变为机器人能理解的指令&#xff0c;从而分阶段完成任务。回…

Hyperledger Fabric 权限策略和访问控制

访问控制是区块链网络十分重要的功能&#xff0c;负责控制某个身份在某个场景下是否允许采取某个操作&#xff08;如读写某个资源&#xff09;。 常见的访问控制模型包括强制访问控制&#xff08;Mandatory Access Control&#xff09;、自主访问控制&#xff08;Discretionar…

代码随想录算法训练营第21天 |530.二叉搜索树的最小绝对差 501.二叉搜索树中的众数 236. 二叉树的最近公共祖先

530.二叉搜索树的最小绝对差 题目链接&#xff1a;530.二叉搜索树的最小绝对差 给你一棵所有节点为非负值的二叉搜索树&#xff0c;请你计算树中任意两节点的差的绝对值的最小值。 示例&#xff1a; 提示&#xff1a;树中至少有 2 个节点。 &#x1f4a1;解题思路 题目中…

UE5 将类修改目录

有个需求&#xff0c;需要修改ue里面类的位置&#xff0c;默认在Public类下面&#xff0c;我想创建一个二级目录&#xff0c;将所有的类分好位置&#xff0c;方便查看。 上图为创建一个类所在的默认位置。 接下来&#xff0c;将其移动到一个新的目录中。 首先在资源管理器中找…

Android - CrashHandler 全局异常捕获器

官网介绍如下&#xff1a;Thread.UncaughtExceptionHandler (Java Platform SE 8 ) 用于线程因未捕获异常而突然终止时调用的处理程序接口。当线程由于未捕获异常而即将终止时&#xff0c;Java虚拟机将使用thread . getuncaughtexceptionhandler()查询该线程的UncaughtExceptio…