先给出结论:get,set两个方法都不能完全防止内存泄漏,还是每次用完ThreadLocal都勤奋的remove一下靠谱。

前言:

  看到有的博客说在把ThreadLocal的所有强引用置空前,调用 set 或 get 方法的话,则可以防止这个失去所有强引用的ThreadLocal对应的value内存泄漏。  但是文章作者一般没有接着向下讲为什么get,set 方法能防止内存泄漏。 

  本着刨根问底的精神,我们来看看原实现,验证一下get,set方法是否真的能防止内存泄漏。

先介绍一下内存布局:

每个Thread保存自己独占的ThreadLocalMap,ThreadLocalMap包含一个散列表(entry数组),散列表里 entry 继承WeakReference<ThreadLocal>,并且 entry 的 key 隐式等于ThreadLocal, value 则是显示用成员变量来存储。

所以一个线程可以用不同的ThreadLocal把不同的值存在这个线程独享的散列表的不同位置上。下面这些entry的key就是不同的ThreadLocal。当有外部的强引用 使用ThreadLocal的时候,这个ThreadLocal是有效的,但是如果强引用都置空,则只剩弱引用,GC在内存紧张的情况下,可能会把弱引用指向的对象回收掉。

1.ThreadLocal还有效

2.ThreadLocal只剩下弱引用

3.只剩弱引用,回收堆上对象

这样的话,就没有路径可以访问这个ThreadLocal了。

但是value还是通过ThreadLocalMap -> entry -> value -> 堆上大对象 的方式强应用着之前的value。这样导致这块内存无法被使用(如果没有其他强应用的话),也无法被回收。称内存泄漏。

于是ThreadLocalMap的设计者,想出了办法:

1.在ThreadLocal get,set 的时候顺带把散列表中的无效entry 置空,并且把这些entry 的 value也置空,以便value被回收,也就是执行清扫操作

2.在ThreadLocal remove 的时候把对应槽位上的 entry 置空,并且把这 个entry 的 value也置空,以便value被回收。顺便执行清扫操作。

get,set 方法真的能保证内存不泄露么?

这篇文章想讨论的问题是:

1.get,set方法的清扫程度是否足够彻底,以至于可以防止内存泄漏。

2.用什么方法才能保证内存不泄露

1如果成立,也即是保证如下场景内存不泄露:

使用多个 ThreadLocal,不是每次都使用 remove 方法,并且把一个ThreadLocal对应的所有强应用置空之前只调用过 get, set方法,调用get,set方法可以防止内存泄漏。

为了打破这一假设,模拟内存泄漏的情况,举以下极端的例子:

先规定:

1.一开始都是有效的entry,并且每个entry的key通过散列算法后算出的位置都是自己所在的位置(都在自己的位置上的话之后的线性清扫中不会造成搬移,因为ThreadLocalMap的散列表用的是开放定址法,所以entry可能因为hash冲突而不在自己位置上)

要达成下面的效果,就要一直没有失效的entry出现,并且一直实现插入,也就是一直执行set方法

假设entry数组有32个槽位

如果执行一次remove,把图中的某个entry无效化。

下面是实现,因为每个entry都在自己的位置上,所以下图的if (e.get() == key) 会在第一个循环就成立,也就是remove会

执行e.clear() 来把弱引用置空,无效化。并且执行一次线性清扫后返回。

关于线性清扫:

  实现较长,分段看:

  上来就把要清扫的位置给置空了(灰色entry的槽位置空):

接着看:

向后遍历整个数组,直到遇到空槽为止,并且第一种情况 (k == null) 为真的情况下,会把无效entry置空,防止内存泄漏。

其实就是向后扫描,遇到无效的就顺带干掉,直到遇到空位置为止。

第二种情况是 : 遇到的entry是有效的,但是不是在自己原本的位置上,而是被hash冲突所迫而在其他位置上的,则把他们搬去

