ThreadLocal内存泄漏问题解决方案

如果说 ThreadLocal 的话,那肯定就会涉及到内存泄漏,为啥嘞

因为 吧啦吧啦 ~

ThreadLocal 解决了什么问题呢?

它是为了解决对象不能被多线程共享访问的问题,通过 threadLocal.set() 方法将对象实例保存在每个线程自己所拥有的 threadLocalMap 中,这样的话每个线程都使用自己的对象实例,彼此不会影响从而达到了隔离的作用,这样就解决了对象在被共享访问时带来的线程安全问题。

啥意思呢?打个比方,现在公司所有人都要填写一个表格,但是只有一支笔,这个时候就只能上个人用完了之后,下个人才可以使用,为了保证"笔"这个资源的可用性,只需要保证在接下来每个人的获取顺序就可以了,这就是 lock 的作用,当这支笔被别人用的时候,我就加 lock ,你来了那就进入队列排队等待获取资源(非公平方式那就另外说了),这支笔用完之后就释放 lock ,然后按照顺序给下个人使用。

但是完全可以一个人一支笔对不对,这样的话,你填写你的表格,我填写我的表格,咱俩谁都不耽搁谁。这就是 ThreadLocal 在做的事情,因为每个 Thread 都有一个副本,就不存在资源竞争,所以也就不需要加锁,这不就是拿空间去换了时间嘛!

在开始之前,咱们先把 Thread, ThreadLocal, ThreadLocalMap 的关系捋一捋:

可以看到,在 Thread 中持有一个 ThreadLocalMap , ThreadLocalMap 又是由 Entry 来组成的,在 Entry 里面有 ThreadLocal 和 value

ThreadLocal 为啥动不动就内存泄漏呢?

在这里先给个解释,后面咱们再详细分析:

首先是因为 ThreadLocal 是基于 ThreadLocalMap 实现的,其中 ThreadLocalMap 的 Entry 继承了 WeakReference ,而 Entry 对象中的 key 使用了 WeakReference 封装,也就是说, Entry 中的 key 是一个弱引用类型,对于弱引用来说,它只能存活到下次 GC 之前

如果此时一个线程调用了 ThreadLocalMap 的 set 设置变量,当前的 ThreadLocalMap 就会新增一条记录,但由于发生了一次垃圾回收,这样就会造成一个结果: key 值被回收掉了,但是 value 值还在内存中,而且如果线程一直存在的话,那么它的 value 值就会一直存在

这样被垃圾回收掉的 key 就会一直存在一条引用链: Thread -> ThreadLocalMap -> Entry -> Value :

就是因为这条引用链的存在,就会导致如果 Thread 还在运行,那么 Entry 不会被回收,进而 value 也不会被回收掉,但是 Entry 里面的 key 值已经被回收掉了

这只是一个线程,如果再来一个线程,又来一个线程…多了之后就会造成内存泄漏

知道是怎么造成内存泄漏之后,接下来要做的事情就好说了,不是因为 value 值没有被回收掉所以才会导致内存泄露的嘛

那使用完 key 值之后,将 value 值通过 remove 方法 remove 掉,这样的话内存中就不会有 value 值了,也就防止了内存泄漏嘛

ThreadLocal 是基于 ThreadLocalMap 实现的?

OK ,上面的内容讲完了,接下来一一来看

首先,你怎么知道 ThreadLocal 是基于 ThreadLocalMap 实现的呢?

从源码知道的~

在源码中能够看到下面这几行代码:

