面试:了解 ThreadLocal 内存泄漏需要满足的 2 个条件吗?

欢迎关注公众号 【11来了】(文章末尾即可扫码关注) ,持续 中间件源码、系统设计、面试进阶相关内容

在我后台回复 「资料」 可领取 编程高频电子书!
在我后台回复「面试」可领取 30w+ 字的硬核面试笔记!
示例图片
感谢你的关注!

面试:了解 ThreadLocal 内存泄漏需要满足的 2 个条件吗?

什么是 ThreadLocal?

ThreadLocal 用于存储线程本地的变量,如果创建了一个 ThreadLocal 变量,在多线程环境下访问这个变量的时候,每个线程都会在自己线程的本地内存中创建一份变量的副本,从而起到 线程隔离 的作用

Thread、ThreadLocal、ThreadLocalMap 之间的关系

1706850199755

每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程所有的 ThreadLocal 对象及其对应的值

ThreadLocalMap由一个个的Entry<key,value>对象构成,Entry继承自weakReference<ThreadLocal<?>>,一个EntryThreadLocal对象和Object构成

  • Entry 的 key 是ThreadLocal对象,并且是一个弱引用。当指向key的强引用消失后,该key就会被垃圾收集器回收
  • Entry 的 value 是对应的变量值,Object 对象

当执行set方法时,ThreadLocal首先会获取当前线程 Thread 对象,然后获取当前线程的ThreadLocalMap对象,再以当前ThreadLocal对象作为key,设置对应的 value。

由于每一条线程均含有各自私有的 ThreadLocalMap 对象,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也就无需使用同步机制来保证多条线程访问容器的互斥性

ThreadLocal 使用场景

1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传送,打破层次间的约束。

即如果一个User对象需要从Controller层传到Service层再传到Dao层,那么把User放在ThreadLocal中,每次使用ThreadLocal来进行获取即可

2、线程间数据隔离

3、进行事务操作,用于存储线程事务信息

4、数据库连接,Session会话管理

ThreadLocal 的内存泄漏问题

先说一下什么情况下会发生内存泄漏,需要满足 2 个条件:

  • 线程内部的 ThreadLocalMap 存储的数据一直未被清理
  • 线程持续存活(线程处在线程池中),导致线程内部的 ThreadLocalMap 对象一直未被回收

接下来说一下什么时候,会符合上边的两个条件,首先对于 第一个条件 来说,有两种情况:ThreadLocal 定义为局部变量、ThreadLocal 定义为全局静态变量

ThreadLocal 被定义为局部变量

当 ThreadLocal 被定义为方法中的 局部变量 ,那么当线程进入该方法的时候,就会将 ThreadLocal 的引用给加载到线程的

如下图所示,在线程栈 Stack 中,有两个变量,ThreadLocalRef 和 CurrentThreadRef,分别指向了声明的局部变量 ThreadLocal ,以及当前执行的线程内部的 ThreadLocalMap 变量

image-20241019135606046

当线程执行完该方法之后,就会将该方法局部变量从栈中删除,因此Stack 线程栈中的 ThreadLocalRef 变量就会被弹出栈,因此 ThreadLocal 变量的强引用消失了,那么 ThreadLocal 变量只有 Entry 中的 key 对他引用,并且还是弱引用,因此这个 ThreadLocal 变量会被垃圾回收器给回收掉,导致 Entry 中的 key 为 null,而 value 还指向了对 Object 的强引用,因此 value 还一直存在 ThreadLocalMap 变量中,如下图:

image-20241019140113170

此时可以看到,ThreadLocalMap 内部 Entry 的 key(ThreadLocal)为 null,因此无法通过 key 去访问到这个 value,导致这个 value 一直无法被回收

因此 JDK 设计者在设计 ThreadLocal 时还添加了清除 ThreadLocalMap 中 key 为 null 的 value,避免内存泄漏,这是在设计时为了避免内存泄漏而采取的措施,而我们使用的时候要保持良好的编程规范,也要手动去 remove,避免内存泄露的发生

ThreadLocal 被定义为全局静态变量

如果定义 ThreadLocal 为 private static final ,那么这个 ThreadLocal 就会在常量池中存储,而不是存储在堆中,因此 ThreadLocal 并不会被回收,也就不会出现 ThreadLocalMap 的 Entry 中 key == null 的情况

这时候要考虑的问题是当前线程在使用完 ThreadLocal 之后要主动 remove,避免数据一直存储在对应的 ThreadLocalMap 中,从而出现脏数据以及内存泄漏

