ThreadLocal的内存泄漏是怎么发生的

前言

        在分析ThreadLocal导致的内存泄露前,需要普及了解一下内存泄露、强引用与弱引用以及GC回收机制,这样才能更好的分析为什么ThreadLocal会导致内存泄露呢?更重要的是知道该如何避免这样情况发生,增强系统的健壮性。

内存泄露

        内存泄露为程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光,

        广义并通俗的说,就是:不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。

强引用与弱引用

强引用

        使用最普遍的引用,一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。

如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象。

弱引用

        JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。

GC回收机制-如何找到需要回收的对象

JVM如何找到需要回收的对象,方式有两种:

  • 引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收,
  • 可达性分析法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。

引用计数法,可能会出现A 引用了 B,B 又引用了 A,这时候就算他们都不再使用了,但因为相互引用 计数器=1 永远无法被回收。 

ThreadLocal的内存泄露分析

        先从前言的了解了一些概念(已懂忽略),接下来我们开始正式的来理解ThreadLocal导致的内存泄露的解析。

实现原理

static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}...}
ThreadLocal实现原理

        每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本。这些对象之间的引用关系如下。

实心箭头表示强引用,空心箭头表示弱引用 

内存泄漏原因

        从上图中可以看出,threadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有thread线程退出以后,value的强引用链条才会断掉。

        但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:

Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value

永远无法回收,造成内存泄漏。

那为什么使用弱引用而不是强引用??

        我们看看Key使用强引用和弱引用分别会有什么问题。

key 使用强引用

        当hreadLocalMap的key为强引用,那么回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal就不会被回收,导致Entry内存泄漏。

key 使用弱引用

        当ThreadLocalMap的key为弱引用,那么回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。回收ThreadLocal后,key为null,在下一次ThreadLocalMap调用set()、get()、remove()方法的时候value值会被清除。

ThreadLocalMap的remove()分析

在这里只分析remove()方式,其他的方法可以查看源码进行分析:

private void remove(ThreadLocal<?> key) {//使用hash方式,计算当前ThreadLocal变量所在table数组位置Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);//再次循环判断是否在为ThreadLocal变量所在table数组位置for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {if (e.get() == key) {//调用WeakReference的clear方法清除对ThreadLocal的弱引用e.clear();//清理key为null的元素expungeStaleEntry(i);return;}}
}

再看看清理key为null的元素expungeStaleEntry(i):

private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;// 根据强引用的取消强引用关联规则,将value显式地设置成null,去除引用tab[staleSlot].value = null;tab[staleSlot] = null;size--;// 重新hash,并对table中key为null进行处理Entry e;int i;for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();//对table中key为null进行处理,将value设置为null,清除value的引用if (k == null) {e.value = null;tab[i] = null;size--;} else {int h = k.threadLocalHashCode & (len - 1);if (h != i) {tab[i] = null;while (tab[h] != null)h = nextIndex(h, len);tab[h] = e;}}}return i;
}

总结

        由于Thread中包含变量ThreadLocalMap,因此ThreadLocalMap与Thread的生命周期是一样长,如果没有手动删除对应key,就会容易导致内存泄漏。

        但是使用弱引用可以多一层保障:弱引用使得ThreadLocal对象不会内存泄漏,而对应的value在下一次ThreadLocalMap调用set(),get(),remove()的时候会被清除。

        因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会容易导致内存泄漏,而不是因为ThreadLocalMap的key是弱引用。

ThreadLocal正确的使用方法

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

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

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

相关文章

chatGPT小白快速入门培训课程-001

一、前言 本文是《chatGPT小白快速入门培训课程》的第001篇文章&#xff0c;全部内容采用chatGPT和chatGPT开源平替软件生成。完整内容大纲详见&#xff1a;《chatGPT小白快速入门课程大纲》。 本系列文章&#xff0c;参与&#xff1a; AIGC征文活动 #AIGC技术创作内容征文# …

