为什么threadlocal要使用弱引用(threadlocal的原理和使用场景)

技术引用和Threadlocal的示例分析引用和Threadlocal的示例分析,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。1 背景某一天在某一个群里面的

引用Threadlocal的例子分析,相信很多没有经验的人对此无能为力。因此,本文总结了问题的原因和解决方法,希望您能通过本文解决这个问题。

00-1010某天,某群的群友突然提出了一个问题:‘thread local的键是弱引用,那么当threadlocal.get()发生时,GC后的键是否为null?’在屏幕前,你可以仔细思考这个问题。在这里,我将告诉你一些关于Java中引用和线程本地的事情。

1 背景

对于很多Java初学者来说,引用和对象会混淆。这是一个代码,

userzhansan=new user(' zhansan ',24);先问个问题。张三是参照物还是客体?很多人会认为张三是一个对象。如果您这样认为,那么请看下面的代码。

用户张山;

张三=新用户('张三',24);其实这段代码的执行效果和初始代码是一样的。这段代码的第一行“用户张山”定义了张山。你觉得张山还是对象吗?如果你还这么想,这个对象应该是什么?的确,张三只是一个参考。熟悉JVM内存分区的同学,应该对下图:比较熟悉。

引用和Threadlocal的示例分析

实际上,张三是堆栈中分配的引用,而新用户('张三',24)是堆栈中分配的对象。“=”用于指向堆中对象的引用。就像你叫张三,但张三只是一个名字,不是一个实际的人,他只是指向你。

其实我们一般提到的参考文献都是强参考文献,供参考。JDK1.2之后,引用的种类不止一种。一般来说,强引用有四种:软引用、弱引用和虚拟引用。接下来,我将逐一介绍这四种引语。

00-1010,就像我们上面说的。

用户张三=新用户('张三',24);这是一个强引用,有点像c的指针,他有以下强引用的特点:

强引用可以直接访问目标对象。

只要这个对象由一个强引用关联,垃圾收集器就不会收集它,即使它抛出OOM异常。

容易造成内存泄漏。

00-1010在Java中使用SoftReference有助于我们定义软引用。有两种施工方法:

publicsofreferent(Treferent);

publicsofreferent(Treferent,ReferenceQueue?superTq);这两种构造方法相似。第二个队列比第一个队列多一个引用队列。构造方法中的第一个参数是我们实际指向的对象。这里,我们创建一个新的软引用,而不是上面强引用的等号。

以下是构建软引用:的示例。

soft Zhang San=new of reference(new user(' Zhang San ',24));00-1010如果一个对象只被软引用指向,那么当内存溢出的时候,也就是我们即将看到OOM的时候,它就会被回收,如果一波回收的内存不够,OOM就会被抛出。如果在弱引用回收过程中设置了引用队列,软引用将再次进入引用队列,但引用指向的对象已被回收。这里,它应该与以下弱引用区分开。弱引用意味着只要有垃圾收集,它所指向的对象就会被收集。下面是代码示例:

publicationstativitmain(String[]args){ 0

参考

enceQueue<User> referenceQueue = new ReferenceQueue();
        SoftReference softReference = new SoftReference(new User("zhangsan",24), referenceQueue);
        //手动触发GC
        System.gc();
        Thread.sleep(1000);
        System.out.println("手动触发GC:" + softReference.get());
        System.out.println("手动触发的队列:" + referenceQueue.poll());
        //通过堆内存不足触发GC
        makeHeapNotEnough();
        System.out.println("通过堆内存不足触发GC:" + softReference.get());
        System.out.println("通过堆内存不足触发GC:" + referenceQueue.poll());
    }

    private static void makeHeapNotEnough() {
        SoftReference softReference = new SoftReference(new byte[1024*1024*5]);
        byte[] bytes = new byte[1024*1024*5];
    }
    输出:
    手动触发GC:User{name='zhangsan', age=24}
    手动触发的队列:null
    通过堆内存不足触发GC:null
    通过堆内存不足触发GC:java.lang.ref.SoftReference@4b85612c

