当前位置:首页 » 《资源分享》 » 正文

ThreadLoad如何防止内存溢出

2 人参与  2024年09月11日 15:21  分类 : 《资源分享》  评论

点击全文阅读


优质博文:IT-BLOG-CN

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

【1】基础概念 : 首先我们先看看ThreadLocalMap的类图,我们知道 ThreadLocal只是一个工具类,他为用户提供getsetremove接口操作实际存放本地变量的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 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;}

在这里插入图片描述

在上面的代码中,我们可以看出,当前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-finallyThreadLocalResource 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();

点击全文阅读


本文链接:http://zhangshiyu.com/post/158308.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1