ThreadLocal内存泄露问题本质分析与代码编写最佳实践

ThreadLocal内存泄露问题本质分析:

接着ThreadLocal继续探究,在上一次https://www.cnblogs.com/webor2006/p/13169438.html已经对于它里面的Entry要用WeakReference意义进行了分析,这一次再对ThreadLocal的底层使用WeakReference的来解决内存泄漏问题进行一个本质的探讨,虽说在上一次已经阐述得也比较清楚了,但是这块的细节还可以再扣一下,细节扣得越细对于你了解ThreadLocal的原理也就越加的深刻明了,不管是在未来的面试还是工作当中这种所谓的“啰嗦”都是有利无害的。

这里先来回顾一下使用ThreadLocal的基本写法:

package com.jvm.reference;

public class MyTest6 {
    private static final ThreadLocal<String> THREADLOCAL = new ThreadLocal<>();

    public static void main(String[] args) {

        THREADLOCAL.set("hello");

        new Thread(() -> {
            THREADLOCAL.set("thread1");
            System.out.println("thread1:" + THREADLOCAL.get());
        }).start();

        new Thread(() -> {
            THREADLOCAL.set("thread2");
            System.out.println("thread2:" + THREADLOCAL.get());
        }).start();

        System.out.println(THREADLOCAL.get());
    }
}

下面用图的方式一步步来阐述ThreadLocal内存泄漏的问题,如之前篇章所了解的,从Thread类开始,因为它里面有个它:

既然是阐述跟内存相关的问题,当然得从内存的结构开始,也就是栈与堆:

此时由于咱们new了一个线程:

所以内存此时的分布为:

而我们又new了一个ThreadLocal对象了:

所以内存分布变化为:

那问一下,此时堆中存在的两个对象是怎么关联起来的呢?

继续往下梳理,对于Thread类中它有一个ThreadLocalMap成员变量,所以此时内存为:

而ThreadLocalMap它里面其实有一个table的成员变量,它是一个Entry数组类型的,如下:

此时图又变为:

其中每一个Entry它是一个虚引用:

那这个弱引用里指向的是哪个对象呢?

也就是此时的k是引用的ThreadLocal对象了,内存图此时为:

那接下来重点就是来看一下关于ThreadLocal内存泄漏的问题,当然ThreadLocal本身是已经解决了内存泄漏点了,只是我们带着学术的角度来理解JDK它为啥要这样设计,这才有助于我们的成长,如上一篇也已经分析过了重点是这个Entry是继承了一个弱引用是解决内存泄漏的根本,那要分析这个泄漏点,那反过来分析一下,假如此时Entry是强引用ThreadLocal此时的情况又会怎么样,如下:

假如咱们ThreadLocal需要加收,肯定栈中的引用需要断掉,如下:

那照理堆中的它得被垃圾收集器给回收:

但是!!!此时从图中可以看到它有被其它对象强引用的,也就是ThreadLocalMap当中的Entry数组中的key所强引用着的,那造成的影响就是这个ThreadLocal对象永远也得不到回收,它得不到回上,那么这个Entry数组的元素永远会持续的只增不减,而糟糕的是此时由于栈中已经没有引用指向该ThreadLocal对象了,那么这处毫无意义的对象会永远在堆中占据着空间得不到释放,那不就是典型内存泄漏发生了么?要想解决这种问题,必须采用弱引用了:

所以接下来再从这种弱引用的角度来看一下,此时的内存泄漏是否还会存在?还是栈中的ThreadLocal引用会断掉:

而此时堆中的ThreadLocal对象只被一个Entry为key的弱引用所指向了,而根据弱引用回收的特点:

此时它就会被垃圾回收器所回收掉,也就是:

是不是一切都完美无内存泄漏了呢?这点可能是比较细的一个点,但是又是考虑你对ThreadLocal理解是否透的一个关键点,就是此时有Key它原来指向的ThreadLocal如今就变为:

新的问题又出现了!!!既然key为null了,那。。

那。。将Value也定义成WeakReference不就可以解决这个新的内存泄漏的情况了呢?显示这种是不行的,因为弱引用是随时有可能被回收的,那不我们有时获取的值不就不确定了么?那对于这个Value的内存泄漏Java的设计者已经帮我们考虑到了,也就是如上篇所分析过的,在ThreadLocal的set和get方法中都会有一个检查key为null的逻辑,专门就是来处理这种泄漏情况下,下面再翻翻源码看一看:

先看一下set()方法:

而接下来看一下get()方法也有类似的处理逻辑:

还有一个方法remove,也有:

 

代码编写最佳实践:

其实吧这是一个非常小的点,但是这一点是看你对ThreadLocal了解程序的一个体现细节,就是我们当发现ThreadLocal如果不用的话,最好要调用这么一个方法,啥方法呢?如下:

调用它意味着啥呢?其实意味着:

看一下它的源码:

 

这样代码的写法在实际中是比较容易忽略的,但是如果在实际中能考虑到这点,而且能说明为啥要这么用,那证明对于ThreadLocal了解也比较透了。

posted on 2020-08-03 22:43  cexo  阅读(461)  评论(0编辑  收藏  举报

导航