当前位置: 首页 > 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;并返回一个新的状态。这有点…...

基于服务器使用 apt 安装、配置 Nginx

&#x1f9fe; 一、查看可安装的 Nginx 版本 首先&#xff0c;你可以运行以下命令查看可用版本&#xff1a; apt-cache madison nginx-core输出示例&#xff1a; nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

Objective-C常用命名规范总结

【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名&#xff08;Class Name)2.协议名&#xff08;Protocol Name)3.方法名&#xff08;Method Name)4.属性名&#xff08;Property Name&#xff09;5.局部变量/实例变量&#xff08;Local / Instance Variables&…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

GitHub 趋势日报 (2025年06月08日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

leetcodeSQL解题:3564. 季节性销售分析

leetcodeSQL解题&#xff1a;3564. 季节性销售分析 题目&#xff1a; 表&#xff1a;sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

自然语言处理——Transformer

自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效&#xff0c;它能挖掘数据中的时序信息以及语义信息&#xff0c;但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN&#xff0c;但是…...

让AI看见世界:MCP协议与服务器的工作原理

让AI看见世界&#xff1a;MCP协议与服务器的工作原理 MCP&#xff08;Model Context Protocol&#xff09;是一种创新的通信协议&#xff0c;旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天&#xff0c;MCP正成为连接AI与现实世界的重要桥梁。…...

鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南

1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发&#xff0c;使用DevEco Studio作为开发工具&#xff0c;采用Java语言实现&#xff0c;包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...