前言
面试时候总被问到“为什么ThreadLocal会内存泄漏”,也看过很多文章来解释,什么弱引用呀,什么手动remove呀,但是总感觉一知半解的,今天就来彻底搞明白为什么ThreadLocal会内存泄漏。
觉得不错的同学可以加我公众号,会经常分享一些技术干货,以及热点AI和科技新闻
一、什么是内存泄漏
顾名思义,就是可用内存变少了。
那么,在JVM虚拟机中,为什么会发生内存泄漏呢,因为有垃圾回收机制,所以如果一块没有用的内存空间不会被回收,那么这块空间本身就浪费掉了,因为这块内存无法回收,你又没办法使用。
二、弱引用是什么?
- 强引用:
我们平时用的最多的一种引用就是强引用,简单的说来一个强引用就是一个引用变量,如 StringBuilder a = new StringBuilder();这里StringBuilder类型的类型变量a就是我们new出来的那个StringBuilder()的强引用,当a一直指向new 出的那个StringBuilder对象时,该对象的空间就不会被GC掉(忽略编译器优化情况下)。
即便是发生OOM错误都不会回收有强引用指向的对象 - 软引用
byte[] b = new byte[2*1024*1024];
//定义一个引用队列
ReferenceQueue<byte[]> refQueue = new ReferenceQueue<byte[]>();
//定义一个软引用对象,并保存b引用
SoftReference<byte[]> sref = new SoftReference(b,refQueue);
当内存充裕的时候不回收,而是等到空间不足的时候才回收这片内存。
同时只有当软引用对象保存的引用指向的空间被GC后,软引用对象才放置到引用队列中
- 弱引用
byte[] b = new byte[2*1024*1024];
//定义一个引用队列
ReferenceQueue<byte[]> refQueue = new ReferenceQueue<byte[]>();
//定义一个弱引用对象,并保存b引用
WeakReference<byte[]> wref = new WeakReference<byte[]>(b,refQueue);
只要发生GC就会被回收,并将弱引用放入到引用队列中
三、什么情况下会发生内存泄漏
2.1 ThreadLocal 内存模型
大家看源码就会发现,Entry中key对ThreadLocal对象的引用是弱引用
先看个代码示例:
ThreadLocal<String> ThreadLocalReference = new ThreadLocal<>();
ThreadLocalReference.set("test");
ThreadLocalReference = null; // 此时ThreadLocalReference对ThreadLocal的引用消失
// 此时如果发生GC,ThreadLocal会被回收,因为是弱引用
// 如果线程还没结束,则ThreadLocalMap无法访问到value,但是value不会被回收,所以value发生了内存泄漏
那么,如果设计强引用,当ThreadLocalReference = null;执行后,发生GC时ThreadLocalReference引用的ThreadLocal应用被回收,、
但是由于ThreadLocal还被key强引用,所以不会被回收,这时ThreadLocal又发生了内存泄漏
- value发生内存泄漏
当ThreadLocalReference = null;执行完成后,图中ThreadLocalReference对ThreadLocal的引用消失,ThreadLocal应用被回收- 如果发生GC,由于Entry中key对ThreadLocal对象的引用是弱引用,ThreadLocal对象又没有强引用指向它,所以ThreadLocal会被回收
- 如果线程还没结束,则ThreadLocalMap无法访问到value(因为key没有了),但是value不会被回收(因为他还在被ThreadLocalMap强引用),所以value发生了内存泄漏
- 如果key设计成强引用
当ThreadLocalReference = null;执行完成后,图中ThreadLocalReference对ThreadLocal的引用消失,ThreadLocal本应该被回收,但是由于ThreadLocal还被key强引用,所以不会被回收,这时ThreadLocal又发生了内存泄漏