ThreadLocal导致JVM内存泄漏原因探究

目录
  • 为什么要使用ThreadLocal
  • 使用ThreadLocal
  • 具体实现
  • 引发内存泄漏的原因

为什么要使用ThreadLocal

在一整个业务逻辑流程中,为了在不同的地方或者不同的方法中使用同一个对象,但是又不想在方法形参中加这个对象,那么就可以使用ThreadLocal来保存

ThreadLocal最大的应用场景就是跨方法进行参数传递

ThreadLocal可以给每一个线程绑定一个变量的副本

使用ThreadLocal

ThreadLocal常用的方法其实也就下面几个

// 返回当前线程所对应的线程局部变量。
public T get() {}
// 设置当前线程的线程局部变量的值。
public void set(T value) {}
// 移除,当线程结束后,该线程thread对象中的局部变量将在下一次gc时回收,如果显示的调用此方法只是可以加快内存回收的速度
// 所以javase开发 普通new Thread()方式中,这个方法并不是必须要调用的
// 但是javaWeb开发中就必须显示调用,因为javaweb都是使用的线程池,并不是一个客户端来一个请求,thread线程对象用完就删除,而是会放回线程池中。
public void remove() {}
// 返回该线程局部变量的一个初始化
// protected方法,显然是为了让子类覆盖而设计的。这个方法在第一次调用 get()或 set(Object)时才执行,并且仅执行 1 次
protected T initialValue() {}

在具体使用的时候,我们ThreadLocal对象一定会定义成静态的,如果不定义成静态的那么其他地方如何通过这个ThreadLocal实例去Map中拿数据嘞?

而且如果是多个线程保存一个变量的副本,一个静态的ThreadLocal也足够了,因为它是作为多个map中的key存在的

简单使用案例

/**
 * @Description: 在一个方法中调用set()方法存值,在另一个方法中调用get()方法取值
 */
public class UseThreadLocalTest {
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    /**
     * 创建一个线程类
     */
    public static class ThreadTest extends Thread{
        private Integer id;
        ThreadTest(Integer id){
            this.id = id;
        }
        @Override
        public void run() {
            threadLocal.set(Thread.currentThread().getName() + ":" + id);
            print();
        }
        public void print(){
            System.out.println(threadLocal.get());
        }
    }
    /**
     * 开三个线程
     */
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new ThreadTest(i).start();
        }
    }
}

// 输入结果如下
Thread-0:0
Thread-1:1
Thread-2:2

具体实现

ThreadLocal底层set()和get()方法的源码如下

// 存值时 map最终是存储在当前线程Thread t = Thread.currentThread()中的,是thread的一个成员变量
// map的key是当前threadLocal对象实例,value是要存的值
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
// 取值时也是也是先从当前线程Thread对象中取出map
// 然后在从map中根据当前threadLocal对象实例作为key获取到entry对象
public T get() {
    Thread t = Thread.currentThread();
    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();
}

为了提高性能,才没有采用加锁的方式,而是将map和各个线程thread对象进行关联,这样就避免了产生线程安全问题,也避免了加锁,提高了性能

我们接下来再来看看ThreadLocalMap它的实现,它类似于jdk1.7版本的hashmap,底层存储的是一个Entry对象的数组,初始容量也是16,存值时先用hash结果和数组长度取余得到数组下标位置,然后判断是否产生了hash冲突,然后使用开发定址法来处理。根据算法的不同又可以分为线性探测再散列、二次探测再散列、伪随机探测再散列。ThreadLocalMap它是使用的线性探测再散列法,如下所示

private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

Entry对象中的key它是一个弱引用,Entry继承了WeakReference类,弱引用跟没引用差不多,GC会直接回收掉,不管内存是否足够都会回收

static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

引发内存泄漏的原因

上面再介绍ThreadLocal基本使用api方法的时候也提到了,如果只是创建一个普通的线程Thread对象,是不会产生内存泄漏问题的。因为map是存储在Thread对象中,一个普通线程执行完了,那么这个线程的局部变量也就会被gc回收。