public class ThreadLocal<T> {
  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 内部维护着 ThreadLocalMap ,而它的 Entry 则继承自 WeakReference 的 ThreadLocal ,其中 Entry 的 k 为 ThreadLocal , v 为 Object ,在调用 super(k) 时就将 ThreadLocal 实例包装成了一个 WeakReference

强弱引用这块内容阿粉就直接放一个表格吧:

引用类型 功能特点
强引用 ( Strong Reference ) 被强引用关联的对象永远不会被垃圾回收器回收掉
软引用( Soft Reference ) 软引用关联的对象,只有当系统将要发生内存溢出时,才会去回收软引用引用的对象
弱引用 ( Weak Reference ) 只被弱引用关联的对象,只要发生垃圾收集事件,就会被回收
虚引用 ( Phantom Reference ) 被虚引用关联的对象的唯一作用是能在这个对象被回收器回收时收到一个系统通知

从表格中应该能够看出来,弱引用的对象只要发生垃圾收集事件,就会被回收

所以弱引用的存活时间也就是下次 GC 之前了

在这里阿粉就有个问题想问问了:为什么 ThreadLocal 采用弱引用,而不是强引用嘞?

在 ThreadLocalMap 上面有些注释,我在这里摘录一部分,或许可以从中窥探一二:

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys

翻译一下就是:(虽然我英语不是很好

为了解决非常大且长期使用的问题,哈希表使用了弱引用的 key

假设,假设, ThreadLocal 使用的是强引用,会怎样呢?

如果是强引用的话,在表格中也能够看出来,被强引用关联的对象,永远都不会被垃圾回收器回收掉

如果引用的 ThreadLocal 对象被回收了,但是 ThreadLocalMap 还持有对 ThreadLocal 的强引用,如果没有 remove 的话, 在 GC 时进行可达性分析, ThreadLocal 依然可达,这样就不会对 ThreadLocal 进行回收,但是我们期望的是引用的 ThreadLocal 对象被回收,这样不就达不到目的了嘛

使用弱引用的话,虽然会出现内存泄漏的问题,但是在 ThreadLocal 生命周期里面,都有对 key 值为 null 时进行回收的处理操作

所以,使用弱引用的话,可以在 ThreadLocal 生命周期中尽可能保证不出现内存泄漏的问题

啥?在 ThreadLcoal 生命周期里面,都有对 key 值为 null 时进行回收的处理操作?有证据么?

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解Spring Cloud中Hystrix 线程隔离导致ThreadLocal数据丢失

    在Spring Cloud中我们用Hystrix来实现断路器,Zuul中默认是用信号量(Hystrix默认是线程)来进行隔离的,我们可以通过配置使用线程方式隔离. 在使用线程隔离的时候,有个问题是必须要解决的,那就是在某些业务场景下通过ThreadLocal来在线程里传递数据,用信号量是没问题的,从请求进来,但后续的流程都是通一个线程. 当隔离模式为线程时,Hystrix会将请求放入Hystrix的线程池中去执行,这个时候某个请求就有A线程变成B线程了,ThreadLocal必然消失了. 下面我

  • ThreadLocal原理及内存泄漏原因

    ThreadLocal有两个问题: 1. 每个变量副本是存储在哪了? 2. 变量副本是怎样从共享的变量中赋值出来的?源码中threadlocal的初始值是什么时候设置的. ThreadLocal为每个线程维护一个变量的副本? 每个线程的ThreadLocalMap都是线程自身持有的,但是初始化是在ThreadLocal中,然后每个线程相当于保存了一个map 这个map存的key是LocalThread的实例,value是存储的线程的局部变量 get方法 根据当前线程获取Thread中的值 set

  • Java源码解析ThreadLocal及使用场景

    ThreadLocal是在多线程环境下经常使用的一个类. 这个类并不是为了解决多线程间共享变量的问题.举个例子,在一个电商系统中,用一个Long型变量表示某个商品的库存量,多个线程需要访问库存量进行销售,并减去销售数量,以更新库存量.在这个场景中,是不能使用ThreadLocal类的. ThreadLocal适用的场景是,多个线程都需要使用一个变量,但这个变量的值不需要在各个线程间共享,各个线程都只使用自己的这个变量的值.这样的场景下,可以使用ThreadLocal.此外,我们使用ThreadL

  • 有关ThreadLocal的面试题你真的懂了吗

    说明 面试官:讲讲你对ThreadLocal的一些理解. 那么我们该怎么回答呢????你也可以思考下,下面看看零度的思考: ThreadLocal用在什么地方? ThreadLocal一些细节! ThreadLocal的最佳实践! 思考 ThreadLocal用在什么地方? 讨论ThreadLocal用在什么地方前,我们先明确下,如果仅仅就一个线程,那么都不用谈ThreadLocal的,ThreadLocal是用在多线程的场景的!!! ThreadLocal归纳下来就2类用途: 保存线程上下文信

  • Java并发编程学习之ThreadLocal源码详析

    前言 多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作的顺序是不可预期的,多线程访问同一个共享变量特别容易出现并发问题,特别是多个线程需要对一个共享变量进行写入时候,为了保证线程安全, 一般需要使用者在访问共享变量的时候进行适当的同步,如下图所示: 可以看到同步的措施一般是加锁,这就需要使用者对锁也要有一定了解,这显然加重了使用者的负担.那么有没有一种方式当创建一个变量的时候,每个线程对其进行访问的时候访问的是自己线程的变量呢?其实ThreaLocal就可

  • java线程本地变量ThreadLocal详解

    介绍 ThreadLocal作为JDK1.2以来的一个java.lang包下的一个类,在面试和工程中都非常重要,这个类的主要目的是提供线程本地的变量,所以也有很多地方把这个类叫做线程本地变量 从字面理解,这个类为每个线程都创建了一个本地变量,实际上是ThreadLocal为变量在每个线程中都创建了一个副本,使得每个线程都可以访问自己内部的副本变量 通常提到多线程,都会考虑变量同步的问题,但是ThreadLocal并不是为了解决多线程共享变量同步的问题,而是为了让每个线程的变量不互相影响,相当于线

  • Java ThreadLocal用法实例详解

    本文实例讲述了Java ThreadLocal用法.分享给大家供大家参考,具体如下: 目录 ThreadLocal的基本使用 ThreadLocal实现原理 源码分析(基于openjdk11) get方法: setInitialValue方法 getEntry方法 set方法 ThreadLocalMap的set方法 replaceStaleEntry方法 cleanSomeSlots方法 rehash方法 expungeStaleEntries方法 resize方法 ThreadLocal实现

  • ThreadLocal内存泄漏问题解决方案

    如果说 ThreadLocal 的话,那肯定就会涉及到内存泄漏,为啥嘞 因为 吧啦吧啦 ~ ThreadLocal 解决了什么问题呢? 它是为了解决对象不能被多线程共享访问的问题,通过 threadLocal.set() 方法将对象实例保存在每个线程自己所拥有的 threadLocalMap 中,这样的话每个线程都使用自己的对象实例,彼此不会影响从而达到了隔离的作用,这样就解决了对象在被共享访问时带来的线程安全问题. 啥意思呢?打个比方,现在公司所有人都要填写一个表格,但是只有一支笔,这个时候就

  • 对ThreadLocal内存泄漏及弱引用的理解

    ThreadLocal内存泄漏及弱引用 1.什么是内存泄漏?Entry的key弱引用与泄漏关系 在TreadLocal中内存泄漏是指TreadLocalMap中的Entry中的key为null,而value不为null.因为key为null导致value一直访问不到,而根据可达性分析,始终有threadRef->currentThread->threadLocalMap->entry->valueRef->valueMemory,导致在垃圾回收的时候进行可达性分析的时候,va

  • IOS 常见内存泄漏以及解决方案

    IOS 常见内存泄漏以及解决方案 整理了几个内存泄漏的例子,由于转载地址已经找不到了,在这里就不一一列出来了. 1 OC和CF转化出现的内存警告 CFStringRef cfString = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,(CFStringRef)picDataString,NULL,CFSTR(":/?#[]@!$&'()*+,;="),kCFStringEncodingUTF8); N