离原本位置最近的后边空槽上。这样在get的时候,会最快找到这个entry,减少开放定址法遍历数组的时间。

最终的效果只是把我们remove的位置置为空槽。

同理,经过几次remove后,我们可以“挖出”下图的效果

正巧,这时候有两个entry的key,也即是ThreadLocal的所有强应用被置空,于是这两个entry无效。

如果之后只执行 set 方法,是否会内存泄漏呢?是否任意调用set之后就保证内存不会泄漏了呢?

我们顺着 set 方法的逻辑看下去,set方法从当前要set的位置开始向后遍历,直到:

1.遇到 key 和我们当前 调用 set 的 ThreadLocal 相等的 entry,则只用直接把entry的value设置以下就好了,和

HashMap的 put(1, A); put(1, B); 中 A 被替换 成B 同理。(红色框)

2.遇到无效entry,是我们关注的地方

3.遇到空槽,直接插入,并且尝试指数清扫,如果指数清扫不成功并且当前entry的使用槽数到达阈值则重散列(蓝色框)

我们重点关注情况2.

假设我们set的位置是下面所指处。

我们接着上面的2分析,2要调用到replaceStaleEntry

再假设set进去的ThreadLocal在本数组中下面绿色位置

方法一开始是找到最靠前的无效entry,直到遇到空槽为止,当然可能会绕数组一圈绕回来

但是因为使用的槽数如果到达阈值,就会rehash,不可能所有槽都用完。所以会遇到空槽的。

表现在我们的例子中:

因为没有找到,所以 slotToExpunge = staleSlot

也就是上图第二个灰色entry的位置。

接着向下看:

我们关注 k == key 的情况,也就是 i 遍历图中绿色槽位的情况。 这种情况下会指向一次线性清扫,然后执行对数清扫。之后返回。

反应在图例中:

从slotToExpunge位置开始,先进行一轮线性清扫:

同之前一样,一上来先把待清扫槽位置空(第二个灰色的entry的位置),之后遇到第二个灰色后面那个空位,所以停下来。

线性清扫返回空位的下标做为参数传给对数清扫。

反应到图例:

对数清扫:清扫次数 = log2(N) ,N是散列表大小,本例中是32,所以要清扫5次,每次清扫是通过调用线性清扫实现的。

并且只有遇到无效entry时才执行线性清扫。

显然,五次扫描中都没有无效entry

返回 removed (false);

cleanSomeSlots要返回,一直返回到replaceStaleEntry,并且继续返回,最后从set方法返回。

 

结果很明显,第一个灰色entry未被清除。

结论:set方法的清扫程度不够深,set方法并不能防止内存泄漏。

get方法呢?

get 方法比较简单,在原本属于当前 key 的位置上找不到当前 key 的 entry 的话,就会根据开放定址法线性遍历找到 key 对应的 entry 。

顺便把路上无效的entry用线性清扫清除掉。

还是刚刚的极端例子:

因为是直接取线性清扫开始的位置,所以 k = key 是 true,所以返回绿色entry。查找成功

但是,第一个灰色entry仍然没有被清除。

什么办法可以保证万无一失呢???

答:每次置空一个ThreadLocal的所有强引用之后,都调用ThreadLocal的remove方法:

e.clear是直接置空弱引用,这样当前这个entry就会无效

之前说过,线性清扫会直接把第一个无效entry,也就是起点的entry槽位置空,以此达到 100 % 的回收效果。

结论:

get,set两个方法都不能完全防止内存泄漏,还是每次用完ThreadLocal都勤奋的remove一下靠谱。

