(一)、基础知识普及
(1.1)内存泄漏(out of memory):不会再被使用的对象或者变量占用的内存不能被回收或释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果,就是内存泄漏,一次内存泄漏危害可以忽略,但内存泄漏堆积后果很严重,无论多少内存,迟早会被占光,最终导致内存溢出
(1.2)内存溢出(Memory Leak):通俗的讲就是内存不够,通常是运行的程序所需要的内存远远的超过了主机内存的所承受的大小
强引用 弱引用 软引用 虚引用
- 强引用 一个对象具有强引用,不会被垃圾回收。当内存空间不足,java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象,如果想取消这种关联,可以显示地将引用赋值为null
- 弱引用 JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象,在java中用WeakReference类来表示
- 软引用 当内存不足时,就会回收,在java中用SoftReference类来表示
- 虚引用 忽略
(1)Thread,ThreadLocal,ThreadLocalMap 关系如下图
(1)Thread,ThreadLocal,ThreadLocalMap 内存模型图
通过源码发现,ThreadLocalMap 是Thread的一个类变量,之所以能让线程之间的数据互相不受影响,就是这个原因,如下:
而Entry[]又是ThreadLocalMap的一个成员变量,它是针对一个线程有多个ThreadLocal变量使用的情况,其对象Entry的Key和Value分别是我们的ThreadLocal实例和设置的值,只是ThreadLocalMap是ThreadLocal的一个静态内部类,而Entry又是ThreadLocalMap的一个静态内部类。
如下所示,10000个线程内部的Entry的Key还是同一个ThreadLocal对象实例
拓展点:分析上面图片,不会出现安全问题,因为设置进去的值是放在ThreadLocalMap里面的,而ThreadLocalMap是每个Thread里面的变量,所以不会出现安全问题
(二)、ThreadLocal内存泄漏的原因分析
从上面的内存模型可以看出,ThreadLocalMap使用ThreadLocal实例的弱引用作为key,如果一个ThreadLocal不存在外部强引用(ThreadLocal的变量指向对象)时,key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中的key变为null,而value还存在着强引用,只有thread线程退出去以后,value的强引用链条才会断掉,但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref ->Thread ->ThreadLocalMap ->Entry -> value,永远无法回收,造成内存泄漏;那么如果ThreadLocalMap的key改为强引用是不是就没有内存泄漏了呢?其实是一样的道理,只是ThreadLocalMap的key就会内存泄漏了
解决方法:
- 每次使用完ThreadLocal都调用它的remove()方法清除数据
- 将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