页面载入中...

当前位置:首页 > 技术分享 > 网络技术 > 网络技术

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

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

  前言: 06Q爱图古源码汇

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

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

 06Q爱图古源码汇

先介绍一下内存布局:06Q爱图古源码汇

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

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

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

1.ThreadLocal还有效06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

2.ThreadLocal只剩下弱引用06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

3.只剩弱引用,回收堆上对象06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

这样的话,就没有路径可以访问这个ThreadLocal了。06Q爱图古源码汇

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

 06Q爱图古源码汇

于是ThreadLocalMap的设计者,想出了办法:06Q爱图古源码汇

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

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

 06Q爱图古源码汇

get,set 方法真的能保证内存不泄露么?06Q爱图古源码汇

这篇文章想讨论的问题是:06Q爱图古源码汇

1.get,set方法的清扫程度是否足够彻底,以至于可以防止内存泄漏。06Q爱图古源码汇

2.用什么方法才能保证内存不泄露06Q爱图古源码汇

 06Q爱图古源码汇

1如果成立,也即是保证如下场景内存不泄露:06Q爱图古源码汇

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

为了打破这一假设,模拟内存泄漏的情况,举以下极端的例子:06Q爱图古源码汇

 06Q爱图古源码汇

先规定:06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

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

要达成下面的效果,就要一直没有失效的entry出现,并且一直实现插入,也就是一直执行set方法06Q爱图古源码汇

假设entry数组有32个槽位06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

如果执行一次remove,把图中的某个entry无效化。06Q爱图古源码汇

   证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

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

 执行e.clear() 来把弱引用置空,无效化。并且执行一次线性清扫后返回。06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

关于线性清扫:06Q爱图古源码汇

实现较长,分段看:06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

上来就把要清扫的位置给置空了(灰色entry的槽位置空):06Q爱图古源码汇

 证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

接着看:06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

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

 其实就是向后扫描,遇到无效的就顺带干掉,直到遇到空位置为止。06Q爱图古源码汇

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

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

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 因为每个entry都在自己的位置上,并且没有遇到无效的entry,最终的效果只是把remove的位置置为空槽。06Q爱图古源码汇

同理,经过几次remove后,我们可以 挖出 下图的效果证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

正巧,这时候有两个entry的key,也即是ThreadLocal的所有强应用被置空,于是这两个entry无效。06Q爱图古源码汇

     证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

如果之后只执行 set 方法,是否会内存泄漏呢?是否任意调用set之后就保证内存不会泄漏了呢?06Q爱图古源码汇

我们顺着 set 方法的逻辑看下去,set方法从当前要set的位置开始向后遍历,直到:06Q爱图古源码汇

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

HashMap的 put(1, A); put(1, B); 中 A 被替换 成B 同理。(红色框)06Q爱图古源码汇

2.遇到无效entry,是我们关注的地方。06Q爱图古源码汇

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

证明:ThreadLocal的get,set方法无法防止内存泄漏 证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

我们重点关注情况2.06Q爱图古源码汇

假设我们set的位置是下面所指处。06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

我们接着上面的2分析,2要调用到replaceStaleEntry06Q爱图古源码汇

再假设set进去的ThreadLocal在本数组中下面绿色位置06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

绿色代表这个entry不在自己的原本位置上,上面的情况是可以得到的。因为remove时执行的线性清扫是向后清扫,并且遇到空槽停下。06Q爱图古源码汇

所以不会影响绿色entry06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

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

但是因为使用的槽数如果到达阈值,就会rehash,不可能所有槽都用完。所以会遇到空槽的。06Q爱图古源码汇

表现在我们的例子中:06Q爱图古源码汇

 证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

因为没有找到,所以 slotToExpunge = staleSlot06Q爱图古源码汇

也就是上图第二个灰色entry的位置。06Q爱图古源码汇

 接着向下看:06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

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

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

反应在图例中:06Q爱图古源码汇

 06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

从slotToExpunge位置开始,先进行一轮线性清扫:06Q爱图古源码汇

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

线性清扫返回空位的下标做为参数传给对数清扫。06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

反应到图例:06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

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

并且只有遇到无效entry时才执行线性清扫。06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 显然,五次扫描中都没有无效entry06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

返回 removed (false);06Q爱图古源码汇

 cleanSomeSlots要返回,一直返回到replaceStaleEntry,并且继续返回,最后从set方法返回。06Q爱图古源码汇

 证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 结果很明显,第一个灰色entry未被清除。06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

结论:set方法的清扫程度不够深,set方法并不能防止内存泄漏。06Q爱图古源码汇

get方法呢?06Q爱图古源码汇

 证明:ThreadLocal的get,set方法无法防止内存泄漏证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

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

顺便把路上无效的entry用线性清扫清除掉。06Q爱图古源码汇

还是刚刚的极端例子:06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

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

 证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

 但是,第一个灰色entry仍然没有被清除。06Q爱图古源码汇

 什么办法可以保证万无一失呢???06Q爱图古源码汇

答:每次置空一个ThreadLocal的所有强引用之后,都调用ThreadLocal的remove方法:06Q爱图古源码汇

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

e.clear是直接置空弱引用,这样当前这个entry就会无效06Q爱图古源码汇

 证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

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

证明:ThreadLocal的get,set方法无法防止内存泄漏06Q爱图古源码汇

 06Q爱图古源码汇

结论:06Q爱图古源码汇

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

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

 06Q爱图古源码汇

免责申明:注册会员下载资源前请仔细阅读本站条款
本站所有资源都来自于网络,版权归原创者所有!本站网络资源不提供任何售后和服务,也不承担任何法律责任,如有侵权或危害了你的利益,请出示相应的资质证明并联系站长,我们将马上予以删除。
来源:爱图古源码网(站长邮箱:a13879801652@foxmail.com 转载请保留出处!)
本文地址:https://www.aitgoo.com/wangluo/35927.html
相关关键词: