首页 > 系统相关 >ThreadLocal是否存在内存泄漏问题,如何防止内存泄漏

ThreadLocal是否存在内存泄漏问题,如何防止内存泄漏

时间:2023-05-28 22:46:10浏览次数:48  
标签:泄漏 Thread 回收 ThreadLocal 线程 内存 null

ThreadLocal还是不能百分百地让程序员避免内存泄露,如果程序员不谨慎就很可能导致

内存泄露?那么今天我们就来聊聊什么样的情况ThreadLocal不会出现内存泄露?什么样的情况会出现内存泄露?我们如何防止内存泄露的情况发生呢?

我们这节就会为同学们一一详细解答,那我们先来简单回忆一下ThreadLocal的底层实现,请看下图图1

图1

 

通过图1我们能清晰的看到JDK的线程本地化ThreadLocal是存储在每个线程Thread内部的ThreadLocalMap里的,且ThreadLocalMap包含了一个Entry数组,

每个Entry存储着一个ThreadLocal和对应的value值,也就是说Thread可以存储多个ThreadLocal对象,既然我们对ThreadLocal的底层实现有所了解,

那么问题来了,如何使用ThreadLocal不会出现内存泄露情况呢?请看下图图2

图2

 

从图图2中,我们能看出,假如我们使用一个线程thread来执行任务,当thread线程执行完任务退出之后,该线程里所持有的ThreadLocalMap

的对象也就

没有了强引用,那么由于ThreadLocalMap没有强引用,所以就可以被JDK垃圾回收器回收了,那么ThreadLocalMap里面所

包含ThreadLocal也就回收掉了。详细的整个流程,请看下图图3

图3

 

到了这里想必同学们应该知道之前如何使用ThreadLocal不会出现内存泄露情况问题的答案了吧,对,它就是创建Thread或者Thread子类来执行任务处理,

随着对应的线程Thread生命周期结束,那么线程Thread所持有的ThreadLocal也会被垃圾回收,不会出现内存泄露情况发生。

 

但是每执行一个任务都要创建一个Thread来处理,对于机器来说开销还是不小的,我们之前文章中讲到,

可以用到线程池的技术来解决频繁的创建和销毁Thread。那么在使用线程池的场景下使用ThreadLocal是否会有内存泄露的情况发生呢?那我们先来看一下图图4

图4

 

在之前的学习中,我们知道线程池里的核心线程Thread执行完任务之后,是不会退出的,可以循环使用的,那就说明线程池里每个核心线程Thread

对应的ThreadLocalMap一直是强引用关系,所以线程Thread对应的ThreadLocal是不会自动回收的,那同学们可能会说,

之前章节不是说了ThreadLocal是WeakReference弱引用,JDK触发垃圾回收的时候可以自动回收吗?同学们说的都没有问题,我们先来看一下图图5

图5

 

在上述图5中,我们用新的一张图来表示Thread和ThreadLocal对应关系,其中ThreadLocalMap中的Entry中的key是属于WeakReference弱引用,

随着JDK的垃圾回收ThreadLocal可以自动被回收,那么我们看一下触发JDK垃圾回收之后的示意图,请看下图图6

在JDK触发垃圾回收之后,对应的ThreadLocal确实可以被垃圾回收掉,变为null值,但是同学们,被自动回收的ThreadLocal所对应value值

是不能被自动回收的,请看下图图7

图7

 

在上述图7,我们能清晰看到ThreadLocal的key是可以被自动回收变成为null,但是对应的value还是被Entry引用着呢,所以value是不能被垃圾回收器自动回收的,到了这里,想必同学们应该知道了,如果在线程池场景中使用ThreadLocal是有内存泄露情况的可能性,原因就是线程池的核心线程Thread是循环利用的,

每个线程Thread所对应的ThreadLoalMap被强引用着,所以每个线程Thread的ThreadLoalMap不能被回收,但是ThreadLoalMap里

含有多个ThreadLocal-value的Entry,虽然ThreadLocal-key是弱引用可以被垃圾回收器自动回收,但是ThreadLocal对应的value是不能被回收的,

所以说有内存泄露的情况可能性。那同学们可能会问,这种情况,不能避免吗?那我们先看一下JDK是否解决方案呢?请看下图图8

图8

 

上述图8,同学们应该还有印象吧,它就是我们之前讲解的ThreadLocal的get方法,先是在红线1处就是获取ThreadLocal对应的Entry,然后再从Entry

获取对应value,那么在红线2处,我们能看到这个if条件,同学们,如果Entry所对应的ThreadLocal被自动回收变成null啦,那这个if判断条件是不成立的,

就会走到getEntryAfterMiss这个方法里对吧,那么就来看看getEntryAfterMiss这个方法的实现,请看下图图9

图9

 

从图9我们能够看到getEntryAfterMiss的逻辑,我们传进来的Entry e其实所对应的key,也就是ThreadLocal是为null的,所以一定会走到上图红线处这个条件里,会走到expungeStaleEntry这个方法里,我们再来看看expungeStaleEntry这个方法的逻辑,请看下图图10

图10

 

从上图图10红线1处,我们能清晰的看到,会把ThreadLocal为null所对应的value设置为null,同时把对应的Entry也设置为null,同时在红线2处,

会遍历所有的为ThreadLocal为null的value和对应的Entry都设置为null,这样就去除了强引用,有助于被垃圾回收,我们再来看一下所对于的示意图图11

图11

 

