ThreadLocal是否会引发内存泄露的分析 good
内存 分析 是否 泄露 引发 ThreadLocal Good
2023-09-11 14:19:23 时间
这篇文章,主要解决一下疑惑:
1. ThreadLocal.ThreadLocalMap中提到的弱引用,弱引用究竟会不会被回收?
2. 弱引用什么情况下回收?
3. JAVA的ThreadLocal和在什么情况下会内存泄露?
带着这些疑问,自己模拟了一下ThreadLocal.ThreadLocalMap的结构,先展示下自己涉及的结构:
![](http://img-my.csdn.net/uploads/201303/10/1362880264_4089.png)
自己实现一个simple的ThreadLocalMap,里面用一个entry用来存放由自己模拟的ThreadLocal调用set方法set进去的值。
并且和JDK的ThreadLocalMap一样里面Entry对象的key用weakReference封装。
Main方法如下:
![点击查看原始大小图片](http://dl.iteye.com/upload/attachment/0081/3945/5567355d-0753-3084-bee5-d5577f63dcba.png)
设置运行参数:
-verbose:gc
看输出结果:
![点击查看原始大小图片](http://dl.iteye.com/upload/attachment/0081/3939/3b0e87f7-b47a-3b93-af78-682b0696a1f9.png)
![点击查看原始大小图片](http://dl.iteye.com/upload/attachment/0081/3939/3b0e87f7-b47a-3b93-af78-682b0696a1f9.png)
这里我已经模拟出了内存泄露的问题,可以看到FULL GC以后,内存还是被占用,且仔细观察可以看到,这个map中的Key已经有为null了。
换句话说你通过Key已经不能获取到value了,当然map.get(null)也是可以的,
不过JAVA里的ThreadLocal不会这么去做,因为Map中key==null的元素可能不唯一。
换句话说你通过Key已经不能获取到value了,当然map.get(null)也是可以的,
不过JAVA里的ThreadLocal不会这么去做,因为Map中key==null的元素可能不唯一。
从我的Main方法中可以看到,我有th=null的操作,但是还是有内存泄露,原因稍后分析。
但有一点可以确定:th=null在这里不能如我们想象的将ThreadLocal th 的引用释放掉后,里面的key,value对象也释放,可能会有疑问我这里持有了ThreadLocalMap的引用tm所以不会回收,但实际上,手动设置JAVA的ThreadLocal为null时,当前线程任然持有ThreadLocalMap的引用,所以不会回收我这里和JAVA是类似的。
但有一点可以确定:th=null在这里不能如我们想象的将ThreadLocal th 的引用释放掉后,里面的key,value对象也释放,可能会有疑问我这里持有了ThreadLocalMap的引用tm所以不会回收,但实际上,手动设置JAVA的ThreadLocal为null时,当前线程任然持有ThreadLocalMap的引用,所以不会回收我这里和JAVA是类似的。
回到刚开始提出的3个问题,一一解答:
1. ThreadLocal.ThreadLocalMap中提到的弱引用,弱引用究竟会不会被回收?
会被回收,如上图所示。key 已经有null的情况了。第一个Key不为null,原因在第二点。在经历过FULL GC后 所有的key都被回收了。
2. 弱引用什么情况下回收?
弱引用在GC(包括MinitorGC和Full GC)时,被扫描到就会被回收,但是有一个前提,该弱引用在外部没有被引用到(这个时候外部的引用等于强引用)。
换句话说,如果我main方法中持有一个key的引用,哪怕他put进Map后被设置为弱引用的,也不会被回收。见下图:
![点击查看原始大小图片](http://dl.iteye.com/upload/attachment/0081/3935/dd854d12-b219-337a-83d6-c12d2f74435f.png)
GC 日志:
![点击查看原始大小图片](http://dl.iteye.com/upload/attachment/0081/3937/69557c53-b437-3f24-b354-71a91cf9b0f8.png)
3. JAVA的ThreadLocal和在什么情况下会内存泄露?
答案是不会,原因如下图,在我们调用ThreadLocal.set()的时候,会做一个将Key== null 的元素清理掉的工作,具体做法是:
第一步:ThreadLocalMap 拿threadLocalHashCode与长度减一相与,求出哈希表的位置下图中的 i 。
第二步:编列Entry,如果找到key相等的,覆盖原值! 或者找到key==null的,将值set进去,并且将遍历时路过的key==null的元素和他的value都置为null,,释放内存。
第三步:最后一个if条件时,做rehash的动作,即:将Entry里的元素重新计算一下Hash值,放到合适的位置去,猜想是为了加快下次访问的速度。
![](http://dl.iteye.com/upload/attachment/0081/3931/d9871918-250c-3108-8eb4-cad5664ccd31.png)
总结:
从这里看出,JAVA的ThreadLocal对Key使用到了弱引用,但是为了保证不再内存泄露,在每次set.get的时候主动对key==null的entry做遍历回收。
虽然不会造成内存泄露,但是因为只有在每次set,get的时候才会对entry做key==null的判断,从而释放内存,所以可能使大对象在内存中存活很长一段时间,从而占用内存。
所以,我们在使用完ThreadLocal里的对象后最好能手动remove一下,或者至少调用下ThreadLocal.set(null)。
值得注意的是ThreadLocal中的key是当前当前ThreadLocal自己,就像上面模拟的外部持有强引用的情况,ThreadLocal.ThreadLocalMap中的key==null情况很少出现,因为,大部分情况ThreadLocal是以单例模式一直存在的。
相关文章
- 全面理解Unity加载和内存管理
- go函数详解:函数定义、形参、返回值定义规范、函数内存分析、不支持重载、支持可变参数、基本数据类型和数组默认都是值传递的、支持自定义数据类型、函数返回值命名
- jvm内存溢出分析实践案例:javax.crypto.JceSecurity大量BouncyCastleProvider实例无法被回收
- 使用ABAP memory inspector分析product 搜索内存占用
- RK3588 Android12 DMABUF内存泄漏问题分析
- 内存问题难定位,那是因为你没用ASAN
- Android中使用Handler造成内存泄露的分析和解决
- 【Linux 内核 内存管理】物理分配页 ⑤ ( get_page_from_freelist 快速路径调用函数源码分析 | 遍历备用区域列表 | 启用 cpuset 检查判定 | 判定脏页数量 )
- 【Linux 内核 内存管理】memblock 分配器编程接口 ② ( memblock_add_range 函数分析 | memblock_insert_region 函数分析 )
- 【Linux 内核 内存管理】mmap 系统调用源码分析 ⑤ ( mmap_region 函数执行流程 | mmap_region 函数源码 )
- 【Android 逆向】整体加固脱壳 ( DEX 优化流程分析 | dvmDexFileOpenPartial | dexFileParse | 脱壳点 | 获取 dex 文件在内存中的首地址 )
- cocostudio内存释放
- C++ new和delete(C++动态分配和释放内存)
- LCC编译器的源程序分析(67)删除内存链表
- 排查软件启动时访问了0xcdcdcdcd内存地址导致内存访问违例的崩溃
- RSAC 2022热点议题——勒索软件、AI安全、威胁分析和狩猎,基于机器学习实现内存取证的技术,使用 Volatility 3 + pslist、psscan、pstree、malfind、netscan 等
- Linux下基于内存分析的Rootkit检测方法——传统方法还是检查已知Rootkit组件默认安装路径上是否存在相应文件,并比对文件签名(signature)。这种检测方式显然过于粗糙,对修改过的/新的Rootkit基本无能为力
- Spark2源码阅读——内存分配
- Spark2源码阅读——内存分配
- SGI STL的二级空间配置器的源码剖析并内存池的实现源码剖析
- 为什么 在内存中为什么 0xffff 是 -1