接下来说一下发生内存泄漏需要满足的第二个条件:线程持续存活

在梳理第一个条件(ThreadLocalMap 中的数据未被清理)时,有两种情况,一种是 ThreadLocal 被定义为局部变量,另一种是 ThreadLocal 被定义为全局静态变量

  • 当 ThreadLocal 被定义为局部变量时,会出现 ThreadLocalMap 中 key == null 的情况,在 JDK 内部会主动清理 key == null 的value,因此这种情况不会出现内存泄漏

  • 当 ThreadLocal 被定义为全局静态变量时,此时 ThreadLocalMap 内部的 key 永远不会被回收,因此如果使用之后不手动 remove 对应变量,就会导致对应的值一直存活在当前线程中,如果此时再满足第二个条件 线程持续存活 ,就会导致对应的值一直不会被回收,出现内存泄漏

当使用 线程池 执行任务时, 核心线程会一直存活 ,就会导致该线程内部的 ThreadLocalMap 变量不会清理,从而导致对应的数据一直在内存中存活, 出现内存泄漏问题 ,因此在使用 ThreadLocal 是一定要遵守正确的使用规范,避免出现内存泄漏

ThreadLocal 使用规范

ThreadLocal 正确的使用方法:

  • 将 ThreadLocal 变量定义成 private static final,这样就一直存在 ThreadLocal 的强引用,也能保证任何时候都能通过 ThreadLocal 的访问到 Entry 的 value 值,进而清除掉
  • 每次使用完 ThreadLocal 都调用它的 remove() 方法清除数据

下面给出 ThreadLocal 的用法:

public class ThreadLocalExample {
    private static final ThreadLocal<Integer> counter = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                int value = counter.get(); // 获取当前线程的副本值
                counter.set(value + 1); // 修改副本值
                System.out.println("Thread " + Thread.currentThread().getName() + " value: " + counter.get());
            } finally {
                // 手动移除
                counter.remove(); // 在线程结束时移除变量
            }
        });

        Thread t2 = new Thread(() -> {
            try {
                int value = counter.get();
                counter.set(value + 1);
                System.out.println("Thread " + Thread.currentThread().getName() + " value: " + counter.get());
            } finally {
                // 手动移除
                counter.remove();
            }
        });

        t1.start();
        t2.start();
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vim工具使用( Linux 网络操作系统 07)

1 概念部分 vim是vimsual interface的简称&#xff0c;它可以执行输出、删除、查找、替换、块操作等众多文本操作&#xff0c;而且用户可以根据自己的需要对其进行定制。这是其他编辑程序所没有的。vim不是一个排版程序&#xff0c;它不像Word或WPS那样可以对字体、格式、段落…

记录 ruoyi-vue-plus在linux 部署遇到的问题

整理 linux 文件不要放在 /, 根目录下&#xff0c;要放在 home 文件夹下。docker 启动mysql 容器&#xff0c;映射的 my.cnf 文件不能设置太高权限&#xff0c;权限太高有安全问题&#xff0c;无法读取。 linux 使用注意事项 docker 文件夹 部署在home文件夹下 总结学习到的…

linux的随机化处理

Linux的随机化处理&#xff08;或称为地址空间布局随机化&#xff0c;ASLR&#xff09;是一种安全特性&#xff0c;旨在提高系统对抗攻击的能力&#xff0c;尤其是缓冲区溢出和代码注入攻击。随机化处理通过改变进程在内存中的布局来减少攻击者利用漏洞的可能性。 随机化处理的…

SOLIDWORKS参数化软件

在产品设计和工程领域&#xff0c;参数化设计是一种革命性的方法&#xff0c;它允许设计者通过定义一系列规则和关系来创建和修改模型。参数化设计的核心在于将设计过程分解为一系列可调整的参数&#xff0c;如尺寸、形状、材料属性等&#xff0c;这些参数之间通过数学关系相互…

【北京迅为】《STM32MP157开发板嵌入式开发指南》- 第四十九章 平台总线总结回顾

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器&#xff0c;既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构&#xff0c;主频650M、1G内存、8G存储&#xff0c;核心板采用工业级板对板连接器&#xff0c;高可靠&#xff0c;牢固耐…

Anaconda虚拟环境安装cuda和pytorch

首先电脑上要有Anaconda&#xff0c;使用conda创建一个虚拟环境,并激活 conda create yolov8 conda activate yolov8winR输入cmd&#xff0c;在命令窗口输入 NVIDIA-smi可以查看到自己电脑支持的cuda环境&#xff0c;如下图 再打开torch的官网 pytorch官网 查看目前支持的版…