通过-Xmx10m设置我们堆内存大小为10,方便构造堆内存不足的情况。可以看见我们输出的情况我们手动调用System.gc并没有回收我们的软引用所指向的对象,只有在内存不足的情况下才能触发。

2.2.2软应用的应用

在SoftReference的doc中有这么一句话:

Soft references are most often used to implement memory-sensitive caches

也就是说软引用经常用来实现内存敏感的高速缓存。怎么理解这句话呢?我们知道软引用他只会在内存不足的时候才触发,不会像强引用那用容易内存溢出,我们可以用其实现高速缓存,一方面内存不足的时候可以回收,一方面也不会频繁回收。在高速本地缓存Caffeine中实现了软引用的缓存,当需要缓存淘汰的时候,如果是只有软引用指向那么久会被回收。不熟悉Caffeine的同学可以阅读深入理解Caffeine

2.3 弱引用

弱引用在Java中使用WeakReference来定义一个弱引用,上面我们说过他比软引用更加弱,只要发生垃圾回收,若这个对象只被弱引用指向,那么就会被回收。这里我们就不多废话了,直接上例子:

public static void main(String[] args)  {
        WeakReference weakReference = new WeakReference(new User("zhangsan",24));
        System.gc();
        System.out.println("手动触发GC:" + weakReference.get());
    }
输出结果:
手动触发GC:null

可以看见上面的例子只要垃圾回收一触发,该对象就被回收了。

2.3.1 弱引用的作用

在WeakReference的注释中写到:

Weak references are most often used to implement canonicalizing mappings.

从中可以知道弱引用更多的是用来实现canonicalizing mappings(规范化映射)。在JDK中WeakHashMap很好的体现了这个例子:

public static void main(String[] args) throws Exception {
        WeakHashMap<User, String> weakHashMap = new WeakHashMap();
        //强引用
        User zhangsan = new User("zhangsan", 24);
        weakHashMap.put(zhangsan, "zhangsan");
        System.out.println("有强引用的时候:map大小" + weakHashMap.size());
        //去掉强引用
        zhangsan = null;
        System.gc();
        Thread.sleep(1000);
        System.out.println("无强引用的时候:map大小"+weakHashMap.size());
    }
输出结果为:
有强引用的时候:map大小1
无强引用的时候:map大小0

可以看出在GC之后我们在map中的键值对就被回收了,在weakHashMap中其实只有Key是弱引用做关联的,然后通过引用队列再去对我们的map进行回收处理。

2.4 虚引用

虚引用是最弱的引用,在Java中使用PhantomReference进行定义。弱到什么地步呢?也就是你定义了虚引用根本无法通过虚引用获取到这个对象,更别谈影响这个对象的生命周期了。在虚引用中唯一的作用就是用队列接收对象即将死亡的通知。

    public static void main(String[] args) throws Exception {
        ReferenceQueue referenceQueue = new ReferenceQueue();
        PhantomReference phantomReference = new PhantomReference(new User("zhangsan", 24), referenceQueue);
        System.out.println("什么也不做,获取:" + phantomReference.get());
    }
输出结果:
什么也不做,获取:null

在PhantomReference的注释中写到:

Phantom references are most often used for scheduling pre-mortem cleanup actions in a more flexible way than is possible with the Java finalization mechanism.

虚引用得最多的就是在对象死前所做的清理操作,这是一个比Java的finalization梗灵活的机制。
在DirectByteBuffer中使用Cleaner用来回收对外内存,Cleaner是PhantomReference的子类,当DirectByteBuffer被回收的时候未防止内存泄漏所以通过这种方式进行回收,有点类似于下面的代码:

public static void main(String[] args) throws Exception {
        Cleaner.create(new User("zhangsan", 24), () -> {System.out.println("我被回收了,当前线程:{}"+ Thread.currentThread().getName());});
        System.gc();
        Thread.sleep(1000);
    }
输出:
我被回收了,当前线程:Reference Handler

3 ThreadLocal

ThreadLocal是一个本地线程副本变量工具类,基本在我们的代码中随处可见。这里就不过多的介绍他了。

3.1 ThreadLocal和弱引用的那些事

