ThreadLocal内存泄漏案例分析实战
2021/11/17 7:12:01
本文主要是介绍ThreadLocal内存泄漏案例分析实战,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
用代码实战,彻底搞清楚ThreadLocal发生内存泄漏的情况。很多文章讲的模棱两可,在和群友的沟通中,基本弄清楚了ThreadLocal到底是什么回事,解决大多数文章都无法把知识点和实际使用结合起来讲。
先写个小例子
/** * 测试threadLocal内存泄漏 * 01:固定6个线程,每个线程持有一个变量 * 按理来说会有 6 * 5 = 30M内存无法回收,其余的在set方法中覆盖了。 */ public class ThreadLocalOutOfMemoryTest { static class LocalVariable { //总共有5M private byte[] locla = new byte[1024 * 1024 * 5]; } // (1)创建了一个核心线程数和最大线程数为 6 的线程池,这个保证了线程池里面随时都有 6 个线程在运行 final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(6, 6, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>()); // (2)创建了一个 ThreadLocal 的变量,泛型参数为 LocalVariable,LocalVariable 内部是一个 Long 数组 static ThreadLocal<LocalVariable> localVariable = new ThreadLocal<LocalVariable>(); public static void main(String[] args) throws InterruptedException { // (3)向线程池里面放入 50 个任务 for (int i = 0; i < 50; ++i) { poolExecutor.execute(new Runnable() { @Override public void run() { // (4) 往threadLocal变量设置值 LocalVariable localVariable = new LocalVariable(); // 会覆盖 ThreadLocalOutOfMemoryTest.localVariable.set(localVariable); // (5) 手动清理ThreadLocal System.out.println("thread name end:" + Thread.currentThread().getName() + ", value:"+ ThreadLocalOutOfMemoryTest.localVariable.get()); // ThreadLocalOutOfMemoryTest.localVariable.remove(); } }); Thread.sleep(1000); } // (6)是否让key失效,都不影响。只要持有的线程存在,都无法回收。 //ThreadLocalOutOfMemoryTest.localVariable = null; System.out.println("pool execute over"); } } 复制代码
- 先打开jdk工具包下面的
jvisualvm.exe
,在运行这个小例子。等待程序结束,主动触发一次垃圾回收。
我们可以看到,大概还有30M的内存没有回收。和我们预期的结论一致。
a.为什么不是50*5=250M 呢?
因为java.lang.ThreadLocal#set()
是会覆盖的,每个线程持有一份,同一个线程执行多次,由于key是同一个ThreadLocal变量,所以会路由到数组的同一个位置上,直接覆盖上次的value。
b.为什么无法被回收掉30M的内存呢?
因为线程池中的6个线程存活,对ThreadLocalMap持有强引用
- 打开第5处的代码,再次运行观察堆内存:
这个很好理解,因为我们手动调用了remove方法,清理了ThreadLocalMap中的对象(把Entry对象指向了null),没有了强引用,当然直接被回收。
- 关闭第5处代码,再打开第6处代码:
和第一种情况一致,任然还有30M的内存无法回收。所以ThreadLocalMap的回收其实和Entry对象的Key是弱引用没有太大关系。
只要持有ThreadLocalMap的线程存活,不管Key失效或者未失效,value都不会被回收。所以才要求我们使用的过程中要即时清理。
ThreadLocalMap的整体结构
- ThreadLocalMap是ThreadLocal的内部类,内部维护了一个Entry类型的table数组。
- Entry对象由Key和Value两个成员变量,key是对ThreadLocal对象的弱引用③,Value是针对这个threadLocal存入的Object类型对象。
- key弱引用threadLocal,每次GC,如果threadLocal对象只剩下被这个ThreadLocalMap的key弱引用, 那么对象将被回收掉,key为null值。
- 程序对ThreadLocal变量的强引用,当这个强引用消失的时候,threadLocal将会被回收。类似与我们代码:
ThreadLocal local = new ThreadLocal(); local = null; 复制代码
- 线程Thread对象对整个ThreadLocalMap持有的强引用。
问题:③这里为什么要使用弱引用呢?
假设一种在Tomcat线程池使用ThradLocal的场景,线程持有对ThreadLocalMap的强引用,导致ThreadLocalMap的生命周期很长。假设为每一个请求创建一个ThreadLocal对象用于存储session,如果不及时销毁,整个ThreadLocalMap占用的内存会越来越大。 提前把Key设置为thradLocal对象的弱应用,当程序不在引用threadLocal对象的时候,gc就可以快速回收掉thradLocal对象,Entry的key为null。在ThreadLocalMap#set方法中会清理key=null的Entry对象,以达到回收内存的目的。
小结
- Thread -> ThreadLocalMap是一对一,一个线程维护一个ThreadLocalMap。
- Thread -> ThreadLocal是一对多,一个线程可以拥有多个ThreadLocal对象,分别hash映射到数组不用的索引。
- Entry中key的弱引用可以看成是协助清理ThreadLocalMap中的Entry键值对的一种方法。但是需要手动把threadLocal变量设置为null:
threadLoca=null
- ThreadLocal内存泄漏和key采用弱引用的关系不大,因为实际编码中没人这样编码
threadLoca=null
,所以是否回收ThreadLocalMap主要取决于线程的生命周期。 - 在线程长生命周期的场景中,用完变量后,要调用ThreadLocalMap#remove()主动清理。
作者:城南码农
链接:https://juejin.cn/post/6982121384533032991
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这篇关于ThreadLocal内存泄漏案例分析实战的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-05-15PingCAP 黄东旭参与 CCF 秀湖会议,共探开源教育未来
- 2024-05-13PingCAP 戴涛:构建面向未来的金融核心系统
- 2024-05-09flutter3.x_macos桌面os实战
- 2024-05-09Rust中的并发性:Sync 和 Send Traits
- 2024-05-08使用Ollama和OpenWebUI在CPU上玩转Meta Llama3-8B
- 2024-05-08完工标准(DoD)与验收条件(AC)究竟有什么不同?
- 2024-05-084万 star 的 NocoDB 在 sealos 上一键起,轻松把数据库编程智能表格
- 2024-05-08Mac 版Stable Diffusion WebUI的安装
- 2024-05-08解锁CodeGeeX智能问答中3项独有的隐藏技能
- 2024-05-08RAG算法优化+新增代码仓库支持,CodeGeeX的@repo功能效果提升