  • Spring关闭Tomcat Servlet容器时内存泄漏问题解决方案

    这篇文章主要介绍了Spring关闭Tomcat Servlet容器时内存泄漏问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 出错信息 22-Sep-2017 06:19:51.064 WARNING [main] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [license] appears t

  • ThreadLocal内存泄漏常见要点解析

    前段时间在网上看到了一篇关于ThreadLocal内存泄漏的文章 于是个人也研究了下ThreadLocal 对象,其原理是: ThreadLocal 定义的变量值 会存储在当前线程的一个map集合中 这个map里面存储的是Entity对象 , Entity对象的key是当前ThreadLocal对象的弱引用, value则是ThreadLocal变量的值 这就产生了一个问题: 如果ThreadLocal变量的强引用丢失了 map里面的弱引会失效 gc就回收ThreadLocal对象 那么 Thr

  • Android Native 内存泄漏系统化解决方案

    导读:C++内存泄漏问题的分析.定位一直是Android平台上困扰开发人员的难题.因为地图渲染.导航等核心功能对性能要求很高,高德地图APP中存在大量的C++代码.解决这个问题对于产品质量尤为重要和关键,高德地图技术团队在实践中形成了一套自己的解决方案. 分析和定位内存泄漏问题的核心在于分配函数的统计和栈回溯.如果只知道内存分配点不知道调用栈会使问题变得格外复杂,增加解决成本,因此两者缺一不可. Android中Bionic的malloc_debug模块对内存分配函数的监控及统计是比较完善的,但

