首页 > 系统相关 >ThreadLocal真的会造成内存泄漏吗?

ThreadLocal真的会造成内存泄漏吗?

时间:2024-01-13 17:33:05浏览次数:23  
标签:泄漏 ThreadLocalMap ThreadLocal 内存 Entry 引用

ThreadLoca在并发场景中,应用非常多。那ThreadLocal是不是真的会造成内存泄漏?今天给大家做一个分享,个人见解,仅供参考。

1、ThreadLocal的基本原理

简单介绍一下ThreadLocal,在多线程并发访问同一个共享变量的情况下,如果不做同步控制的话,就可能会导致数据不一致的问题,所以,我们需要使用synchronized加锁来解决。
而ThreadLocal换了一个思路来处理多线程的情况,
微信截图_20240113161952.png
ThreadLocal本身并不存储数据,它使用了线程中的threadLocals属性,threadLocals的类型就是在ThreadLocal中的定义的ThreadLocalMap对象,当调用ThreadLocal的set(T value)方法时,ThreadLocal将自身的引用也就是this作为Key,然后,把用户传入的值作为Value存储到线程的ThreadLocalMap中,这就相当于每个线程的读写操作都是基于线程自身的一个私有副本,线程之间的数据是相互隔离的,互不影响。
这样一来基于ThreadLocal的操作也就不存在线程安全问题了。它相当于采用了用空间来换时间的思路,从而提高程序的执行效率。

2、四种对象引用

在ThreadLocalMap内部,维护了一个Entry数组table的属性,用来存储键值对的映射关系,来看这样一段代码片段:

static class ThreadLocalMap {

    ...
    private Entry[] table;
    static class Entry implements WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    ...
}

Entry将ThreadLocal作为Key,值作为Value保存,它继承自WeakReference,注意构造函数里的第一行代码super(k),这意味着ThreadLocal对象是一个「弱引用」。有的小伙伴可能对「弱引用」不太熟悉,这里再介绍一下Java的四种引用关系。
在JDK1.2之后,Java对引用的概念做了一些扩充,将引用分为“强”、“软”、“弱”、“虚”四种,由强到弱依次为:
image.png
强引用:指代码中普遍存在的赋值行为,如:Object o = new Object(),只要强引用关系还在,对象就永远不会被回收。
软引用:还有用处,但不是必须存活的对象,JVM会在内存溢出前对其进行回收,例如:缓存。
弱引用:非必须存活的对象,引用关系比软引用还弱,不管内存是否够用,下次GC一定回收。
虚引用:也称“幽灵引用”、“幻影引用”,最弱的引用关系,完全不影响对象的回收,等同于没有引用,虚引用的唯一的目的是对象被回收时会收到一个系统通知。
这个描述还是比较官方的,简单总结一下,大家应该都追过剧,强引用就好比是男主角,怎么都死不了。软引用就像女主角,虽有一段经历,还是没走到最后。弱引用就是男二号,注定用来牺牲的。虚引用就是路人甲了。

3、造成内存泄漏的原因

内存泄漏和ThreadLocalMap中定义的Entry类有非常大的关系。

3.1内存泄漏相关概念

Memory overflow:内存溢出,没有足够的内存提供申请者使用。
Memory leak:内存泄漏是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。I内存泄漏的堆积终将导致内存溢出。

3.2 如果key使用强引用

假设ThreadLocalMap中的key使用了强引用,那么会出现内存泄漏吗?
此时ThreadLocal的内存图(实线表示强引用)如下:
image.png

  1. 假设在业务代码中使用完ThreadLocal, ThreadLocal ref被回收了
  2. 但是因为threadLocalMap的Entry强引用了threadLocal, 造成ThreadLocal无法被回收
  3. 在没有手动删除Entry以及CurrentThread依然运行的前提下, 始终有强引用链threadRef → currentThread → entry, Entry就不会被回收( Entry中包括了ThreadLocal实例和value), 导致Entry内存泄漏也就是说: ThreadLocalMap中的key使用了强引用, 是无法完全避免内存泄漏的
3.3 如果key使用弱引用

假设ThreadLocalMap中的key使用了弱引用, 那么会出现内存泄漏吗?
image.png

  1. 假设在业务代码中使用完ThreadLocal, ThreadLocal ref被回收了
  2. 由于threadLocalMap只持有ThreadLocal的弱引用, 没有任何强引用指向threadlocal实例, 所以threadlocal就可以顺利被gc回收, 此时Entry中的key = null
  3. 在没有手动删除Entry以及CurrentThread依然运行的前提下, 也存在有强引用链threadRef → currentThread → value, value就不会被回收, 而这块value永远不会被访问到了, 导致value内存泄漏也就是说: ThreadLocalMap中的key使用了弱引用, 也有可能内存泄漏。
3.4 内存泄漏的真实原因

image.png

