当前位置: 首页 > news >正文

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和使用弱引用等多种解决方案。在具体场景中,可以根据实际情况选择最佳的解决方案。

相关文章:

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

目录 ThreadLocal内存泄漏的原因&#xff1f; 改进和优化 cleanSomeSlots方法 expungeStaleEntry方法 replaceStaleEntry方法 为什么使用弱引用&#xff1f; Thread.exit() ThreadLocal内存泄漏最佳解决方案 在使用完毕后立即清理ThreadLocal 使用InheritableThreadL…...

js实现红包雨功能(canvas,react,ts),包括图片不规则旋转、大小、转速、掉落速度控制、屏幕最大红包数量控制等功能

介绍 本文功能由canvas实现红包雨功能&#xff08;index.tsx&#xff09;本文为react的ts版。如有其他版本需求可评论区观赏地址&#xff0c;需过墙 import React, { Component } from react; // import ./index.css; import moneyx from /assets/images/RedEnvelopeRain/bal…...

【数字IC设计/FPGA】FIFO与流控机制

流控&#xff0c;简单来说就是控制数据流停止发送。常见的流控机制分为带内流控和带外流控。 FIFO的流水反压机制 一般来说&#xff0c;每一个fifo都有一个将满阈值afull_value&#xff08;almost full&#xff09;。当fifo内的数据量达到或超过afull_value时&#xff0c;将满…...

C++笔记之遍历vector的所有方式

C笔记之遍历vector的所有方式 —— 2023年4月15日 上海 code review 文章目录 C笔记之遍历vector的所有方式1.普通for循环2.迭代器版3.const迭代器4.C11引入的范围for循环5.使用auto关键字和迭代器6.使用std::for_each算法7.使用std::for_each和lambda表达式8.普通版vector::at…...

OpenCV 笔记(2):图像的属性以及像素相关的操作

Part11. 图像的属性 11.1 Mat 的主要属性 在前文中&#xff0c;我们大致了解了 Mat 的基本结构以及它的创建与赋值。接下来我们通过一个例子&#xff0c;来看看 Mat 所包含的常用属性。 先创建一个 3*4 的四通道的矩阵&#xff0c;并打印出其相关的属性&#xff0c;稍后会详细…...

基于指数分布优化的BP神经网络(分类应用) - 附代码

基于指数分布优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于指数分布优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.指数分布优化BP神经网络3.1 BP神经网络参数设置3.2 指数分布算法应用 4.测试结果…...

Python--练习:使用while循环求1~100之间,所有偶数的和(涉及if判断是不是偶数)

案例&#xff1a;求1~100之间&#xff0c;所有偶数的和 思考&#xff1a; 先套用原有基础模式&#xff0c;之后再思考其他的。 其实就是在之前文章 Python--练习&#xff1a;使用while循环求1..100的和-CSDN博客 的基础上&#xff0c;再判断如果获取到里面的全部偶数&#…...

带温度的softmax

用pytorch写一下使用带有温度的softmax的demo import torch import torch.nn.functional as F# 定义带有温度的softmax函数 def temperature_softmax(logits, temperature1.0):return F.softmax(logits / temperature, dim-1)# 输入logits logits torch.tensor([[1.0, 2.0, 3.…...

js函数调用的方式有几种

在 JavaScript 中&#xff0c;函数可以通过不同的方式进行调用。以下是常见的几种函数调用方式&#xff1a; 函数调用&#xff1a;使用函数名称后跟一对小括号来调用函数&#xff0c;这是最基本的调用方式。 functionName(); 方法调用&#xff1a;函数可以作为对象的方法进行调…...

聊聊设计模式--简单工厂模式

简单工厂模式 ​ 前面也学了很多各种微服务架构的组件&#xff0c;包括后续的服务部署、代码管理、Docker等技术&#xff0c;那么作为后端人员&#xff0c;最重要的任务还是代码编写能力&#xff0c;如何让你的代码写的漂亮、易扩展&#xff0c;让别人一看赏心悦目&#xff0c…...

