Java进阶篇--并发容器之ThreadLocal内存泄漏

目录

ThreadLocal内存泄漏的原因?

改进和优化

cleanSomeSlots方法

expungeStaleEntry方法

replaceStaleEntry方法

为什么使用弱引用?

Thread.exit()

ThreadLocal内存泄漏最佳解决方案

在使用完毕后立即清理ThreadLocal

使用InheritableThreadLocal替代ThreadLocal

使用弱引用清理ThreadLocal


ThreadLocal内存泄漏的原因?

ThreadLocal是为了解决多线程共享访问对象带来的线程安全问题。它通过为每个线程分配一个对象实例,达到隔离的目的,使得线程之间互不影响。与同步机制不同的是,同步机制以时间换空间,控制线程访问共享对象的顺序,而ThreadLocal则是为每个线程分配一个对象实例,牺牲了空间效率换来时间效率。但是,在ThreadLocal使用过程中存在内存泄漏的风险,如果线程执行结束后,ThreadLocal,ThreadLocalMap,entry都会被回收掉,但在线程池中,线程是复用的,所以ThreadLocal的内存泄漏就值得我们关注。

ThreadLocal内存泄漏的原因主要是因为在使用ThreadLocal时没有及时清理ThreadLocal对象所引用的线程特有的副本。具体来说,当一个线程结束后,如果没有手动清理或者调用remove方法来移除对应的ThreadLocal对象,那么这个ThreadLocal对象仍然会被ThreadLocalMap持有,而ThreadLocalMap是通过弱引用来关联ThreadLocal对象的,如果ThreadLocal对象没有被其他强引用持有,那么在垃圾回收的时候就会被回收,但是对应的线程特有的副本却无法被回收,从而导致内存泄漏。

另外,如果使用线程池来管理线程,线程池中的线程是会被复用的,而不会在每次任务执行结束后销毁线程。这就意味着线程池中的线程仍然持有之前任务中创建的ThreadLocal对象,而这些对象对应的线程特有的副本却不会被释放,从而导致内存泄漏的问题。

改进和优化

对于ThreadLocal内存泄漏的问题,Java在不同版本中进行了不同的改进和优化。以下是一些改进措施:

cleanSomeSlots方法

cleanSomeSlots方法的改进: 在JDK 6之前,ThreadLocalMap中没有自动清理过期Entry的机制。JDK 7引入了cleanSomeSlots方法来解决这个问题。每次调用set或get方法时,会以一定的概率触发该方法,该方法会遍历整个表格,并清理掉过期的Entry。这样可以减轻内存泄漏的风险,使得那些已经过期且无法再被访问的线程特有副本得到释放。