出现内存泄漏的真实原因出改以上两种情况
比较以上两种情况,我们就会发现:
内存泄漏的发生跟 ThreadLocalIMap 中的 key 是否使用弱引用是没有关系的。那么内存泄漏的的真正原因是什么呢?
细心的同学会发现,在以上两种内存泄漏的情况中.都有两个前提:

  1. 没有手动侧除这个 Entry
  2. CurrentThread 依然运行

第一点很好理解,只要在使用完下 ThreadLocal ,调用其 remove 方法翻除对应的 Entry ,就能避免内存泄漏。
第二点稍微复杂一点,由于ThreodLocalMap 是 Threod 的一个属性,被当前线程所引甲丁所以它的生命周期跟 Thread 一样长。那么在使用完 ThreadLocal 的使用,如果当前Thread 也随之执行结束, ThreadLocalMap 自然也会被 gc 回收,从根源上避免了内存泄漏。
综上, ThreadLocal 内存泄漏的根源是:
由于ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏.

4、如何避免内存泄漏?

不要听到「内存泄漏」就不敢使用ThreadLocal,只要规范化使用是不会有问题的。我给大家支几个招:
1、每次使用完ThreadLocal都记得调用remove()方法清除数据。
2、将ThreadLocal变量尽可能地定义成static final,避免频繁创建ThreadLocal实例。这样也就保证程序一直存在ThreadLocal的强引用,也能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的Value值,进而清除掉。
当然,就是使用不规范,ThreadLocal内部也做了一些优化,比如:
1、调用set()方法时,ThreadLocal会进行采样清理、全量清理,扩容时还会继续检查。
2、调用get()方法时,如果没有直接命中或者向后环形查找时也会进行清理。
3、调用remove()时,除了清理当前Entry,还会向后继续清理。

image

标签:泄漏,ThreadLocalMap,ThreadLocal,内存,Entry,引用
From: https://www.cnblogs.com/zhaotianen/p/17962653

相关文章

  • 数组内存图
    ......
  • 聊一聊 .NET高级调试 中的一些内存术语
    一:背景1.讲故事在高级调试的旅程中,经常会有一些朋友问我什么是工作集(内存),什么是提交大小,什么是VirtualSize,什么是WorkingSet。。。截图如下:既然有很多朋友问,这些用口头也不怎么好描述,刚好上午有时间就系统的聊一下吧。二:内存术语解读1.VirtualSize是什么可能有些朋......
  • C++ 单例模式以及内存管理
    引用:https://zhuanlan.zhihu.com/p/37469260https://www.cnblogs.com/xiaolincoding/p/11437231.htmlhttps://blog.csdn.net/unonoi/article/details/121138176单例模式:一个类在全局范围内只有一个实例化的对象核心:构造函数是私有的,防止外界创建单例类的对象。使用类内的......
  • C/C++程序的内存开辟——《初学C语言第55天》
    //————C/C++程序的内存开辟C++程序内存分配的几个区域://intt=2;//staticintr=1;//voidtest()//{//  statice=1;//  intn=1;//  intarr[10]={1,2,3,4};//  charg[]="helloworld";//  char*p="abcd";//  int*a=(int*)malloc......
  • 【C语言进阶篇】动态内存分配的六个常见错误
    <br>(文章目录)前言  <fontcolor=green>......
  • 异构计算关键技术之内存管理与DMA(一)
    异构计算关键技术之内存管理与DMA(一)诞生伊始,计算机处理能力就处于高速发展中。及至最近十年,随着大数据、区块链、AI等新技术的持续火爆,人们为提升计算处理速度更是发展了多种不同的技术思路。大数据受惠于分布式集群技术,区块链带来了专用处理器(Application-SpecificIC,ASIC)的春......
  • java基础语法面向对象之单个对象内存图
    一:概述在面向对象的学习中,需要去了解对象的内存图,在这里以单个对象的内存图为例进行讲解。二:具体说明<1>代码publicclassStudent{Stringname;intage;publicvoidstudy(){System.out.println(name+"好好学习");......
  • 开发内存检测脚本
    if实践:1.单分支if2.if分支的嵌套3.开发内存监控的脚本4.开发nginx,mysql服务监控脚本5.开发rsync起停脚本6.作业:nginx服务监控脚本 1.单分支if条件测试语句,改造为if判断语句,if结合条件测试 将上述改造为if脚本:脚本内容: ......
  • [ 20230308 CQYC省选模拟赛 T2 ] 塑料内存条
    题意给定\(n\)个不可重集,初始每个集合\(i\)有元素\(c_i\)。请你以下\(3\)种操作:1xy在集合\(x\)插入\(y\)。2xy将\(y\)集合所有数插入\(x\),并删除\(y\)集合(不影响别的集合的下标)3xy求\(x\)集合与\(y\)集合的交之和。Sol可塑性记忆。注意到前......
  • 内存使用率多少算正常?
    参考链接:https://baijiahao.baidu.com/s?id=1777064449174417489&wfr=spider&for=pchttps://baijiahao.baidu.com/s?id=1782730192697326164&wfr=spider&for=pc今天远程遇到一个问题,用浏览器的时候,直接就卡死了。打开任务管理器,内存使用已经到了百分之90左右了。对于一般的办......