但如果结合到了线程池,一个Thread线程对象用完后放回线程池中,如果这个时候我们程序不显示的调用remove()方法,那么就会造成内存泄漏问题了。

因为Entry对象中的Key的弱引用,但是value还会存在,就会存在map中key为null的value

ThreadLocal 的底层实现中我们可以看见,无论是 get()set()在某些时 候,调用了 expungeStaleEntry() 方法用来清除 Entry 中 Key 为 null 的 Value,但是这是不及时的,也不是每次都会执行的,所以一些情况下还是会发生内存泄露。

到此这篇关于ThreadLocal导致JVM内存泄漏原因探究的文章就介绍到这了,更多相关JVM内存泄漏内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • ThreadLocal原理及内存泄漏原因

    ThreadLocal有两个问题: 1. 每个变量副本是存储在哪了? 2. 变量副本是怎样从共享的变量中赋值出来的?源码中threadlocal的初始值是什么时候设置的. ThreadLocal为每个线程维护一个变量的副本? 每个线程的ThreadLocalMap都是线程自身持有的,但是初始化是在ThreadLocal中,然后每个线程相当于保存了一个map 这个map存的key是LocalThread的实例,value是存储的线程的局部变量 get方法 根据当前线程获取Thread中的值 set

  • 对ThreadLocal内存泄漏及弱引用的理解

    ThreadLocal内存泄漏及弱引用 1.什么是内存泄漏?Entry的key弱引用与泄漏关系 在TreadLocal中内存泄漏是指TreadLocalMap中的Entry中的key为null,而value不为null.因为key为null导致value一直访问不到,而根据可达性分析,始终有threadRef->currentThread->threadLocalMap->entry->valueRef->valueMemory,导致在垃圾回收的时候进行可达性分析的时候,va

  • ThreadLocal内存泄漏常见要点解析

    前段时间在网上看到了一篇关于ThreadLocal内存泄漏的文章 于是个人也研究了下ThreadLocal 对象,其原理是: ThreadLocal 定义的变量值 会存储在当前线程的一个map集合中 这个map里面存储的是Entity对象 , Entity对象的key是当前ThreadLocal对象的弱引用, value则是ThreadLocal变量的值 这就产生了一个问题: 如果ThreadLocal变量的强引用丢失了 map里面的弱引会失效 gc就回收ThreadLocal对象 那么 Thr

  • ThreadLocal内存泄漏问题解决方案

    如果说 ThreadLocal 的话,那肯定就会涉及到内存泄漏,为啥嘞 因为 吧啦吧啦 ~ ThreadLocal 解决了什么问题呢? 它是为了解决对象不能被多线程共享访问的问题,通过 threadLocal.set() 方法将对象实例保存在每个线程自己所拥有的 threadLocalMap 中,这样的话每个线程都使用自己的对象实例,彼此不会影响从而达到了隔离的作用,这样就解决了对象在被共享访问时带来的线程安全问题. 啥意思呢?打个比方,现在公司所有人都要填写一个表格,但是只有一支笔,这个时候就

  • ThreadLocal导致JVM内存泄漏原因探究

    目录 为什么要使用ThreadLocal 使用ThreadLocal 具体实现 引发内存泄漏的原因 为什么要使用ThreadLocal 在一整个业务逻辑流程中,为了在不同的地方或者不同的方法中使用同一个对象,但是又不想在方法形参中加这个对象,那么就可以使用ThreadLocal来保存 ThreadLocal最大的应用场景就是跨方法进行参数传递 ThreadLocal可以给每一个线程绑定一个变量的副本 使用ThreadLocal ThreadLocal常用的方法其实也就下面几个 // 返回当前线程

  • java OOM内存泄漏原因及解决方法

    前言 这篇文章主要介绍了java OOM内存泄漏原因及解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.什么是OOM OOM,全称"Out Of Memory",翻译成中文就是"内存用完了",当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error 二.为什么会OOM.出现的原因是什么 为什么会没有内存了呢?原因不外乎有两点: ① 分配的少了:比如虚拟机本身可

  • 浅谈JVM内存溢出原因和解决思路

    目录 栈溢出(虚拟机栈和本地方法栈) 产生原因 解决思路 堆溢出 产生原因 解决思路 方法区和运行时常量池溢出 产生原因 解决思路 本机直接内存溢出 产生原因 解决思路 栈溢出(虚拟机栈和本地方法栈) 产生原因 在HotSpot中,只能由-Xss参数来设定.因为在HotSpot中不区分虚拟机栈和本地方法栈的. 栈溢出时会出现两种异常:StackOverflowError异常和OutOfMemoryError异常. StackOverflowError异常因为线程请求的栈深度大于虚拟机允许的最大深

  • 解决vue自定义指令导致的内存泄漏问题

    vue的自定义指令是一个比较容易引起内存泄漏的地方,原因就在于指令通常给元素绑定了事件,但是如果忘记了解绑,就会产生内存泄漏的问题. 看下面代码: directives: { scroll: { inserted (el, cb) { // 不是元素节点 || 未设置回调函数 if (el.nodeType !== 1 || !cb) return let direct = 'down' let rollHeight = 0 let getScrollEventTarget = (target)

  • Android内存溢出及内存泄漏原因进行

    内存溢出(Out Of Memory):Android系统中每一个应用程序可以向系统申请一定的内存,当申请的内存不够用的时候,就产生了内存溢出. 内存泄漏:当某个对象不再被使用,即不再有变量引用它时,该对象占用的内存就会被系统回收.当某个对象不再被使用,但是在其他对象中仍然有变量引用它时,该对象占用的内存就无法被系统回收,从而导致了内存泄漏. 当内存泄漏过多时,可用内存空间会减少,应用程序申请的内存不够用,就会导致内存溢出. 内存溢出原因: 1.内存泄漏过多. 2.内存中加载的数据量超过内存的可

  • Android Handler内存泄漏原因及解决方案

    目录: 1.须知: 主线程Looper生命周期和Activity的生命周期一致. 非静态内部类,或者匿名内部类.默认持有外部类引用. 2.原因: Handler造成内存泄露的原因.非静态内部类,或者匿名内部类.使得Handler默认持有外部类的引用.在Activity销毁时,由于Handler可能有未执行完/正在执行的Message.导致Handler持有Activity的引用.进而导致GC无法回收Activity. 3.可能造成内存泄漏 匿名内部类: //匿名内部类 Handler handl

  • Android内存溢出及内存泄漏原因进解析

    内存溢出(Out Of Memory):Android系统中每一个应用程序可以向系统申请一定的内存,当申请的内存不够用的时候,就产生了内存溢出. 内存泄漏:当某个对象不再被使用,即不再有变量引用它时,该对象占用的内存就会被系统回收.当某个对象不再被使用,但是在其他对象中仍然有变量引用它时,该对象占用的内存就无法被系统回收,从而导致了内存泄漏. 当内存泄漏过多时,可用内存空间会减少,应用程序申请的内存不够用,就会导致内存溢出. 内存溢出原因: 1.内存泄漏过多. 2.内存中加载的数据量超过内存的可

  • GoLang内存泄漏原因排查详解

    目录 背景 临时性内存泄漏 通道理解 背景 Go 语言中有对应的Go 内存回收机制,在Go采用 并发三色标记清除  算法, 但是由于实际的过程中 发现会有一些内存泄漏的常见,内存泄漏 分为: 临时性 和 永久性内存泄漏. 初步排查过程中: 发现Linux使用top 发现内存随着时间会持续的增加没有稳定在一个合理值中. 在使用 pprof ,BBC 等 Go的内存泄漏工具进行排查 临时性内存泄漏 指的释放内存 不及时,对应的内存在更晚时候释放,这类问题主要是 string,slice 和底层的Bu

  • javascript removeChild 导致的内存泄漏

    为得求证,自己写了一个页面来验证怎样内存泄漏.代码如下 复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head

随机推荐