C++ MFC 标准库 加密解密解惑

&#x1f3c6;本文收录于《全栈Bug调优(实战版)》专栏&#xff0c;主要记录项目实战过程中所遇到的Bug或因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&am…

第五届光学与图像处理国际学术会议(ICOIP 2025)征稿中版面有限!

第五届光学与图像处理国际学术会议&#xff08;ICOIP 2025&#xff09; 2025 5th International Conference on Optics and Image Processing (ICOIP 2025&#xff09; 重要信息 时间地点&#xff1a;2025年4月25-27日丨中国西安 截稿日期&#xff1a;2024年12月16日23:59 …

Java项目-基于Springboot的应急救援物资管理系统项目(源码+说明).zip

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 开发运行环境 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/…

【数据结构与算法】力扣 54. 螺旋矩阵

问题描述 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a; matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a; [1,2,3,6,9,8,7,4,5]示例 2&#xff1a; 输入&#xff1a; ma…

【C++】deque(空间适配器))

适配器 适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结)&#xff0c;该种模式是将一个类的接口转换成客户希望的另外一个接口。 STL标准库中stack和queue的底层结构 deque原理介绍 deque(双端队列)&#xff1a;是一种…

Visual Studio2022 无法打开源文件

今天在新电脑上安装了Visual Studio2022&#xff0c;但是无法打开多个源文件&#xff0c;网上搜索都是说重新安装Windows SDK 我最开始安装的sdk版本是win11SDK 修改成其他版本后也没有解决问题 最后安装了Windows 10 SDK 10.0.20348.0 这个版本&#xff0c;成功解决问题。 有…

快速了解kubernetes中的存储管理

目录 一 configmap 1.1 configmap的功能 1.2 configmap的使用场景 1.3 configmap创建方式 1.3.1 字面值创建 1.3.2 通过文件创建 1.3.3 通过目录创建 1.3.4 通过yaml文件创建 1.3.5 configmap的使用方式 1.3.5.1 使用configmap填充环境变量 1.3.5.2 通过数据卷使用c…

大数据-173 Elasticsearch 索引操作 增删改查 详细 JSON 操作

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

【ESP32-IDFV5.3.1开发】带SSL的MQTT-demo连接教程

目录 1.VSCODE以及IDF环境配置(略) 2.准备demo 2.1打开VSCODE,主菜单创建示例 找到SSL对应demo,点击创建,并成功创建项目,点击编译,显示编译成功即可以下一步。 确认该demo支持的开发板是你手上的开发板 3.修改demo配置项 3.1插上开发板,点击底部开发工具,分别配置好烧…

Maya---骨骼绑定

调节骨骼大小 回车键确认骨骼 FK子集跟父集走 IK子集不跟父集走 前视图中按shift键添加骨骼 清零、删除历史记录&#xff0c;创建新的物体

Go 设置并发控制数量 【go并发模型】

背景&#xff1a;go的并发控制也是老生常谈&#xff0c;在公司业务中也是经常出现 谈谈我们这次并发模型的适用场景&#xff1a;要处理的任务很多比如有10000个&#xff0c;没开并发的时候我们要一个一个进行执行这个时候其实无论是cpu压力还是数据库和redis压力都比较小也就是…

CTFHUB技能树之HTTP协议——响应包源代码

开启靶场&#xff0c;打开链接&#xff1a; 是个贪吃蛇小游戏&#xff0c;看不出来有什么特别的地方 用burp抓包看看情况&#xff1a; 嗯&#xff1f;点击“开始”没有抓取到报文&#xff0c;先看看网页源代码是什么情况 居然直接给出flag了&#xff0c;不知道这题的意义何在 …

C++初阶(五)--类和对象(中)--默认成员函数

目录 一、默认成员函数&#xff08;Default Member Functions&#xff09; 二、构造函数&#xff08; Constructor&#xff09; 1.构造函数的基本概念 2.构造函数的特征 3.构造函数的使用 无参构造函数 和 带参构造函数 注意事项&#xff1a; 4.默认构造函数 隐式生成的…

023_Layout_and_Containers_in_Matlab界面布局与容器

容器 基于uifigure进行的图形用户界面设计&#xff0c;可以分为以下几种容器类型&#xff1a; 图窗&#xff1a;uifigure布局&#xff1a;uigridlayout面板&#xff1a;uipanel标签页&#xff1a;uitabgroup、uitab 这几个对象除uifigure外相互可以形成嵌套的关系&#xff0…