win10强制卸载奇安信天擎

1、win r 打开运行 2、输入msconfig进入系统配置面板 3、点击引导&#xff0c;修改安全引导配置项 4、重启系统&#xff08;桌面会变成纯黑背景&#xff0c;符合预期&#xff0c;莫紧张&#xff09; 5、删除安装的文件夹 若是安装天擎时选择的自定义安装&#xff0c;则配置…

基于Selenium技术方案的爬取界面内容实践

1. 定位页面&#xff08;多窗口切换&#xff09; WebDriver提供了处理多个窗口的能力&#xff0c;这是通过使用“WebDriver.switchTo.window()”方法来切换到已知名称的窗口来实现的。如果名称未知&#xff0c;您可以使用“WebDriver.getWindowHandles()”获取已知窗口列表。您…

ffmpeg源码编译成功,但是引用生成的静态库(.a)报错,报错位置在xxx_list.c,报错信息为某变量未定义

背景&#xff1a;本文是对上一个文章的补充&#xff0c;在源码编译之前&#xff0c;项目是有完整的ffmpeg编译脚本的&#xff0c;只不过新增了断点调试ffmpeg&#xff0c;所以产生的上面的文章&#xff0c;也就是说&#xff0c;我在用make编译成功后&#xff0c;再去做的源码编…

【立创EDA】【1】原理图绘制常用操作

总线使用 作用&#xff1a;总线可以方便多个元件的连接总线必须配合总线分支使用原理图上的总线只是视觉上的效果&#xff0c;欲使网络完整&#xff0c;还需添加网络标签&#xff08;如下图&#xff0c;未添加网络标签时&#xff0c;即使总线连接完成&#xff0c;网络依旧不完…

从零到精通:学习这些R语言必学包成为数据分析高手!

一、操作数据 ❝ tidyverse ❞ tidyverse是一个强大的R语言数据分析工具集&#xff0c;包含了多个核心包。其中包括ggplot2用于数据可视化、dplyr用于数据处理和转换、tidyr用于数据整理和重塑、readr用于数据导入和读取、purrr用于函数式编程、stringr用于字符串处理等。 ❝ d…

JavaScript、TypeScript、ES5、ES6之间的联系和区别

ECMAScript&#xff1a; 一个由 ECMA International 进行标准化&#xff0c;TC39 委员会进行监督的语言。通常用于指代标准本身。JavaScript&#xff1a; ECMAScript 标准的各种实现的最常用称呼。这个术语并不局限于某个特定版本的 ECMAScript 规范&#xff0c;并且可能被用于…

5. 服务发现

当主机较少时&#xff0c;在抓取配置中手动列出它们的IP地址和端口是常见的做法&#xff0c;但不适用于较大规模的集群。尤其不适用使用容器和基于云的实例的动态集群&#xff0c;这些实例经常会变化、创建或销毁的情况。 Prometheus通过使用服务发现解决了这个问题&#xff1…

(4)各个属性角色分析显示-4

将折线图、数据集、散点图集合在一个html文件中&#xff1a; &#xff08;1&#xff09;将折线图、数据集、散点图设置为函数a()、b()、c()&#xff0c; &#xff08;2&#xff09;再调用page.add()函数&#xff0c;将三个图片组合在一起 &#xff08;3&#xff09;运行page.…

Ajax 笔记(二)—— Ajax 案例

笔记目录 2. Ajax 综合案例2.1 案例一-图书管理2.1.1 渲染列表2.1.2 新增图书2.1.3 删除图书2.1.4 编辑图书 2.2 案例二-背景图的上传和更换2.2.1 上传2.2.2 更换 2.3 案例三-个人信息设置2.3.1 信息渲染2.3.2 头像修改2.2.3 信息修改2.3.4 提示框 Ajax 笔记&#xff1a; Ajax…

c#在设计时调试自定义 Windows 窗体控件

