一、OOM的场景
1、代码
一个线程数为500的线程池,所有线程共享一个ThreadLocal变量,每一个线程执行的时候插入一个大的List集合。
2、设置JVM参数设置最大内存为256M
3、输出结果
可以看出,单线程池执行到第212的时候,就报了错误,出现OOM内存溢出错误。
二、ThreadLocal为什么会内存泄漏
1、首先看下使用ThreadLocal是如何保存数据的
public class ThreadLocal<T> {
...
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
...
}
可以看到,数据实际是保存在Thread t对象的threadLocals属性上的,key为threadLocal,value为调用threadLocal.set()方法传进来的值。
2、ThreadLocal的原理图
实线代表强引用,虚线代表弱引用
3、泄漏的原因
ThreadLocal使用完成后,ThreadLocalRef变为了null,ThreadLocalMap指向ThreadLocal的引用是弱引用,垃圾回收器一旦发现ThreadLocal上只有弱引用(一定请注意只有弱引用,没强引用),不管当前内存空间是否足够,那么都会回收ThreadLocal。此时ThreadLocal对象是可以被回收的。
但是由于线程一直存在,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
只有当前thread结束以后,current thread就不会存在栈中,强引用断开,Current Thread、Map value将全部被GC回收。最好的做法是将调用threadlocal的remove方法
三、为什么ThreadLocalMap指向ThreadLocal的引用是弱引用?
如果key 使用强引用,引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,会导致ThreadLocal不会被回收,导致Entry内存泄漏。
key 使用弱引用的好处是:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。
四、怎么避免内存泄漏呢?
每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就清理。