ThreadLocal底层原理以及内存泄漏

文章目录

    • 基本原理
    • Set过程
    • get过程
    • 为什么要使用WeakReference

基本原理

每一个线程Thread中都有一个变量ThreadLocalMap。

ThreadLocal底层原理以及内存泄漏_第1张图片

ThreadLocal的全部操作都是基于Thread. ThreadLocalMap进行操作的。

ThreadLocalMap是一个Entry数组,Entry储存Key(WeakReference>)和Value(Object)。

ThreadLocal底层原理以及内存泄漏_第2张图片

Set过程

ThreadLocal底层原理以及内存泄漏_第3张图片

  1. 根据当前线程Thread获取其内部的ThreadLocalMap对象。

  2. 如果是第一次设置值,ThreadLocalMap对象是空值,所以会进行初始化操作,即调用createMap(t,value)方法

  3. 如果不是第一次,就将ThreadLocal对象包装为弱引用对象作为Key,所设的值为value,构造Entry对象,添加到Entry数组中去。
    ThreadLocal底层原理以及内存泄漏_第4张图片

  4. set方法中获取key的对应的索引值,并从此位置进行遍历,nextIndex每次将i+1。当有相同的key时,则替换旧值。如果遍历之后无相同key,则创建新值。所以

ThreadLocal处理hash碰撞的方法是使用环形数组,进行线性探测法,依次检测下一个位置。

get过程

ThreadLocal底层原理以及内存泄漏_第5张图片
ThreadLocal底层原理以及内存泄漏_第6张图片
ThreadLocal底层原理以及内存泄漏_第7张图片

  1. 根据当前线程,首先获取ThreadLocalMap对象

  2. 由于ThreadLocalMap使用的当前的ThreadLocal作为key,所以传入的参数为this,然后调用getEntry()方法,通过这个key构造索引,根据索引去table(Entry数组)中去查找线程本地变量,找到Entry对象,然后判断Entry对象e不为空并且e的引用与传入的key一样则直接返回。

  3. 如果找不到则调用getEntryAfterMiss()方法。调用getEntryAfterMiss表示直接散列到的位置没找到,那么顺着hash表递增(循环)地往下找,从i开始,一直往下找,直到找到目标对象或者null。

为什么要使用WeakReference>

具有弱引用的对象拥有短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

如果不使用弱引用,因为Entry对象包含ThreadLocal对象作为key,所以Entry对象会包含ThreadLocal对象的强引用所以即使在外层将ThreadLocal=null,也无法回收ThreadLocal对象。使用弱引用,当外层ThreadLocal=null时,Entry对象只包含ThreadLocal对象的虚引用,可以直接被GC回收。 当把ThreadLocal对象的引用置为null后,没有任何强引用指向内存中的ThreadLocal实例,threadLocals的key是它的弱引用,故它将会被GC回收。

但线程中threadLocals里的value却没有被回收,因为存在着一条从Entry对象连接过来的强引用,且因为无法再通过ThreadLocal对象的get方法获取到这个value,它永远不会被访问到了,所以还存在内存泄漏问题。
ThreadLocal底层原理以及内存泄漏_第8张图片

内存泄漏问题

ThreadLocal底层原理以及内存泄漏_第9张图片

每个thread中都存在一个map,map的类型的ThreadLocal,ThreadLocalMap,Map中的key为一个threadlocal的实例,这个map的确使用了弱引用,不过弱引用只是针对key,每个key都弱引用指向threadlocal。当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal会被gc回收。但是,我们的value却不能被回收,因为存在一条从current thread连接过来的强引用(Entry数组中的Entry对象强引用value对象)。只有当前thread结束以后,current thread就不会存在栈中,强引用断开。Current Thread,Map,value将全部被gc回收。

只要这个线程对象被gc回收,就不会出现内存泄漏,但是在ThreadLocal设为null和线程结束这段时间不会被回收的。如果线程暂时不回收,这就发生了真正意义上的内存泄漏。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄漏。

Java为了最小化减少内存泄漏的可能性和影响,在ThreadLocal的get,set的时候都会清除线程Map里所有key为null的value。所以最坏的情况就是threadlocal对象设null了,开始发生内存泄漏,然后使用线程池。这个线程结束,线程放回线程池中不销毁,这个线程一直不被使用,或者分配使用了又不再调用get,set方法,那么这个期间就会发生真正的内存泄漏。

你可能感兴趣的:(java)