内存泄露 threadlocal的内存泄露 ThreadLocal为何使用弱引用

Threadlocal的弱引用类型

根据官网给出的定义:This class provides thread-local variables,换句话说,threadLocal存储当前thread的局部变量,该局部变量是线程私有的,Thread内部维护的属性代码如下:

/*
 * InheritableThreadLocal values pertaining to this thread. This map is
 * maintained by the InheritableThreadLocal class.
 */
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

ThreadLocal维护的静态内部类ThreacLocalMap,而ThreacLocalMap维护的是静态内部类Entry类,Entry继承WeakReference<ThreadLocal<?>> ,如代码所示:

/**
  * ThreadLocalMap is a customized hash map suitable only for
  * maintaining thread local values. No operations are exported
  * outside of the ThreadLocal class. The class is package private to
  * allow declaration of fields in class Thread.  To help deal with
  * very large and long-lived usages, the hash table entries use
  * WeakReferences for keys. However, since reference queues are not
  * used, stale entries are guaranteed to be removed only when
  * the table starts running out of space.
  */
 static class ThreadLocalMap {

     /**
      * The entries in this hash map extend WeakReference, using
      * its main ref field as the key (which is always a
      * ThreadLocal object).  Note that null keys (i.e. entry.get()
      * == null) mean that the key is no longer referenced, so the
      * entry can be expunged from table.  Such entries are referred to
      * as "stale entries" in the code that follows.
      */
     static class Entry extends WeakReference<ThreadLocal<?>> {
         /** The value associated with this ThreadLocal. */
         Object value;

         Entry(ThreadLocal<?> k, Object v) {
             super(k);
             value = v;
         }
     }
......
}

因为entry是弱引用,这里容易出现内存泄露。

了解内存泄露之前,我们需要了解Java引用类型 - 弱引用。

弱引用

什么是弱引用

如果发生gc操作时,弱引用对象就会被回收,不管引用对象是否置为null。

假设引用对象不为null

引用对象不为null:

 @Test
public void testReference() {
     CbcCompany company = new CbcCompany();
     company.setName("test company");
     WeakReference<CbcCompany> weakReference = new WeakReference(company);
     int i = 0;
     boolean flag = true;
     while (flag) {
         i++;
         if (weakReference.get() != null) {
             System.out.println("cbcCompany is alive for " + i + " loops - " + weakReference.get());
         } else {
             System.out.println("cbcCompany has been collected.");
             flag = false;
         }
     }
 }

输出结果为:

在这里插入图片描述

假设引用对象为null

@Test
public void testReference() {
    CbcCompany company = new CbcCompany();
    company.setName("test company");
    WeakReference<CbcCompany> weakReference = new WeakReference(company);
    company = null;
    int i = 0;
    boolean flag = true;
    System.gc();
    System.out.println("cbcCompany is alive for " + i + " loops - " + weakReference.get());
}

输出结果为:弱用带来的危害

threadLocal的内存泄露的问题

我们定义一个threadLocal对象,通过set方法注入值,此后threadLocal对象置位null,在垃圾回收时被回收。

Entry的key值为null,value值不为空,对象便被ThreadLocalMap引用着。

如果当前线程的生命周期比较长,那么外部的ThreadLocalMap对象也一直存在,因而这就形成一个强引用链:thread -> threadLocalMap -> entry ->null

因此,在使用完毕后,及时调用thread.remove()方法回收对象。

分析ThreadLocal为何使用弱引用

既然弱引用极容易被Gc回收,从而造成内存泄漏,那么,ThreadLocal为什么还要使用弱引用呢?

在线上环境,jvm默认给每个工作线程分配的内存大小为1mb,假如这里使用的是强引用,当线程执行完还没有被Gc回收,当前map也没有被回收,entry所维护的threadLocal的引用对象自然不会被回收,那么,这就会造成内存碎片。本来就不大的内存,加上碎片的产生,容易造成栈异常。除非线程结束,线程被回收了,map也跟着回收。

但是,如果使用线程池的话,当前核心线程永远都不会被回收,除非调用线程池的shutdown方法,线程才会被回收。但使用线程池就是为了以更少的线程来处理更多的任务,所以很少去调用线程池的shutdown方法。

但是,弱引用也会产生内存泄漏,假如threadlocal=null,gc回收threadlocallocal,但map里面的value却没有被回收。而这块value永远不会被访问到了. 所以存在着内存泄露。但是,Java为了最小化减少内存泄露的可能性和影响,在ThreadLocal的get,set的时候都会清除线程Map里所有key为null的value。

解决ThreadLocal的内存泄露

当线程的某个localThread使用完了,马上调用threadlocal的remove方法,那就啥事没有!

其实,只要这个线程对象及时被gc回收,这个内存泄露问题影响不大。但在threadLocal设为null到线程结束,中间这段时间不会被回收的,就发生了我们认为的内存泄露。
  
最要命的是线程对象一直不被回收的情况,就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网全栈开发实战

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值