厦网站建设培训学校,wordpress简约博客主题,企业网站推广推广阶段,做网站需要看的书优质博文#xff1a;IT-BLOG-CN
从 ThreadLocalMap看 ThreadLocal使用不当的内存泄漏问题
【1】基础概念 #xff1a; 首先我们先看看ThreadLocalMap的类图#xff0c;我们知道 ThreadLocal只是一个工具类#xff0c;他为用户提供get、set、remove接口操作实际存放本地变…优质博文IT-BLOG-CN
从 ThreadLocalMap看 ThreadLocal使用不当的内存泄漏问题
【1】基础概念 首先我们先看看ThreadLocalMap的类图我们知道 ThreadLocal只是一个工具类他为用户提供get、set、remove接口操作实际存放本地变量的threadLocals调用线程的成员变量也知道 threadLocals是一个ThreadLocalMap类型的变量下面我们来看看ThreadLocalMap这个类。在此之前我们回忆一下Java中的四种引用类型链接 【2】分析ThreadLocalMap内部实现 我们知道ThreadLocalMap内部实际上是一个Entry数组private Entry[] table我们先看看Entry的这个内部类
/*** 是继承自WeakReference的一个类该类中实际存放的key是指向ThreadLocal的弱引用和与之对应的value值(该value值* 就是通过ThreadLocal的set方法传递过来的值)由于是弱引用当get方法返回null的时候意味着回收引用*/
static class Entry extends WeakReferenceThreadLocal? {/** value就是和ThreadLocal绑定的 */Object value;//kThreadLocal的引用被传递给WeakReference的构造方法Entry(ThreadLocal? k, Object v) {super(k);value v;}
}
//WeakReference构造方法(public class WeakReferenceT extends ReferenceT )
public WeakReference(T referent) {super(referent); //referentThreadLocal的引用
}//Reference构造方法
Reference(T referent) {this(referent, null);//referentThreadLocal的引用
}Reference(T referent, ReferenceQueue? super T queue) {this.referent referent;this.queue (queue null) ? ReferenceQueue.NULL : queue;
}在上面的代码中我们可以看出当前ThreadLocal的引用k被传递给WeakReference的构造函数所以ThreadLocalMap中的key为ThreadLocal的弱引用。当一个线程调用ThreadLocal的set方法设置变量的时候当前线程的 ThreadLocalMap就会存放一个记录这个记录的key值为ThreadLocal的弱引用value就是通过set设置的值。如果当前线程一直存在且没有调用该ThreadLocal的remove方法如果这个时候别的地方还有对ThreadLocal的引用那么当前线程中的ThreadLocalMap中会存在对ThreadLocal变量的引用和 value对象的引用是不会释放的就会造成内存泄漏。
考虑这个ThreadLocal变量没有其他强依赖如果当前线程还存在由于线程的ThreadLocalMap里面的key是弱引用所以当前线程的ThreadLocalMap里面的ThreadLocal变量的弱引用在gc的时候就被回收但是对应的value还是存在的这就可能造成内存泄漏(因为这个时候 ThreadLocalMap会存在key为 null但是value不为null的entry项 )。 ThreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用在没有其他地方对ThreadLocal依赖ThreadLocalMap中的ThreadLocal对象就会被回收掉但是对应的value不会被回收这个时候Map中就可能存在key为null但是value不为null的项这需要实际使用的时候使用完毕及时调用 remove方法避免内存泄漏。 如果使用线程池由于线程可能并不是真正的关闭比如newFixedThreadPool会保持线程一只存活。因此如果将一些大对象存放到ThreadLocalMap中可能会造成内存泄漏。因为线程没有关闭无法回收但是这些对象不会再被使用了。如果希望及时回收对象则可以使用Thread.remove()方法将变量移除。
ThreadLocalObject threadLocal new ThreadLocal();
// 存储数据
threadLocal.set(someData);
// 使用完毕后清除
threadLocal.remove();我们再看下ThreadLocal底层的源码
public T get() { //获取当前线程Thread t Thread.currentThread(); //获取当前线程的ThreadLocalMap变量ThreadLocalMap map getMap(t); if (map ! null) { ThreadLocalMap.Entry e map.getEntry(this); if (e ! null) { SuppressWarnings(unchecked) T result (T)e.value; return result; } } return setInitialValue();
}public void set(T value) { Thread t Thread.currentThread(); ThreadLocalMap map getMap(t); if (map ! null) map.set(this, value); else createMap(t, value);
}public void remove() { ThreadLocalMap m getMap(Thread.currentThread()); if (m ! null) m.remove(this);
}remove()方法逻辑比较简单首先获取当前线程的ThreadLocalMap对象然后循环遍历key将目标key以及对应的value都设置为null。
private void remove(ThreadLocal? key) {Entry[] tab table;int len tab.length;int i key.threadLocalHashCode (len-1);// 循环遍历目标key然后将key和value都设置为nullfor (Entry e tab[i];e ! null;e tab[i nextIndex(i, len)]) {if (e.get() key) {e.clear();// 清理value值expungeStaleEntry(i);return;}}
}使用try-with-resources或try-finally块如果你的ThreadLocal变量在需要清理的资源管理上下文中使用可以使用try-with-resources自动清理或try-finally手动清理块来确保及时清理。
try (ThreadLocalResource resource new ThreadLocalResource()) {// 使用 ThreadLocalResource
}
// 或者使用 try-finally
ThreadLocalResource resource new ThreadLocalResource();
try {// 使用 ThreadLocalResource
} finally {resource.close(); // 在 close 方法中清理 ThreadLocal 变量
}使用InheritableThreadLocal如果需要在子线程中访问父线程的ThreadLocal变量并且确保在子线程中正确清理可以考虑使用InheritableThreadLocal。这个类允许子线程继承父线程的ThreadLocal变量并在子线程完成后自动清理。
ThreadLocalString threadLocal new InheritableThreadLocal();
threadLocal.set(Hello, Parent Thread);
Runnable childTask () - {String value threadLocal.get(); // 子线程可以访问父线程的 ThreadLocal 变量// ...
};
Thread childThread new Thread(childTask);
childThread.start();