private string demoStringValue null; [Browsable(true)] public string DemoString {get{return this.demoStringValue;}set{demoStringValue value;} } 参考链接 在设计时调试自定义控件 - Windows Forms .NET Framework | Microsoft Learnhttps://learn.microsoft.com/z…

zookeeper和kafka

目录 一、zookeeper理论 1.1、zookeeper定义 1.2、zookeeper工作机制 1.3、zookeeper特点 1.4、zookeeper的数据结构 1.5、zookeeper应用场景 1.6、zookeeper的选举机制 二、部署Zookeeper 集群 2.1、环境准备 2.2、安装 Zookeeper 2.3、修改配置文件 2.4、配置…

40G光模块在高速网络中的关键角色

40G光模块在高速网络中扮演着关键的角色。它是用于传输数据的光纤通信设备&#xff0c;具备高速数据传输能力&#xff0c;广泛应用于数据中心、电信运营商、企业网络等领域。本文我们来看看40G光模块在高速网络中是如何发挥它的作用的&#xff01; 一、认识40G光模块 为实现4…

Mr. Cappuccino的第59杯咖啡——简单手写SpringIOC框架

简单手写SpringIOC框架 环境搭建基于XML方式项目结构项目代码运行结果 基于注解方式项目结构项目代码运行结果 简单手写SpringIOC框架核心原理基于XML方式原理项目结构项目代码运行结果 基于注解方式原理项目结构项目代码运行结果 环境搭建 基于XML方式 项目结构 项目代码 p…

存储过程的学习

1&#xff0c;前言 这是实习期间学习的&#xff0c;我可能是在学校没好好听课&#xff0c;&#xff08;或者就是学校比较垃&#xff0c;没教这部分&#xff0c;在公司经理让我下去自己学习&#xff0c;太难了&#xff0c;因为是公司代码很多部分都是很多表的操作&#…

Android进阶之SeekBar动态显示进度

SeekBar 在开发中并不陌生,默认的SeekBar是不显示进度的,当然用吐司或者文案在旁边实时显示也是可以的,那能不能移动的时候才显示&#xff0c;默认不显示呢,当然网上花哨的三方类太多了&#xff0c;但是我只是单纯的想在SeekBar的基础上去添加一个可以跟随移动显示的气泡而已~ …

易经学习开篇

前言&#xff1a;傅佩荣讲易经&#xff0c;看视频学习笔记第一篇。主要包括易经的概念、学习易经的意义以及易经的组成。 目录 易经的概念 学习易经的意义 易经的组成 爻 爻位 乘承比应 动爻 卦 八纯卦 结语 易经的概念 广义的易经&#xff0c;指的是三本经书&…

智慧工地源码,Spring Cloud+ Vue+UniApp开发,微服务架构

智慧工地源码&#xff0c;智慧工地云平台源码 智慧工地APP源码 智慧工地的核心是数字化&#xff0c;它通过传感器、监控设备、智能终端等技术手段&#xff0c;实现对工地各个环节的实时数据采集和传输&#xff0c;如环境温度、湿度、噪音等数据信息&#xff0c;将数据汇集到云…

11_Pulsar Adaptors适配器、kafka适配器、Spark适配器

2.3. Pulsar Adaptors适配器 2.3.1.kafka适配器 2.3.2.Spark适配器 2.3. Pulsar Adaptors适配器 2.3.1.kafka适配器 Pulsar 为使用 Apache Kafka Java 客户端 API 编写的应用程序提供了一个简单的解决方案。 在生产者中, 如果想不改变原有kafka的代码架构, 就切换到Pulsar的…

若依部署前后端

打包项目 前端打包 npm run build:prod将代码上传到指定目录 配置nginx转发 server{listen 8090;server_name localhost;location / {root /home/cc_library/dist;index index.html index.htm;# 配置 history模式&#xff0c;刷新页面会404&#xff0c;&#xff0c;因为服…