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内存泄漏的原因? 改进和优化 cleanSomeSlots方法 expungeStaleEntry方法 replaceStaleEntry方法 为什么使用弱引用? Thread.exit() ThreadLocal内存泄漏最佳解决方案 在使用完毕后立即清理ThreadLocal 使用InheritableThreadL…...
js实现红包雨功能(canvas,react,ts),包括图片不规则旋转、大小、转速、掉落速度控制、屏幕最大红包数量控制等功能
介绍 本文功能由canvas实现红包雨功能(index.tsx)本文为react的ts版。如有其他版本需求可评论区观赏地址,需过墙 import React, { Component } from react; // import ./index.css; import moneyx from /assets/images/RedEnvelopeRain/bal…...
【数字IC设计/FPGA】FIFO与流控机制
流控,简单来说就是控制数据流停止发送。常见的流控机制分为带内流控和带外流控。 FIFO的流水反压机制 一般来说,每一个fifo都有一个将满阈值afull_value(almost full)。当fifo内的数据量达到或超过afull_value时,将满…...
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 的主要属性 在前文中,我们大致了解了 Mat 的基本结构以及它的创建与赋值。接下来我们通过一个例子,来看看 Mat 所包含的常用属性。 先创建一个 3*4 的四通道的矩阵,并打印出其相关的属性,稍后会详细…...
基于指数分布优化的BP神经网络(分类应用) - 附代码
基于指数分布优化的BP神经网络(分类应用) - 附代码 文章目录 基于指数分布优化的BP神经网络(分类应用) - 附代码1.鸢尾花iris数据介绍2.数据集整理3.指数分布优化BP神经网络3.1 BP神经网络参数设置3.2 指数分布算法应用 4.测试结果…...
Python--练习:使用while循环求1~100之间,所有偶数的和(涉及if判断是不是偶数)
案例:求1~100之间,所有偶数的和 思考: 先套用原有基础模式,之后再思考其他的。 其实就是在之前文章 Python--练习:使用while循环求1..100的和-CSDN博客 的基础上,再判断如果获取到里面的全部偶数&#…...
带温度的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 中,函数可以通过不同的方式进行调用。以下是常见的几种函数调用方式: 函数调用:使用函数名称后跟一对小括号来调用函数,这是最基本的调用方式。 functionName(); 方法调用:函数可以作为对象的方法进行调…...
聊聊设计模式--简单工厂模式
简单工厂模式 前面也学了很多各种微服务架构的组件,包括后续的服务部署、代码管理、Docker等技术,那么作为后端人员,最重要的任务还是代码编写能力,如何让你的代码写的漂亮、易扩展,让别人一看赏心悦目,…...
Python基础教程:内置函数之字典函数的使用方法
嗨喽~大家好呀,这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 len(字典名): 返回键的个数,即字典的长度 # len(字典名): # 返回键的个数,即字典的长度dic {a:123,b:456,c:789…...
Pytorch从零开始实战06
Pytorch从零开始实战——明星识别 本系列来源于365天深度学习训练营 原作者K同学 文章目录 Pytorch从零开始实战——明星识别环境准备数据集模型选择开始训练模型可视化模型预测总结 环境准备 本文基于Jupyter notebook,使用Python3.8,Pytorch2.0.1c…...
RT-Thread学习笔记(三):线程管理
线程管理 线程管理相关概念什么是时间片轮转调度器锁线程运行机制线程的五种状态 动态和静态创建线程区别动态和静态创建线程优缺点RT-Thread动态线程管理函数动态创建线程动态删除线程 RT-Thread静态线程管理函数静态创建线程 线程其他操作线程启动线程延时获得当前执行的线程…...
pymysql连接Mariadb/Mysql出现错误(配置正确情况下)解决办法
场景:在kali中使用python中pymysql对Mariadb进行连接,在整个过程中配置全部正确,但是就是无法进行连接,提示结果如下: Access denied for user rootlocalhost解决办法:进入数据库中,将默认密码…...
数据仓库扫盲系列(1):数据仓库诞生原因、基本特点、和数据库的区别
数据仓库的诞生原因 随着互联网的普及,信息技术已经深入到各行各业,并逐步融入到企业的日常运营中。然而,当前企业在信息化建设过程中遇到了一些困境与挑战。 1、历史数据积存。 过去企业的业务系统往往是在较长时间内建设的,很…...
DataX-web安装部署和使用
DataX-web的环境准备 MySQL (5.5) 必选,对应客户端可以选装, Linux服务上若安装mysql的客户端可以通过部署脚本快速初始化数据库 JDK (1.8.0_xxx) 必选 DataX 必选 Python (2.x) (支持Python3需要修改替换datax/bin下面的三个python文件,替换文件在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++运动会计分系统 期末设计源码
文章目录 题目介绍功能源码效果展示带报告(内容) 题目介绍 使用语言: 两个版本都会发: 版本1:C语言 版本2: C 代码量: 500 题目介绍: 要求:初始化输入: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 提供的一个状态管理钩子,通常用于管理组件的复杂状态逻辑。它采用两个参数:reducer 函数和初始状态。Reducer 函数接受当前状态和一个操作(action),并返回一个新的状态。这有点…...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...
学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...
