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

news/2024/11/17 6:43:53/

前言

        在分析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为线程变量的副本。这些对象之间的引用关系如下。

 

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

内存泄漏原因

        从上图中可以看出,hreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有thead线程退出以后,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.ppmy.cn/news/1022599.html

相关文章

无涯教程-Perl - getpwnam函数

描述 此函数基于EXPR指定的用户名,从/etc/passwd文件提取的列表context中返回字段列表。通常这样使用- ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell) getpwnam($user); 在标量context中,返回数字用户ID。如果尝试访问整个/etc/passwd文件,则应使用getpwent…

解决VScode远程服务器时opencv和matplotlib无法直接显示图像的问题

解决VScode远程服务器时opencv和matplotlib无法直接显示图像的问题 1、本方案默认本地已经安装了VScode与MobaXterm2、在服务器端3、在本地端安装MobaXterm4、测试5、opencv显示测试&#xff08;测试过程中需保持MobaXterm开启的状态&#xff09;6、 matplotlib显示测试&#x…

vs2022+qt6.24+Cef编译

1.QCefView源码下载地址 https://github.com/cefview/qcefview2.目录层级关系如下&#xff1a; 3.下载CefViewCore git pull --regit pull --recurse-submodules上面命令失败直接用下面的命令 git clone gitgithub.com:CefView/CefViewCore.git4.编译QCefView准备工作 a.准…

(四)ESP32基于MicroPython平台——驱动TFT-1.44寸屏(SPI)

一. 所需器件工具 1.ESP32-CAM开发板。开发板购买链接 2.TFT-1.44寸屏。TFT-1.44寸屏购买链接 二. 硬件SPI接口简介 有两个硬件SPI通道允许更快的传输速率&#xff08;最高80Mhz&#xff09;。 HSPI (id1) VSPI (id2) sck 14 18 mosi 13 23 miso 12 19 三. TFT-1.4…

【torch.nn : Pooling Layers】

文章目录 MaxPool2dAvgPool2dAdaptiveAvgPool2dMaxUnpool2d MaxPool2d CLASS torch.nn.MaxPool2d(kernel_size, strideNone, padding0, dilation1, return_indicesFalse, ceil_modeFalse)功能&#xff1a; 在由几个输入平面组成的输入信号上应用2D最大池化。 举个简单的例子&a…

YOLOv5、YOLOv8改进:添加ShuffleAttention注意力机制

广泛应用的注意力机制主要有空间注意力机制和通道注意力机制&#xff0c;其目的分别是捕捉像素级的成对关系和通道依赖关系。虽然将两种机制融合在一起可以获得比单独更好的性能&#xff0c;但计算开销不可避免。因而&#xff0c;本文提出Shuffle Attetion&#xff0c;即SA&…

[保研/考研机试] KY87 鸡兔同笼 北京大学复试上机题 C++实现

描述 一个笼子里面关了鸡和兔子&#xff08;鸡有2只脚&#xff0c;兔子有4只脚&#xff0c;没有例外&#xff09;。已经知道了笼子里面脚的总数a&#xff0c;问笼子里面至少有多少只动物&#xff0c;至多有多少只动物。 输入描述&#xff1a; 每组测试数据占1行&#xff0c;…

2023年的深度学习入门指南(25) - 通义千问7b

2023年的深度学习入门指南(25) - 通义千问7b 最近发生的两件事情都比较有意思&#xff0c;一个是连续开源了7b和13b模型的百川&#xff0c;对其53b闭源了&#xff1b;另一个是闭源项目通义千问开源了自己的7b模型。 下面我们就来研究下通义千问7b. 使用通义千问7b 首先安装…