ThreadLoad如何防止内存溢出

程序猿进阶 2024-09-08 10:05:04 阅读 96

优质博文:IT-BLOG-CN

从 ThreadLocalMap看 ThreadLocal使用不当的内存泄漏问题

【1】基础概念 : 首先我们先看看<code>ThreadLocalMap的类图,我们知道 ThreadLocal只是一个工具类,他为用户提供getsetremove接口操作实际存放本地变量的threadLocals(调用线程的成员变量),也知道 threadLocals是一个ThreadLocalMap类型的变量,下面我们来看看ThreadLocalMap这个类。在此之前,我们回忆一下Java中的四种引用类型链接

【2】分析<code>ThreadLocalMap内部实现: 我们知道ThreadLocalMap内部实际上是一个Entry数组private Entry[] table,我们先看看Entry的这个内部类

/**

* 是继承自WeakReference的一个类,该类中实际存放的key是指向ThreadLocal的弱引用和与之对应的value值(该value值

* 就是通过ThreadLocal的set方法传递过来的值)由于是弱引用,当get方法返回null的时候意味着回收引用

*/

static class Entry extends WeakReference<ThreadLocal<?>> {

/** value就是和ThreadLocal绑定的 */

Object value;

//k:ThreadLocal的引用,被传递给WeakReference的构造方法

Entry(ThreadLocal<?> k, Object v) {

super(k);

value = v;

}

}

//WeakReference构造方法(public class WeakReference<T> extends Reference<T> )

public WeakReference(T referent) {

super(referent); //referent:ThreadLocal的引用

}

//Reference构造方法

Reference(T referent) {

this(referent, null);//referent:ThreadLocal的引用

}

Reference(T referent, ReferenceQueue<? super T> queue) {

this.referent = referent;

this.queue = (queue == null) ? ReferenceQueue.NULL : queue;

}

在这里插入图片描述

在上面的代码中,我们可以看出,当前<code>ThreadLocal的引用k被传递给WeakReference的构造函数,所以ThreadLocalMap中的keyThreadLocal的弱引用。当一个线程调用ThreadLocalset方法设置变量的时候,当前线程的 ThreadLocalMap就会存放一个记录,这个记录的key值为ThreadLocal的弱引用,value就是通过set设置的值。如果当前线程一直存在且没有调用该ThreadLocalremove方法,如果这个时候别的地方还有对ThreadLocal的引用,那么当前线程中的ThreadLocalMap中会存在对ThreadLocal变量的引用和 value对象的引用,是不会释放的,就会造成内存泄漏。

考虑这个ThreadLocal变量没有其他强依赖,如果当前线程还存在,由于线程的ThreadLocalMap里面的key是弱引用,所以当前线程的ThreadLocalMap里面的ThreadLocal变量的弱引用在gc的时候就被回收,但是对应的value还是存在的这就可能造成内存泄漏(因为这个时候 ThreadLocalMap会存在keynull但是value不为nullentry )。

ThreadLocalMap中的Entrykey使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLocal依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的value不会被回收,这个时候Map中就可能存在keynull但是value不为null的项,这需要实际使用的时候使用完毕及时调用 remove方法避免内存泄漏。

如果使用线程池,由于线程可能并不是真正的关闭(比如newFixedThreadPool会保持线程一只存活)。因此,如果将一些大对象存放到ThreadLocalMap中,可能会造成内存泄漏。因为线程没有关闭,无法回收,但是这些对象不会再被使用了。如果希望及时回收对象,则可以使用Thread.remove()方法将变量移除。

ThreadLocal<Object> 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都设置为null

for (Entry e = tab[i];

e != null;

e = tab[i = nextIndex(i, len)]) {

if (e.get() == key) {

e.clear();

// 清理value值

expungeStaleEntry(i);

return;

}

}

}

使用try-with-resourcestry-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变量,并在子线程完成后自动清理。

ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();

threadLocal.set("Hello, Parent Thread");

Runnable childTask = () -> {

String value = threadLocal.get(); // 子线程可以访问父线程的 ThreadLocal 变量

// ...

};

Thread childThread = new Thread(childTask);

childThread.start();



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。