到了这里,同学们可能会说,JDK的expungeStaleEntry的方法不是会把ThreadLocal为null所对应value和Entry对象设置为null嘛,这样就可以被垃圾回收啦,

那在线程池的使用场景下就不会出现内存泄露的情况了啊?同学们,只有在调用ThreadLocal的get、set、remove方法的时候才会触发expungeStaleEntry方法

的执行,才会把被自动垃圾回收的ThreadLocal为null所对应的value和Entry才会设置为null。换句话说,正常的情况是不会出现内存泄露的,但是如果我们

没有调用ThreadLocal对应的set、get、remove方法就不会把对应的value和Entry设置为null,这样就可能会出现内存泄露情况。对吧,那如何避免内存泄露

的情况呢?那就是我们在不使用的时候就调用一下ThreadLoca的remove方法,来加快垃圾回收,避免内存泄露。

 

最后简单总结一下,由于ThreadLocalMap包含了ThreadLocal,且线程Thread中包含变量ThreadLocalMap,因此ThreadLocalMap与Thread的生命周期

是相同的,所以在线程池场景中使用ThreadLocal的时候,我们还是要养成好习惯,ThreadLocal不在使用的时候调用remove方法,避免内存泄漏情况发生。

 

转:https://zhuanlan.zhihu.com/p/554781932

标签:泄漏,Thread,回收,ThreadLocal,线程,内存,null
From: https://www.cnblogs.com/blogzero/p/17439043.html

相关文章

  • 深入理解 Java 虚拟机 —— Java 内存模型与线程
    处理器的效率和一致性(与java内存访问可类比)计算机同时去做几件事情,不仅是因为计算机的运算能力强大了,还有一个很重要的原因是计算机的运算速度与它的存储和通信子系统的速度差距太大,大量的时间都花费在磁盘I/O、网络通信或者数据库访问上。如果不希望处理器在大部分时间里都处......
  • Linux为什么要有大页内存
    Linux为什么要有大页内存?为什么DPDK要求必须要设置大页内存?这都是由系统架构决定的,系统架构发展到现在,又是在原来的基础上一点点演变的。一开始为了解决一个问题,大家设计了一个很好的方案,随着事物的发展,发现无法满足需求,就在原来的基础上改进,慢慢的变成了现在的样子。不过技术革新......
  • 记一次redis数据库RDB内存事故排查处理
    事故表现:redis状态正常,但客户端不能使用,定位日志结论,redis内存申请不通过,导致中断用户操作解决办法1.解锁相关配置(不能解决根本问题,根本原因来源于开发使用姿势不对)两种解决办法一.打开系统层始终同意分配内存(不建议)编辑文件/etc/sysctl.conf添加vm.overcommit_memory=1内核参......
  • java面试(9)内存泄露
    1:Java中也存在栈内存泄露的情况?  在Java中,栈内存主要用于存储方法调用和本地变量。与堆内存不同,栈内存的分配和释放是由编译器和虚拟机自动处理的,通常不需要手动释放。  然而,如果在编写代码时出现一些问题,可能会导致栈内存泄露。以下是一些可能引起栈内存泄露的常见情......
  • Linux 大页内存 Huge Pages 虚拟内存
    Linux为什么要有大页内存?为什么DPDK要求必须要设置大页内存?这都是由系统架构决定的,系统架构发展到现在,又是在原来的基础上一点点演变的。一开始为了解决一个问题,大家设计了一个很好的方案,随着事物的发展,发现无法满足需求,就在原来的基础上改进,慢慢的变成了现在的样子。不过技术革新......
  • 性能测试-分析内存瓶颈
    top或者free查看内存利用率看used的值有没有很高(一般持续超过70%就会认为可能有瓶颈)看buff/cache的值,通常情况比较大不是什么问题,如果系统需要更多内存时,buff/cache就会自动释放,但是如果占用了大量内存导致系统无法分配足够的内存给其他进程使用,就需要再分析和优化 top......
  • PCI5565反射内存
    PCI5565反射内存具备多项特性,使其成为高效的数据传输解决方案。首先,它是一种高速的网络,支持nGbaud数据传输,可以快速处理大量数据。其次,PCI5565反射内存易于使用,无需过多的操作步骤即可实现数据传输。另外,它的可扩展性非常强,可方便地升级到其他形式。与操作系统和处理器无关的特性也......
  • LabVIEW|小技巧:同址操作节省内存空间
      在LabVIEW中,有时候对于同一个数组、同一个簇或者其他数据容器的数据操作,如下图的搜索后再进行替换,这个时候LabVIEW会生成一个原数组的副本数组给替换函数使用,这就造成内存的浪费;解决方法是:使用右边的同址操作结构,即对于同一地址数组的不同操作,这样不会产生容器副本,节省了空间......
  • linux 内存管理
    内存管理的目标外存是程序存储的地方,内存是进程运行的地方。内存管理的目标除了实现进程之间的隔离、进程与内核之间的隔离、减少物理内存并发使用的数量之外,还有以下几个目标。1、减少内存碎片,包括外部碎片和内部碎片。外部碎片是指还在内存分配器中的内存,但是由于比较分散,无......
  • ThreadLocal源码学习笔记
    系列文章目录和关于我一丶ThreadLocal结构#每一个Thread对象都有一个名为threadLocals类型为ThreadLocal.ThreadLocalMap的属性,ThreadLocal.ThreadLocalMap对象内部存在一个Entry数组,其中存储的Entry对象key是ThreadLocal,value便是我们绑定在线程上的值。ThreadLocal可以做......