上面说了这么多关于引用的事,这里终于回到了主题了我们的ThreadLocal和弱引用有什么关系呢?

在我们的Thread类中有下面这个变量:

ThreadLocal.ThreadLocalMap threadLocals

ThreadLocalMap本质上也是个Map,其中Key是我们的ThreadLocal这个对象,Value就是我们在ThreadLocal中保存的值。也就是说我们的ThreadLocal保存和取对象都是通过Thread中的ThreadLocalMap来操作的,而key就是本身。在ThreadLocalMap中Entry有如下定义:

 static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

可以看见Entry是WeakReference的子类,而这个弱引用所关联的对象正是我们的ThreadLocal这个对象。我们又回到上面的问题:

"threadlocal的key是弱引用,那么在threadlocal.get()的时候,发生GC之后,key是否是null?"

这个问题晃眼一看,弱引用嘛,还有垃圾回收那肯定是为null,这其实是不对的,因为题目说的是在做threadlocal.get()操作,证明其实还是有强引用存在的。所以key并不为null。如果我们的强引用不存在的话,那么Key就会被回收,也就是会出现我们value没被回收,key被回收,导致value永远存在,出现内存泄漏。这也是ThreadLocal经常会被很多书籍提醒到需要remove()的原因。

你也许会问看到很多源码的ThreadLocal并没有写remove依然再用得很好呢?那其实是因为很多源码经常是作为静态变量存在的生命周期和Class是一样的,而remove需要再那些方法或者对象里面使用ThreadLocal,因为方法栈或者对象的销毁从而强引用丢失,导致内存泄漏。

3.2 FastThreadLocal

FastThreadLocal是Netty中提供的高性能本地线程副本变量工具。在Netty的io.netty.util中提供了很多牛逼的工具,后续会一一给大家介绍,这里就先说下FastThreadLocal。

FastThreadLocal有下面几个特点:

  • 使用数组代替ThreadLocalMap存储数据,从而获取更快的性能。(缓存行和一次定位,不会有hash冲突)

  • 由于使用数组,不会出现Key回收,value没被回收的尴尬局面,所以避免了内存泄漏。

看完上述内容,你们掌握引用和Threadlocal的示例分析的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注行业资讯频道,感谢各位的阅读!

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/150149.html

(0)

相关推荐

  • 三相功率的计算公式,三相电动机功率计算方法

    技术三相功率的计算公式,三相电动机功率计算方法三相电动机功率三相功率的计算公式: P3=3P相=3*U相*I相*COSφ*效率=√3*U 线 *I线*COSφ*η 效率
    =1.732U线*I线*COSφ*效率
    1.三相

    2021年10月25日
  • c语言中常见的常量类型是什么(c语言符号常量定义的关键字)

    技术C语言中几种常量的认识和理解是怎样的本篇文章为大家展示了C语言中几种常量的认识和理解是怎样的 ,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。一、C语言常量是什么?常量是指在程

    攻略 2021年12月13日
  • 怎么删除表和Oracle的回收站

    技术怎么删除表和Oracle的回收站本篇内容介绍了“怎么删除表和Oracle的回收站”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够

    攻略 2021年11月4日
  • 如何深度剖析Python语言特点

    技术如何深度剖析Python语言特点这期内容当中小编将会给大家带来有关如何深度剖析Python语言特点,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。这里并不涉及python的特殊规则和

    攻略 2021年10月28日
  • 送闺蜜结婚礼物排行榜,闺蜜结婚我送什么礼物比较好呢

    技术送闺蜜结婚礼物排行榜,闺蜜结婚我送什么礼物比较好呢从某种意义上而言送闺蜜结婚礼物排行榜,创意的多少取决于你为准备那礼物花了多少心思。 所以了,你可以自制礼物,比如个性电子相册、幸运星等等。当然,可以送买的礼物,但需要

    生活 2021年10月21日
  • react hooks组件间的传值方式是什么

    技术react hooks组件间的传值方式是什么这篇文章给大家介绍react hooks组件间的传值方式是什么,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。父传子通过props传值,使用useSta

    攻略 2021年11月12日