public class MyThreadLocal<T> extends ThreadLocal<T> {@Overrideprotected T initialValue() {// 初始化方法return ...;}@Overridepublic void set(T value) {super.set(value);cleanSomeSlots();}@Overridepublic T get() {T value = super.get();cleanSomeSlots();return value;}private void cleanSomeSlots() {ThreadLocalMap map = getMap(Thread.currentThread());if (map != null) {map.cleanSomeSlots();}}
}

expungeStaleEntry方法

expungeStaleEntry方法的改进: JDK 8引入了expungeStaleEntry方法,该方法用于显式地清理过期的Entry。在ThreadLocalMap的size超过阈值时被调用,该方法会遍历整个表格,将key为null的Entry移除以释放关联的线程特有副本。

public class MyThreadLocal<T> extends ThreadLocal<T> {@Overrideprotected T initialValue() {// 初始化方法return ...;}@Overridepublic void set(T value) {super.set(value);expungeStaleEntry();}@Overridepublic T get() {T value = super.get();expungeStaleEntry();return value;}private void expungeStaleEntry() {ThreadLocalMap map = getMap(Thread.currentThread());if (map != null) {map.expungeStaleEntry();}}
}

replaceStaleEntry方法

replaceStaleEntry方法的改进: JDK 9引入了replaceStaleEntry方法,用于在创建新的Entry时替换已经过期的Entry。该方法主要解决了JDK 8中可能出现的并发问题,保证在替换Entry时不会有其他线程同时访问旧的Entry,从而避免了可能的内存泄漏。

public class MyThreadLocal<T> extends ThreadLocal<T> {@Overrideprotected T initialValue() {// 初始化方法return ...;}@Overridepublic void set(T value) {super.set(value);replaceStaleEntry();}@Overridepublic T get() {T value = super.get();replaceStaleEntry();return value;}private void replaceStaleEntry() {ThreadLocalMap map = getMap(Thread.currentThread());if (map != null) {map.replaceStaleEntry();}}
}

为什么使用弱引用?

使用弱引用主要是为了解决ThreadLocal中的内存泄漏问题。在线程局部变量中,如果使用强引用,即使在业务代码中将ThreadLocal实例设置为null,由于Entry强引用着ThreadLocal,ThreadLocal对象无法被垃圾回收,从而导致内存泄漏。

而使用弱引用修饰ThreadLocal可以解决这个问题。当ThreadLocal实例不再被业务代码使用时,由于ThreadLocalMap中使用了弱引用来引用ThreadLocal实例,ThreadLocal实例会在下一次垃圾回收时被正确地回收掉。同时,在ThreadLocal的生命周期中会对key为null的脏entry进行处理,避免出现潜在的内存泄漏。

尽管使用弱引用会导致可能出现一些内存泄漏问题,但相比起使用强引用造成的内存泄漏,弱引用的使用能够保证在ThreadLocal的生命周期内尽可能地避免内存泄漏问题,从而提高应用的安全性和可靠性。

需要注意的是,虽然使用弱引用可以减少内存泄漏的潜在问题,但仍然需要在使用ThreadLocal时注意及时清理和移除不再使用的ThreadLocal实例,以确保整体系统的资源利用效率。

Thread.exit()

Thread.exit()方法是一个废弃的方法,不推荐使用。它会导致线程突然终止,可能会破坏线程的稳定性和数据完整性,并且无法保证所有资源的正确释放。在正常情况下,应该通过执行完任务或者正常结束的方式让线程退出。如果需要强制终止线程,可以通过调用Thread的interrupt方法来进行管理和控制。

public class InterruptExample {public static void main(String[] args) {Thread thread = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {// 执行线程的任务// ...// 检查中断标志if (Thread.currentThread().isInterrupted()) {System.out.println("线程被中断,退出循环");break;}}System.out.println("线程退出");});thread.start();// 给线程发送中断信号thread.interrupt();}}

在这个示例中,线程在while循环中执行任务,并在每次循环开始时检查中断标志。如果中断标志被设置,线程会退出循环并输出相应信息。

在main方法中,我们使用thread.interrupt()方法给线程发送中断信号。这会将线程的中断标志设置为true。线程在下一次循环开始时会检查到这个中断标志,并做出相应的处理来退出循环。

这种方式可以安全地控制线程的退出,避免了Thread.exit()方法可能导致的问题。同时,它也提供了更灵活和可控的方式来管理线程的生命周期。

ThreadLocal内存泄漏最佳解决方案

由于ThreadLocal为每个线程维护一个独立的变量副本,因此如果没有及时清理ThreadLocal,可能会导致内存泄漏问题。下面是一些解决ThreadLocal内存泄漏问题的最佳实践:

在使用完毕后立即清理ThreadLocal

及时清理是防止内存泄漏的最佳解决方案之一。确保在使用完ThreadLocal后调用其remove()方法,清除数据。

特别是在使用线程池的情况下,由于线程的复用性,如果没有清理ThreadLocal,可能会导致线程中保存的数据对后续线程产生干扰,进而导致业务逻辑出现问题。因此,类似于加锁与解锁一样,使用完ThreadLocal后就应该立即清理,以确保下次使用时不会受到上次使用遗留下来的数据的影响。

public class UserContext {private static final ThreadLocal<User> USER_THREAD_LOCAL = new ThreadLocal<>();public static void setUser(User user) {USER_THREAD_LOCAL.set(user);}public static User getUser() {return USER_THREAD_LOCAL.get();}public static void clear() {USER_THREAD_LOCAL.remove();}
}

在这个示例中,我们定义了一个静态的ThreadLocal变量USER_THREAD_LOCAL,并提供了setUser、getUser和clear方法,在使用完USER_THREAD_LOCAL后,可以调用clear方法清理ThreadLocal。

通过及时清理ThreadLocal,可以有效避免内存泄漏问题,并确保数据在不同线程间的隔离性。

使用InheritableThreadLocal替代ThreadLocal

如果需要在父线程和子线程之间共享ThreadLocal变量,可以使用InheritableThreadLocal替代ThreadLocal。InheritableThreadLocal也是一种ThreadLocal,但它可以让子线程继承父线程的ThreadLocal变量副本,从而避免重复创建副本的问题。

public class InheritableRequestContext {private static final InheritableThreadLocal<String> REQUEST_ID = new InheritableThreadLocal<>();public static void setRequestId(String requestId) {REQUEST_ID.set(requestId);}public static String getRequestId() {return REQUEST_ID.get();}public static void clear() {REQUEST_ID.remove();}
}

在这个示例中,我们使用了InheritableThreadLocal来定义共享变量REQUEST_ID,并提供了setRequestId、getRequestId和clear方法,以便在线程间共享该变量。

使用弱引用清理ThreadLocal

使用弱引用来清理ThreadLocal。通过将ThreadLocal变量存储在WeakReference中,可以让垃圾回收器在需要释放内存时自动清理ThreadLocal变量。

public class WeakRequestContext {private static final ThreadLocal<WeakReference<String>> REQUEST_ID = new ThreadLocal<>();public static void setRequestId(String requestId) {REQUEST_ID.set(new WeakReference<>(requestId));}public static String getRequestId() {WeakReference<String> ref = REQUEST_ID.get();return ref != null ? ref.get() : null;}public static void clear() {REQUEST_ID.remove();}
}

在这个示例中,我们使用了ThreadLocal和WeakReference来定义变量REQUEST_ID,并提供了setRequestId、getRequestId和clear方法。

总之,为避免ThreadLocal内存泄漏问题,可以采用立即清理、使用InheritableThreadLocal和使用弱引用等多种解决方案。在具体场景中,可以根据实际情况选择最佳的解决方案。

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

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

相关文章

FreeSWITCH 1.10.10 简单图形化界面12 - 注册IMS

FreeSWITCH 1.10.10 简单图形化界面12 - 注册IMS 0、 界面预览1、IMS注册-SIP中继基本设置界面2、IMS注册-SIP中继呼叫设置3、IMS中继-代理设置界面4、IMS注册-SIP中继状态界面5、IMS注册-SIP中继详细状态界面6、IMS注册-SIP中继代拨号码优先界面 FreeSWITCH界面安装参考&#…

postgresql14-用户与角色(二)

介绍 查看 SELECT rolname FROM pg_roles;postgres是系统初始化时默认创建的角色&#xff0c;为超级管理员。 \duList of rolesRole name | Attributes | Member of ------------------------------------------------------…

RPA的尽头是超自动化?

超自动化在经过数年的发酵期后&#xff0c;已从一个科技概念崛起为市值近千亿元的新赛道&#xff0c;包括各大互联网巨头、科技公司都纷纷围绕超自动化进行战略布局。 一方面&#xff0c;是行业巨头选择纷纷跻身超自动化新赛道&#xff0c;另一方面&#xff0c;RPA行业的领军企…

Android-Framework 不允许应用商场下载的应用安装,adb 、pm 可以正常安装

一、环境 高通865 Android10 二、情景 从framework层禁止应用商场下载的应用安装&#xff0c;adb 、pm 可以正常安装 三、代码实现 frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java -35,6 35,7 import android.os.Build;…

kubesphere 使用docker kubesphere 使用教程

1. 在centos上的安装流程 1.1 安装需要的环境 yum install -y socat conntrack ebtables ipset curl1.2 下载KubeKey #电脑必须可以访问github&#xff0c;很重要。不然安装过程会出问题 curl -sfL https://get-kk.kubesphere.io | VERSIONv1.2.1 sh - chmod x kk1.3 开始安…

如何链接未被使用的符号

如何链接未被使用的符号 我们知道&#xff0c;在ld链接静态库过程中&#xff0c;通常只有被使用到的符号才回被链接进可执行文件中。 而在某些场景中&#xff0c;我们希望在链接的时候也能把未使用的符号也给链接进来。 在ext/mylib.h文件中声明了一个add函数和一个Register…

DistributedDataParallel数据不均衡

背景 在使用 DistributedDataParallel 进行数据并行训练时&#xff0c;每次反向传播都需要执行 all_reduce 操作以同步各个进程的梯度。all_reduce 需要进程组中的所有进程参与&#xff0c;如果某一个进程没有执行 all_reduce&#xff08;一个进程的输入较其他进程少&#xff…

软件测试(五)自动化 selenium

文章目录 自动化测试单元测试&#xff1a;单元测试&#xff1a;UI自动化 selenium工具定义特点&#xff1a;原理&#xff1a;seleniumjava环境搭建SeleniumAPI获取测试结果&#xff1a;添加等待浏览器操作键盘事件鼠标事件多层框架/窗口定位下拉框处理弹窗处理上传文件操作关闭…

Unity Editor 打包指定资源(AssetBundle)和加载指定资源

前言&#xff1a; 一般用于ui资源打包和加载&#xff0c;代码比较简单没什么好说的&#xff0c;直接上代码。 打包代码&#xff1a; [MenuItem("Assets/打包指定的预设")]public static void BuildAsset() {var selectObject Selection.activeObject;if (selectObje…

怎样才能去除视频中的背景音乐,保留人声?

做视频剪辑&#xff0c;二次创作的朋友&#xff0c;需要去除视频中的背景音乐&#xff0c;保留人声&#xff1b;或者去除人声&#xff0c;保留背景音乐。如果请身边做视频的朋友帮忙&#xff0c;可有时不能沟通到位&#xff0c;完成后的效果并不是很理想&#xff0c;就很尴尬了…

26. 通过 cilium pwru了解网络包的来龙去脉

pwru是一种基于eBPF的工具,可跟踪Linux内核中的网络数据包,并具有先进的过滤功能。它允许对内核状态进行细粒度检查,以便通过调试网络连接问题来解决传统工具(如iptables TRACE或tcpdump)难以解决甚至无法解决的问题。在本文中,我将介绍pwru如何在不必事先了解所有内容的…

低代码助力软件开发

低代码开发工具正在日益变得强大&#xff0c;它正不断弥合着前后端开发之间的差距。对于后端来说&#xff0c;基于低代码平台开发应用时&#xff0c;完全不用担心前端的打包、部署等问题&#xff0c;也不用学习各种框架&#xff08;Vue、React、Angular等等&#xff09;&#x…

Mysql中的RR 隔离级别,到底有没有解决幻读问题

Mysql 中的 RR 事务隔离级别&#xff0c;在特定的情况下会出现幻读的问题。所谓的幻读&#xff0c;表示在同一个事务中的两次相同条件的查询得到的数据条数不一样。 在 RR 级别下&#xff0c;什么情况下会出现幻读 这样一种情况&#xff0c;在事务 1 里面通过 update 语句触发当…

一款简单漂亮的WPF UI - AduSkin

前言 经常会有同学会问&#xff0c;有没有好看简单的WPF UI库推荐的。今天就给大家推荐一款简单漂亮的WPF UI&#xff0c;融合多个开源框架组件&#xff1a;AduSkin。 WPF是什么&#xff1f; WPF 是一个强大的桌面应用程序框架&#xff0c;用于构建具有丰富用户界面的 Windo…

大模型基础——大模型范式

大模型背后的范式 整个预训练语言模型的使用范式&#xff1a; 对于预训练模型&#xff0c;最核心的要素是从无标注的数据中去学习&#xff0c;通过自监督的一些任务去做预训练&#xff0c;得到丰富的知识。在具体的应用中&#xff0c;会引入一些任务相关的数据&#xff0c;去调…

C++学习之多态详解

目录 多态的实现 例题 重载 重写 重定义的区别 抽象类 多态实现原理 多态的实现 C中的多态是指&#xff0c;当类之间存在层次结构&#xff0c;并且类之间是通过继承关联时&#xff0c;就会用到多态。多态意味着调用成员函数时&#xff0c;会根据调用函数的对象的类型来执…

万能DIY预约小程序源码系统 上门预约服务小程序搭建 适用于各种预约场景 自由DIY功能模块

在这个快节奏的时代&#xff0c;预约服务已经成为了我们日常生活的一部分。从看病挂号到餐厅预订&#xff0c;从美发美容到家政服务&#xff0c;预约已经深入到了各个领域。然而&#xff0c;传统的预约方式存在着许多不便&#xff0c;如电话预约、在线填写表格等&#xff0c;不…

MyBatis Generator - 快速生成 实体类 和 映射文件

目录 一、MyBatis Generator 的使用 1.1、生成类和映射文件 1.1.1、在 pom.xml 中引入依赖 1.1.2、根据 configurationFile 标签中配置的路径 创建 generatorConfig.xml 文件 1.1.3、自动生成类 和 映射文件 1.1.4、在 Insert 标签中添加获取主键值的选项 1.1.5、扫描配置…

Linux编译器-gcc/g++使用函数库

【Linux】系列文章目录 【Linux】基础常见指令&#xff1a;http://t.csdn.cn/hwLPb 【Linux】基本权限&#xff1a;http://t.csdn.cn/faFZg 【Linux】软件包管理器yum与环境开发工具vim&#xff1a;http://t.csdn.cn/LEqkm 目录 【Linux】系列文章目录 前言 一、gcc选项总…

Android Update Engine 分析(二十四)制作降级包时,到底发生了什么?

文章目录 0. 导读1. 关于升级和降级的本质2. Android 如何制作降级包?3. Android 降级流程全追踪1. 使用 "--downgrade" 选项制作降级包2. OPTIONS.downgrade = True 选项3. OPTIONS.wide_user_data = True 选项4. 制作差分包对时间戳的检查5. 总结6. 思考题7. 其它…