证明:ThreadLocal的get,set方法无法防止内存泄漏的更多相关文章

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

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

  2. 重写hashCode方法,导致内存泄漏

    package com.nchu.learn.base.reflect; import org.junit.Test; import java.util.Collection; import java ...

  3. java中的hashcode方法作用以及内存泄漏问题

    本文装载:http://hi.baidu.com/iduany/item/6d66dfc9d5f2da1650505870 hashCode()方法的作用&使用分析 一直以来都想写篇文章来说明 ...

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

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

  5. 分析 ThreadLocal 内存泄漏问题

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

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

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

  7. 18.一篇文章,从源码深入详解ThreadLocal内存泄漏问题

    1. 造成内存泄漏的原因? threadLocal是为了解决对象不能被多线程共享访问的问题,通过threadLocal.set方法将对象实例保存在每个线程自己所拥有的threadLocalMap中,这 ...

  8. ThreadLocal为什么会内存泄漏

    1.首先看下ThreadLocal的原理图: 在ThreadLocal的生命周期中,都存在这些引用. 其中,实线代表强引用,虚线代表弱引用: 2.ThreadLocal的实现:每个Thread维护一个 ...

  9. ThreadLocal内存泄漏真因探究(转)

    出处: 链接:https://www.jianshu.com/p/a1cd61fa22da ThreadLocal原理回顾 ThreadLocal的原理:每个Thread内部维护着一个ThreadLo ...

随机推荐

  1. Mac下安装octave

    1.首先安装Command Line Tool xcode-select --install2.Mac OSX平台下,用神器Homebrew安装 curl -LsSf http://github.co ...

  2. qemu-guest-agent详解

    qemu guest agent简称qga, 是运行在虚拟机内部的一个守护程序(qemu-guest-agent.service),他可以管理应用程序,执行宿主机发出的命令. QEMU为宿主机和虚拟机 ...

  3. 【JMeter_03】JMeter GUI操作界面介绍

    JMeter主界面主要分为 标题栏.菜单栏.工具栏.测试计划树形目录.内容展示区 标题栏:主要展示JMeter的程序版本.当前脚本的名称.脚本的储存路径 菜单栏:程序基本上所有功能的所属分类目录,基本 ...

  4. .Net Core踩坑记:读取txt中文乱码

    迁移.net framework的项目,有块读取txt中文转码的问题,普通的不能再普通的代码,想都没想直接copy过去,也没测,结果今天就被坑了.Core是3.1版本,这是原来的代码: string ...

  5. Java Service Wrapper 浅谈

    在实际开发过程中很多模块需要独立运行,他们并不会以web形式发布,传统的做法是将其压缩为jar包独立运行,这种形式简单易行也比较利于维护,但是 一旦服务器重启或出现异常时,程序往往无法自行修复或重启. ...

  6. JavaWeb网上图书商城完整项目--day03-1.图书模块功能介绍及相关类创建

    1 前两天我们学习了user用户模块和图书的分类模块,接下来我们学习图书模块 图书模块的功能主要是下面的功能: 2 接下来我们创建对应的包 我们来看看对应的数据库表t_book CREATE TABL ...

  7. Jmeter系列(29)- 详解 JDBC Connection Configuration

    如果你想从头学习Jmeter,可以看看这个系列的文章哦 https://www.cnblogs.com/poloyy/category/1746599.html 前言 发起 jdbc 请求前,需要有 ...

  8. 动态追踪技术之SystemTap

    SystemTap SystemTap是一个深入检查Linux系统活动的工具,使用该工具编写一些简单的代码就可以轻松的提取应用或内核的运行数据,以诊断复杂的性能或者功能问题.有了它,开发者不再需要重编 ...

  9. html+css快速入门教程(2)

    3 标签 3.1 div div 标签表示一个区块或者区域,你可以把它看成是一个容器,比如说一个 竹篮 作用:用来把网页分块 并且里面可以装任意的html元素 <div>这里是一个div容 ...

  10. NodeMCU手把手入门:配置NodeMCU ESP8266开发板环境及点亮LED灯

    之前一直在玩树莓派,最近实验室买了些NodeMCU就想着玩一玩,没想到挺有意思的.其实树莓派能实现的功能,它大部分也可以,价格比派也便宜不少,舍不得买派的同学可以先买这个开发板玩一玩. 本文主要介绍了 ...