Python基础教程:内置函数之字典函数的使用方法

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 len(字典名)&#xff1a; 返回键的个数&#xff0c;即字典的长度 # len(字典名)&#xff1a; # 返回键的个数&#xff0c;即字典的长度dic {a:123,b:456,c:789…...

Pytorch从零开始实战06

Pytorch从零开始实战——明星识别 本系列来源于365天深度学习训练营 原作者K同学 文章目录 Pytorch从零开始实战——明星识别环境准备数据集模型选择开始训练模型可视化模型预测总结 环境准备 本文基于Jupyter notebook&#xff0c;使用Python3.8&#xff0c;Pytorch2.0.1c…...

RT-Thread学习笔记(三):线程管理

线程管理 线程管理相关概念什么是时间片轮转调度器锁线程运行机制线程的五种状态 动态和静态创建线程区别动态和静态创建线程优缺点RT-Thread动态线程管理函数动态创建线程动态删除线程 RT-Thread静态线程管理函数静态创建线程 线程其他操作线程启动线程延时获得当前执行的线程…...

pymysql连接Mariadb/Mysql出现错误(配置正确情况下)解决办法

场景&#xff1a;在kali中使用python中pymysql对Mariadb进行连接&#xff0c;在整个过程中配置全部正确&#xff0c;但是就是无法进行连接&#xff0c;提示结果如下&#xff1a; Access denied for user rootlocalhost解决办法&#xff1a;进入数据库中&#xff0c;将默认密码…...

数据仓库扫盲系列(1):数据仓库诞生原因、基本特点、和数据库的区别

数据仓库的诞生原因 随着互联网的普及&#xff0c;信息技术已经深入到各行各业&#xff0c;并逐步融入到企业的日常运营中。然而&#xff0c;当前企业在信息化建设过程中遇到了一些困境与挑战。 1、历史数据积存。 过去企业的业务系统往往是在较长时间内建设的&#xff0c;很…...

DataX-web安装部署和使用

DataX-web的环境准备 MySQL (5.5) 必选&#xff0c;对应客户端可以选装, Linux服务上若安装mysql的客户端可以通过部署脚本快速初始化数据库 JDK (1.8.0_xxx) 必选 DataX 必选 Python (2.x) (支持Python3需要修改替换datax/bin下面的三个python文件&#xff0c;替换文件在do…...

sqlmap防御以及文件读写

