为了更好的提供文章,我已经将博客迁移到了自建的博客网站上,我将更多的从源码分析的角度入手,为大家带来更多的深度文章,请大家继续关注我~! 博客地址:www.liuinsect.com
_______________________________________________________________________________
这篇文章,主要解决一下疑惑:
1. ThreadLocal.ThreadLocalMap中提到的弱引用,弱引用究竟会不会被回收?
2. 弱引用什么情况下回收?
3. JAVA的ThreadLocal和在什么情况下会内存泄露?
带着这些疑问,自己模拟了一下ThreadLocal.ThreadLocalMap的结构,先展示下自己涉及的结构:
自己实现一个simple的ThreadLocalMap,里面用一个entry用来存放由自己模拟的ThreadLocal调用set方法set进去的值。
并且和JDK的ThreadLocalMap一样里面Entry对象的key用weakReference封装。
Main方法如下:
设置运行参数:
-verbose:gc
看输出结果:
这里我已经模拟出了内存泄露的问题,可以看到FULL GC以后,内存还是被占用,且仔细观察可以看到,这个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是类似的。
回到刚开始提出的3个问题,一一解答:
1. ThreadLocal.ThreadLocalMap中提到的弱引用,弱引用究竟会不会被回收?
会被回收,如上图所示。key 已经有null的情况了。第一个Key不为null,原因在第二点。在经历过FULL GC后 所有的key都被回收了。
2. 弱引用什么情况下回收?
弱引用在GC(包括MinitorGC和Full GC)时,被扫描到就会被回收,但是有一个前提,该弱引用在外部没有被引用到(这个时候外部的引用等于强引用)。
换句话说,如果我main方法中持有一个key的引用,哪怕他put进Map后被设置为弱引用的,也不会被回收。见下图:
GC 日志:
3. JAVA的ThreadLocal和在什么情况下会内存泄露?
答案是不会,原因如下图,在我们调用ThreadLocal.set()的时候,会做一个将Key== null 的元素清理掉的工作,具体做法是:
第一步:ThreadLocalMap 拿threadLocalHashCode与长度减一相与,求出哈希表的位置下图中的 i 。
第二步:编列Entry,如果找到key相等的,覆盖原值! 或者找到key==null的,将值set进去,并且将遍历时路过的key==null的元素和他的value都置为null,,释放内存。
第三步:最后一个if条件时,做rehash的动作,即:将Entry里的元素重新计算一下Hash值,放到合适的位置去,猜想是为了加快下次访问的速度。
总结:
从这里看出,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是以单例模式一直存在的。
相关推荐
但这里,`ThreadLocal` 的设计使得其内部的引用(即使是最弱的引用)在类加载器试图卸载时依然存在,这就形成了一个内存泄漏点。 解决这个问题的关键在于,我们需要确保在不再需要 `ThreadLocal` 变量时,及时清除...
Entry继承自WeakReference<ThreadLocal>,这意味着ThreadLocal对象如果不再被引用,可以被垃圾收集器回收,避免内存泄漏。然而,这种设计也存在一个问题:如果ThreadLocal没有被外部引用,但其对应的Entry还在Map中...
### 导致JVM内存泄露的ThreadLocal详解 #### 一、为什么要有ThreadLocal 在多线程编程中,为了避免线程间的数据竞争和保证线程安全性,常常需要使用同步机制如`synchronized`来控制线程对共享资源的访问。然而,...
4. **内存泄漏问题**:尽管Entry使用了弱引用,但当线程不结束而ThreadLocal对象被回收时,Entry中的value仍然存在,可能导致内存泄漏。因此,使用完ThreadLocal后,最好显式调用`remove()`方法。 5. **线程局部...
java并发编程 基础知识,守护线程与线程, 并行和并发有什么区别? 什么是上下文切换? 线程和进程区别 什么是线程和进程? 创建线程有哪几种方式?,如何避免线程死锁 ...ThreadLocal内存泄漏分析与
4. ThreadLocal:如果线程局部变量未被正确清理,可能会导致内存泄漏。 5. 注册监听器未取消:注册的监听器如果没有在适当的时候解除注册,会持续持有对对象的引用。 四、内存泄漏的检测方法 1. **手动代码审查**...
过度依赖ThreadLocal可能导致代码难以理解和维护,且可能导致内存泄漏。在设计和使用ThreadLocal时,需要谨慎评估其必要性和潜在风险。 在分析ThreadLocal源码时,可以了解到它如何在内部实现线程隔离,以及...
1. **工具辅助**:使用诸如VisualVM、JProfiler等工具来监控和分析应用程序的内存使用情况,找出潜在的内存泄漏源。 2. **代码审查**:定期进行代码审查,尤其是关注静态集合类、监听器、内部类以及ThreadLocal的...
3. 内存管理:了解Java的内存模型和垃圾回收机制,才能理解ThreadLocal的内存泄漏风险和弱引用的作用。 4. HTTP相关:虽然题目中没有直接涉及,但HTTPClient是一个常见的网络通信工具,经常和ThreadLocal结合使用,...
然而,如果线程持续运行但不再使用某些`ThreadLocal`变量,应显式调用`remove()`方法释放这些变量,避免内存泄漏。 #### 五、ThreadLocal的应用场景 1. **数据库连接管理**:如上文的Hibernate示例,通过`...
- **Leak Suspects**:提供自动分析内存泄露的可能原因。 ##### 3.3 具体案例分析 假设在生产环境中,一个项目运行三周左右会出现内存溢出异常。JDK 使用的是 64 位版本,配置参数为 `-Xmx3078M -Xms3078M -XX:...
- 执行remove操作,之后通过调试工具检查内存中的对象是否被回收,以此来验证ThreadLocal的内存泄漏问题。 ThreadLocal是一个功能强大但又需要仔细使用的工具,掌握其原理和正确使用方法对于编写高性能和高可靠性的...
- 清理:如果不手动调用`remove()`方法,线程结束后,ThreadLocal变量不会自动清除,可能导致内存泄漏。因此,使用完ThreadLocal变量后,最好显式调用`remove()`。 4. **避免内存泄漏**: 由于ThreadLocalMap使用...
* 内存泄露:如果 ThreadLocal 对象不被正确释放,将会导致内存泄露。 * 性能问题: ThreadLocal 的实现机制可能会带来一些性能问题,例如哈希表的查找和更新操作。 ThreadLocal 是 Java 语言中的一种机制,用于...
如果线程长时间运行或应用中存在大量线程复用,不及时清理ThreadLocal可能导致内存泄漏。 - 应在不再需要ThreadLocal时调用`remove()`方法来清除,以避免内存资源浪费。 3. **ThreadLocal的内部实现** - ...
因此,如果不再使用ThreadLocal,建议显式调用`remove()`方法,以避免内存泄漏。 ### 3. ThreadLocal的工作原理 ThreadLocal的内部维护了一个ThreadLocalMap,它是基于弱引用的HashMap。每个线程都有自己的...
4. **清理ThreadLocal**:由于ThreadLocal变量在内存中会占用资源,当线程结束或者不再需要这些变量时,应当使用`remove()`方法进行清理,防止内存泄漏。 ```java threadLocalVar.remove(); ``` ThreadLocal的...
- 内存泄漏:如果不再使用ThreadLocal,但没有调用`remove()`,那么在长时间运行的应用中,这些线程局部变量可能会导致内存泄漏。因为线程结束后,其ThreadLocal的引用并不会自动清除。 - 不适用于跨线程通信:...