  • JS常见内存泄漏及解决方案解析

    内存泄漏? 官方解释:内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果. 通俗点就是指由于疏忽或者错误造成程序未能释放已经不再使用的内存,不再用到的内存却没有及时释放,从而造成内存上的浪费. 避免内存泄漏? 在局部作用域中,等函数执行完毕,变量就没有存在的必要了,垃圾回收机制很亏地做出判断并且回收,但是对于全局变量,很难判断什么时候不用这些变量,无法正常回收:所以,尽量少使用全局变量.在

  • Java DWR内存泄漏问题解决方案

    机器跑了一晚上,发现有崩溃现象,由于页面内有动态绘图功能,我怀疑是绘图原因,但是今天上午有人提醒我才想到,是不是间隔调用时DWR产生了内存泄漏问题? 网上查了一下貌似大家都在讨论这个问题,之前我也挺老手说过DWR有内存问题,可是没有遇到过.原来DWR在间隔调用这种情况下会有问题! 按照大家的说法,修改engine.js配置文件来解决问题,目前我也修改了一下,修改方法如下: 在 dwr.engine._sendData = function(batch) {} 内有,这样几行代码: // Get

  • Android内存泄漏的轻松解决方法

    前言 内存管理的目的就是让我们在开发过程中有效避免我们的应用程序出现内存泄露的问题.内存泄露相信大家都不陌生,我们可以这样理解:「没有用的对象无法回收的现象就是内存泄露」. 如果程序发生了内存泄露,则会带来以下这些问题 应用可用的内存减少,增加了堆内存的压力 降低了应用的性能,比如会触发更频繁的 GC 严重的时候可能会导致内存溢出错误,即 OOM Error 下面我们从基础说起 基础知识 Java 的内存分配简述 方法区(non-heap):编译时就分配好,在程序整个运行期间都存在.它主要存放静

  • vue中的eventBus会不会产生内存泄漏你知道吗

    目录 引入 内容 eventBus在vue中的实现 例子:使用eventBus 例子:不及时取消订阅 其它注意事项 总结 eventBus是在vue中经常用来解决跨组件消息传递的问题,但对它的使用要特别注意,否则会产生很严重的后果. 引入 本文介绍了eventBus的实现原理,并介绍它如何在vue中使用,并举了一个具体的例子来说明,如果使用不当,它会造成内存泄漏. 要注意eventBus并不是前端的概念. 由greenrobot [1] 组织贡献(该组织还贡献了greenDAO),一个Andro

随机推荐