导致JVM内存泄露的ThreadLocal详解

很常见的关于ThreadLocal的面试题的问法:

1.说说你对ThreadLocal的理解。

2.ThreadLocal 是什么?有哪 些使用场景?什么是线程局部变量?

3.ThreadLocal内存泄漏分析与解决方案。

ps:想理解好ThreadLocal,必须先得理解好JVM的内存模型

多个线程共同操作一个共享变量,一定会引发并发问题,那么解决的方法就是对代码进行同步,比如synchronized关键字,但是ThreadLocal换了一种思路:让每个线程都拥有共享变量的副本,这样就不会引发多线程并发问题了。

ThreadLocal 是一个本地线程副本变量工具类,在每个线程中都创建了一个  ThreadLocalMap 对象,简单说 ThreadLocal 就是一种以空间换时间的做法, 每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。通过这种方 式,避免资源在多线程间共享。

使用方法很简单:

T initialValue(),set(T value) ,T get() ,remove() 是比较常用的方法,尤其是set(T value)和get()

使用场景:跨方法的参数传递,例如:数据库连接Conn,平时我们要保证事务,并没有看到service或者dao的方法参数中有conn,其实就是通过ThreadLocal来传递的。还有skywalking中的traceId也是用ThreadLocal来实现的。

线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共 享。Java 提供 ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小 心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任 何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风 险。

ThreadLocal内存泄漏分析

 在ThreadLocal类中有一个静态内部类ThreadLocalMap,这个Map就是用来存储线程局部变量数据的,底层是一个Entry的数组,注意这个Entry的键 是一个弱引用,而且键类型是ThreadLocal的类型

这里又涉及到一道重要的面试题:

Java 中都有哪些引用类型? 强软弱虚

1 强引用:发生 gc 的时候不会被回收。 
2 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
3 弱引用:有用但不是必须的对象,在下一次GC时会被回收。 
4 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,
  用PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。

这个ThreadLocalMap在Thread类中用到了,是Thread的一个成员变量

 

虚拟机栈中的栈帧会不断的出栈,而且当一个任务运行结束后,虚拟机栈会销毁, 那么下图中的引用就没有了

 ThreadLocal对象就剩下一个虚引用引用着了,那么gc之后 ThreadLocal对象就没有了,

 只剩下这么多了,大家都知道线程池中的线程是生命周期很长的,那这部分永远都占用着内存空间,就会导致内存泄漏。

所以线程池和ThreadLocal结合使用的时候一定要注意。

总结:

ThreadLocal造成内存泄漏的原因?

ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所 以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会 被清理掉,而 value 不会被清理掉。这样一来,ThreadLocalMap 中就会出现key 为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个 时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在 调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后 最好手动调用remove()方法。

ThreadLocal内存泄漏解决方案?

1.每次使用完ThreadLocal,都调用它的remove()方法,清除数据。 

2.将ThreadLocal变量尽可能定义成static final 类型的,避免频繁创建ThreadLocal实例。这样可以保证程序中一直存在ThreadLocal的强引用,也能保证任何时候都能通过ThreadLocal的弱引用访问Entry中的Value值。

补充:下面是弱引用的例子

package cn.tulingxueyuan.xiaoshanshan.base.threadlocal;

import java.lang.ref.WeakReference;

public class Yinyong {

    public static void main(String[] args) {

        User user = new User() ;
//        User user1 = user;
        WeakReference<User> user1 = new WeakReference<>(user);
        System.out.println(user);
        user =null ;
        System.gc();
        System.out.println("gc 完成 ...");

//        System.out.println(user1);
        System.out.println(user1.get());
    }

    static class User{

        String name ;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

运行结果:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值