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

1、ThreadLocal的基本原理

简单介绍一下ThreadLocal,在多线程并发访问同一个共享变量的情况下,如果不做同步控制的话,就可能会导致数据不一致的问题,所以,我们需要使用synchronized加锁来解决。

而ThreadLocal换了一个思路来处理多线程的情况,



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对引用的概念做了一些扩充,将引用分为“强”、“软”、“弱”、“虚”四种,由强到弱依次为:



强引用:指代码中普遍存在的赋值行为,如:Object o = new Object(),只要强引用关系还在,对象就永远不会被回收。

软引用:还有用处,但不是必须存活的对象,JVM会在内存溢出前对其进行回收,例如:缓存。

弱引用:非必须存活的对象,引用关系比软引用还弱,不管内存是否够用,下次GC一定回收。

虚引用:也称“幽灵引用”、“幻影引用”,最弱的引用关系,完全不影响对象的回收,等同于没有引用,虚引用的唯一的目的是对象被回收时会收到一个系统通知。

这个描述还是比较官方的,简单总结一下,大家应该都追过剧,强引用就好比是男主角,怎么都死不了。软引用就像女主角,虽有一段经历,还是没走到最后。弱引用就是男二号,注定用来牺牲的。虚引用就是路人甲了。

3、造成内存泄漏的原因

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

3.1内存泄漏相关概念

Memory overflow:内存溢出,没有足够的内存提供申请者使用。

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

3.2 如果key使用强引用

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

此时ThreadLocal的内存图(实线表示强引用)如下:

  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使用了弱引用, 那么会出现内存泄漏吗?

  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 内存泄漏的真实原因

出现内存泄漏的真实原因出改以上两种情况

比较以上两种情况,我们就会发现:

内存泄漏的发生跟 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,还会向后继续清理。

ThreadLocal真的会造成内存泄漏吗?的更多相关文章

  1. ThreadLocal 的机制与内存泄漏

    ThreadLocal笔记 如上图所示 每个Thread 都有一个map,里面存着Entry<Key,value>,而key是实现了WeakReference的ThreadLocal,如果 ...

  2. ThreadLocal基本使用和内存泄漏分析

    ThreadLocal基础部分 ThreadLoal的作用 保存线程的独立变量,即每个线程维护一份.这种变量在线程的生命周期内起作用,减少同一个线程内多个函数之间公共变量传递麻烦. 使用场景 需要给不 ...

  3. 关于js闭包是否真的会造成内存泄漏(转载)

    闭包是一个非常强大的特性,但人们对其也有诸多无解.一种危言耸听的说法是闭包会造成内存泄露. 局部变量本来应该在函数退出的时候被解除引用,但如果局部变量被封闭在闭包形成的环境中,那么这个局部变量就能一直 ...

  4. 深入剖析ThreadLocal实现原理以及内存泄漏问题

    关于ThreadLocalMap<ThreadLocal, Object>弱引用问题: 当线程没有结束,但是ThreadLocal已经被回收,则可能导致线程中存在ThreadLocalMa ...

  5. 证明:ThreadLocal的get,set方法无法防止内存泄漏

    先给出结论:get,set两个方法都不能完全防止内存泄漏,还是每次用完ThreadLocal都勤奋的remove一下靠谱. 前言:   看到有的博客说在把ThreadLocal的所有强引用置空前,调用 ...

  6. 深入分析 ThreadLocal 内存泄漏问题

    前言 ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.但是如果滥用ThreadLocal,就可能 ...

  7. 并发编程(四):ThreadLocal从源码分析总结到内存泄漏

    一.目录      1.ThreadLocal是什么?有什么用?      2.ThreadLocal源码简要总结?      3.ThreadLocal为什么会导致内存泄漏? 二.ThreadLoc ...

  8. ThreadLocal以及内存泄漏

    ThreadLocal是什么 ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.但是如果滥用Thr ...

  9. 分析 ThreadLocal 内存泄漏问题

    ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.但是如果滥用 ThreadLocal,就可能会导 ...

  10. java多线程--------深入分析 ThreadLocal 内存泄漏问题

    前言 ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.但是如果滥用ThreadLocal,就可能 ...

随机推荐

  1. 分布式事务 —— SpringCloud Alibaba Seata

    Seata 简介 传统的单体应用中,业务操作使用同一条连接操作不同的数据表,一旦出现异常就可以整体回滚.随着公司的快速发展.业务需求的变化,单体应用被拆分成微服务应用,原来的单体应用被拆分成多个独立的 ...

  2. 循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(9) -- 实现系统动态菜单的配置和权限分配

    在WPF应用端开发,它的界面类似于Winform端,因此我们也需要对系统的菜单进行动态配置,这样才能把系统的功能弹性发挥到极致,通过动态菜单的配置方式,我们可以很容易的为系统新增所需的功能,通过权限分 ...

  3. ABC319 A-E 题解

    A 用 map <string, int> 将名字对应的值存下来即可. 赛时代码 B 按照题意暴力模拟,注意细节. 赛时代码 C 答辩题,卡了我半个小时. 枚举 \(1\sim 9\) 的 ...

  4. Windows上的多jdk版本管理工具

    前言 Java在Windows上因为版本太多导致难以管理,这个项目可以很好的解决这点 项目地址 GitHub - ystyle/jvms: JDK Version Manager (JVMS) for ...

  5. dotnet 探究 SemanticKernel 的 planner 的原理

    在使用 SemanticKernel 时,我着迷于 SemanticKernel 强大的 plan 能力,通过 plan 功能可以让 AI 自动调度拼装多个模块实现复杂的功能.我特别好奇 Semant ...

  6. Java Springbool敏感词过工具类滤

    Java Springbool敏感词过工具类滤 1. 功能描述利用前缀树这种数据结构,设计并开发出敏感词过滤工具. 2. 构建敏感词表resource/sensitive-words.txt 3. 敏 ...

  7. BI 数据可视化平台建设(1)—交叉表组件演变实战

    作者:vivo 互联网大数据团队 - Zhu Jianchen 本文是vivo互联网大数据团队<BI数据可视化平台建设>系列文章第1篇 - 交叉表组件. 交叉表在数据分析里应用广泛,通过本 ...

  8. 使用rancher rke快速安装k8s集群

    概述 Rancher Kubernetes Engine(RKE)是一个用于部署.管理和运行Kubernetes集群的开源工具.旨在简化Kubernetes集群的部署和操作. RKE具有以下特点和功能 ...

  9. ChatGLM3-6B:新一代开源双语对话语言模型,流畅对话与低部署门槛再升级

    ChatGLM3-6B:新一代开源双语对话语言模型,流畅对话与低部署门槛再升级 1.ChatGLM3简介 ChatGLM3 是智谱AI和清华大学 KEG 实验室联合发布的新一代对话预训练模型.Chat ...

  10. Chinese Bank Card and Credit Card ID

    Regular match expression: [^0-9]((3|4|5|6|9)\d{15,18})[^0-9] Rule characteristics: first number:3 or ...