一.防御 过滤 1.使用过滤函数 $email filter_var($_POST[email], FILTER_VALIDATE_EMAIL); if ($email) { // input is a valid email address } else { // input is not a valid email address 使用 filter_var() 函数和 FILTER_VALIDATE_EMAIL 过滤器来验证用户输…...

【源码】C/C++运动会计分系统 期末设计源码

文章目录 题目介绍功能源码效果展示带报告&#xff08;内容&#xff09; 题目介绍 使用语言&#xff1a; 两个版本都会发&#xff1a; 版本1&#xff1a;C语言 版本2&#xff1a; C 代码量&#xff1a; 500 题目介绍&#xff1a; 要求&#xff1a;初始化输入&#xff1a;N-参赛…...

Ubuntu安装Docker

卸载官方库中之前的旧版本 sudo apt-get remove docker docker-engine docker-ce docker.io更新安装包列表 sudo apt-get update安装以下包以使apt可以通过HTTPS使用存储库 sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common添…...

useReducer+createContext真的可以代替Redux吗?

概念 useReducer useReducer 是 React 提供的一个状态管理钩子&#xff0c;通常用于管理组件的复杂状态逻辑。它采用两个参数&#xff1a;reducer 函数和初始状态。Reducer 函数接受当前状态和一个操作&#xff08;action&#xff09;&#xff0c;并返回一个新的状态。这有点…...

AI专著撰写必备!实测8款AI工具,快速生成20万字专著且低查重率!

研究者专著创作困境与AI工具解决方案 对于多数研究者而言&#xff0c;编写学术专著常常面临最大的挑战&#xff0c;便是“有限的精力”与“无限的需求”之间的矛盾。撰写一本专著通常需要3到5年&#xff0c;甚至更长的时间&#xff0c;而研究者们平常还需要完成教学、科研项目…...

别再硬扛期刊论文了!Paperxie 这四步,帮你把 “难产稿” 变成投稿通关文

paperxie-免费查重复率aigc检测/开题报告/毕业论文/智能排版/文献综述/期刊论文https://www.paperxie.cn/ai/journalArticleshttps://www.paperxie.cn/ai/journalArticles 你有没有过这种时刻&#xff1f;对着空白文档发呆三小时&#xff0c;期刊论文的标题都定不下来&#xf…...

LxgwNeoXiHei特殊符号支持:易经、麻将、象棋符号全解析

LxgwNeoXiHei特殊符号支持&#xff1a;易经、麻将、象棋符号全解析 【免费下载链接】LxgwNeoXiHei A Chinese sans-serif font derived from IPAex Gothic. 一款衍生于「IPAexゴシック」的中文黑体字型。 项目地址: https://gitcode.com/gh_mirrors/lx/LxgwNeoXiHei Lxg…...

技术债务灾难:行业集体埋雷

冰山之下&#xff0c;测试之困在追求敏捷与快速交付的软件开发现代洪流中&#xff0c;“技术债务”已从一个晦涩的工程隐喻&#xff0c;演变为悬在无数项目头顶的达摩克利斯之剑。对于身处质量保障一线的软件测试从业者而言&#xff0c;技术债务远非开发团队的内部烦恼&#xf…...

DayZ社区离线模组:如何打造完全掌控的单机生存体验?

DayZ社区离线模组&#xff1a;如何打造完全掌控的单机生存体验&#xff1f; 【免费下载链接】DayZCommunityOfflineMode A community made offline mod for DayZ Standalone 项目地址: https://gitcode.com/gh_mirrors/da/DayZCommunityOfflineMode 厌倦了网络延迟、服务…...

DDrawCompat完整指南:3步解决Windows经典游戏兼容性问题 [特殊字符]

DDrawCompat完整指南&#xff1a;3步解决Windows经典游戏兼容性问题 &#x1f3ae; 【免费下载链接】DDrawCompat DirectDraw and Direct3D 1-7 compatibility, performance and visual enhancements for Windows Vista, 7, 8, 10 and 11 项目地址: https://gitcode.com/gh_m…...

车载场景问答准确率从63%跃升至91.7%:Dify动态上下文管理与多模态指令微调实战手记(含CAN总线语义注入代码)

第一章&#xff1a;车载场景问答准确率从63%跃升至91.7%&#xff1a;Dify动态上下文管理与多模态指令微调实战手记&#xff08;含CAN总线语义注入代码&#xff09;在智能座舱真实部署环境中&#xff0c;原始基于静态Prompt的问答系统在车载多轮对话中表现乏力——语音打断、CAN…...

GetQzonehistory:3步完成QQ空间历史说说一键导出备份指南

GetQzonehistory&#xff1a;3步完成QQ空间历史说说一键导出备份指南 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 你是否曾担心QQ空间中的珍贵回忆会随着时间流逝而消失&#xff1f;…...

PCIe 3.0信号完整性深度优化:除了100欧姆差分阻抗,这些细节才是性能关键

PCIe 3.0信号完整性深度优化&#xff1a;除了100欧姆差分阻抗&#xff0c;这些细节才是性能关键 在高速数字电路设计中&#xff0c;PCIe 3.0接口的信号完整性优化一直是硬件工程师面临的挑战。虽然大多数工程师都熟悉100欧姆差分阻抗的基本要求&#xff0c;但真正决定系统稳定性…...

告别模拟器:用Termux+Ubuntu+JDK在安卓手机上搭建轻量Java开发环境

安卓手机变身Java开发机&#xff1a;TermuxUbuntuJDK全栈解决方案 在咖啡馆等朋友时突然需要调试一段业务逻辑代码&#xff0c;出差途中发现线上服务报错需要紧急修复&#xff0c;通勤路上想继续昨晚未完成的算法练习——这些场景下&#xff0c;我们往往懊恼没带笔记本电脑。其…...