Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >聊聊引用和 ThreadLocal 那些事儿

聊聊引用和 ThreadLocal 那些事儿

作者头像
kirito-moe
发布于 2019-04-30 10:23:46
发布于 2019-04-30 10:23:46
74500
代码可运行
举报
运行总次数:0
代码可运行

来源:pixiv

1 背景

某一天,技术交流群里面的某个群友突然提出了一个问题:"ThreadLocal 的 key 是弱引用,那么在 threadLocal.get() 的时候,发生 GC 之后,key 是否是 null?"屏幕前的你可以好好想想这个问题,在这里我先卖个关子,先讲讲 Java 中引用和 ThreadLocal 的那些事。

2 Java 中的引用

对于很多 Java 初学者来说,会把引用和对象给搞混淆。下面有一段代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
User zhangsan = new User("zhangsan", 24);

这里先提个问题zhangsan到底是引用还是对象呢?很多人会认为zhangsan是个对象,如果你也是这样认为的话那么再看一下下面一段代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
User zhangsan;zhangsan = new User("zhangsan", 24);

这段代码和开始的代码其实执行效果是一致的,这段代码的第一行 User zhangsan,定义了 zhangsan,那你认为 zhangsan 还是对象吗?如果你还认为的话,那么这个对象应该是什么呢?的确,zhangsan 其实只是一个引用,对 JVM 内存划分熟悉的同学应该熟悉下面的图片:

其实 zhangsan 是栈中分配的一个引用,而 new User("zhangsan", 24) 是在堆中分配的一个对象。而 = 的作用是用来将引用指向堆中的对象的。就像你叫张三但张三是个名字而已并不是一个实际的人,他只是指向的你。

我们一般所说的引用其实都是代指的强引用,在 JDK1.2 之后引用不止这一种,一般来说分为四种:强引用、软引用、弱引用、虚引用。而接下来我会一一介绍这四种引用。

2.1 强引用

上面我们说过了 Userzhangsan=newUser("zhangsan",24); 这种就是强引用,有点类似 C 的指针。对强引用他的特点有下面几个:

  • 强引用可以直接访问目标对象。
  • 只要这个对象被强引用所关联,那么垃圾回收器都不会回收,那怕是抛出 OOM 异常。
  • 容易导致内存泄漏。

2.2 软引用

在Java中使用SoftReference帮助我们定义软引用。其构造方法有两个:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public SoftReference(T referent);public SoftReference(T referent, ReferenceQueue<? super T> q);

两个构造方法相似,第二个比第一个多了一个引用队列,在构造方法中的第一个参数就是我们的实际被指向的对象,这里用新建一个 SoftReference 来替代我们上面强引用的等号。 下面是构造软引用的例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 softZhangsan = new SoftReference(new User("zhangsan", 24));
2.2.1 软引用有什么用?

如果某个对象他只被软引用所指向,那么他将会在内存要溢出的时候被回收,也就是当我们要出现 OOM 的时候,如果回收了一波内存还不够,这才抛出 OOM,弱引用回收的时候如果设置了引用队列,那么这个软引用还会进一次引用队列,但是引用所指向的对象已经被回收。这里要和下面的弱引用区分开来,弱引用是只要有垃圾回收,那么他所指向的对象就会被回收。下面是一个代码例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {    ReferenceQueue<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 来定义一个弱引用,上面我们说过他比软引用更加弱,只要发生垃圾回收,若这个对象只被弱引用指向,那么就会被回收。这里我们就不多废话了,直接上例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 很好的体现了这个例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 进行定义。弱到什么地步呢?也就是你定义了虚引用根本无法通过虚引用获取到这个对象,更别谈影响这个对象的生命周期了。在虚引用中唯一的作用就是用队列接收对象即将死亡的通知。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 被回收时,为防止内存泄漏,通过这种方式进行回收,有点类似于下面的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 类中有下面这个变量:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ThreadLocal.ThreadLocalMap threadLocals;

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 理解不深导致,很多时候只记着一个如果是弱引用,在垃圾回收时就会被回收,就会导致把这个观念先入为主,没有做更多的分析思考。所以大家再分析一个问题的时候还是需要更多的站在不同的场景上做更多的思考。

最后这篇文章被我收录于 JGrowing-Java基 础篇,一个全面,优秀,由社区一起共建的Java学习路线,如果您想参与开源项目的维护,可以一起共建,github地址为:https://github.com/javagrowing/JGrowing ,点击下方原文链接,麻烦给个小星星哟。

转载声明:本文转载自「咖啡拿铁」

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-03-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Kirito的技术分享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
基础篇:JAVA引用类型和ThreadLocal
平时并发编程,除了维护修改共享变量的场景,有时我们也需要为每一个线程设置一个私有的变量,进行线程隔离,java提供的ThreadLocal可以帮助我们实现,而讲到ThreadLocal则不得不讲讲java的四种引用,不同的引用类型在GC时表现是不一样的,引用类型Reference有助于我们了解如何快速回收某些对象的内存或对实例的GC控制
潜行前行
2021/06/25
4100
ThreadLocal之强、弱、软、虚引用
ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候(通过其get或set方法)都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户ID或事务ID)与线程关联起来。
鱼找水需要时间
2023/02/16
4190
ThreadLocal之强、弱、软、虚引用
强软弱虚引用,只有体会过了,才能记住
Java的内存分配和内存回收,都不需要程序员负责,都是由伟大的JVM去负责,一个对象是否可以被回收,主要看是否有引用指向此对象,说的专业点,叫可达性分析。
田维常
2020/04/26
8050
聊聊Java的引用类型(强引用、软引用、弱引用、虚引用),示例WeakHashMap的使用【享学Java】
Java语言中的数据类型可划分为值类型和引用类型。从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。 这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
YourBatman
2019/09/03
1.3K0
聊聊Java的引用类型(强引用、软引用、弱引用、虚引用),示例WeakHashMap的使用【享学Java】
你知道Java的四种引用类型吗?
在Java中提供了四个级别的引用:强引用,软引用,弱引用和虚引用。在这四个引用类型中,只有强引用FinalReference类是包内可见,其他三种引用类型均为public,可以在应用程序中直接使用。引用类型的类结构如图所示。
Java技术江湖
2019/09/24
9090
你知道Java的四种引用类型吗?
搜狐面试官:说说你理解的JVM中四种引用类型!
强引用是默认支持,当内存不足的时候,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会回收对象。
Java程序猿
2021/06/27
3070
Java4种引用类型到底如何用?
finalize函数是对象在gc的时候,一定会调用该方法。我们重写一下该方法并且打印一行日志。
chengcheng222e
2021/11/04
3700
深入浅出JVM(十四)之内存溢出、泄漏与引用
这里推荐一篇从Java线程池相关的文章:复现线程池引发的生产环境BUG 文章通过讲故事的方式描述线程池使用不当存在的问题,并举例增大线程数量、调整拒绝策略等多种方式让线程池能够承受住更大的并发
菜菜的后端私房菜
2024/11/28
2030
阿里面试: 说说强引用、软引用、弱引用、虚引用吧
我们都知道 JVM 垃圾回收中,GC判断堆中的对象实例或数据是不是垃圾的方法有引用计数法和可达性算法两种。
乔戈里
2020/05/25
3.7K0
阿里面试: 说说强引用、软引用、弱引用、虚引用吧
Java中的四种引用类型
从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用,下面分别介绍下这四种引用。
布禾
2021/04/09
4050
Java基础 之软引用、弱引用、虚引用
在JDK1.2以前的版本中,当一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及状态,程序才能使用它。这 就像在日常生活中,从商店购买了某样物品后,如果有用,就一直保留它,否则就把它扔到垃圾箱,由清洁工人收走。一般说来,如果物品已经被扔到垃圾箱,想再 把它捡回来使用就不可能了。
IT工作者
2022/05/06
9800
大数据基础系列之JAVA引用详解
一,四种引用介绍 从Java SE2开始,就提供了四种类型的引用:强引用、软引用、弱引用和虚引用。Java中提供这四种引用类型主要有两个目的:第一是可以让程序员通过代码的方式决定某些对象的生命周期;第二是有利于JVM进行垃圾回收。 1,强引用 强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 test对象未消亡之前,object和str
Spark学习技巧
2018/01/31
5850
threadlocal底层实现_ioc的底层实现原理
作用: 提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂性。
全栈程序员站长
2022/09/21
6730
threadlocal底层实现_ioc的底层实现原理
强引用,软引用,弱引用,幻象引用有什么区别?
不同的引用类型,主要体现的是对象的不同的可达性(reachable)状态和对垃圾收集的影响。
王小明_HIT
2020/05/25
4.4K0
深入AQS源码阅读与强软弱虚4种引用以及ThreadLocal原理与源码
今天咱们继续讲AQS的源码,在上节课我教大家怎么阅读AQS源码,跑不起来的不读、解决问题就好 —目的性、一条线索到底、无关细节略过,读源码的时候应该先读骨架,比如拿AQS来说,你需要了解AQS是这么一个数据 结构,你读源码的时候读起来就会好很多,在这里需要插一句,从第一章到本章,章章的内容都是环环相扣的,没学习前边,建议先去补习一下前面的章节。
愿天堂没有BUG
2022/10/28
2540
深入AQS源码阅读与强软弱虚4种引用以及ThreadLocal原理与源码
Java中的四种Reference
首先要大致了解 Java 的几种引用类型。如下图所示,JDK 1.2 之后新增了 Reference 的概念,给开发人员提供了与 GC 交互的一种渠道。
GreatSQL社区
2023/02/23
3250
一文带你读懂Java的强引用、软引用、弱引用、虚引用
开讲前,我们先回顾下JVM的基本结构。根据《Java虚拟机规范(Java SE 7版)》。Java虚拟机所管理的内存将会包括以下几个运行时数据区域:
后台技术汇
2022/05/28
4820
一文带你读懂Java的强引用、软引用、弱引用、虚引用
Java引用类型
使用Java开发,我们不需要去管理对象的生命周期,因为JVM会帮我们回收垃圾,不过这就是安全的吗,显然不是,因为JVM 的堆区存在了很多未回收的对象实例,那么就有可能发生内存溢出,所以我们就有必要在对强引用,弱引用,软引用,虚引用 有所了解。
小四的技术之旅
2022/07/26
6780
Java引用类型
Java 中的四种引用
之前我们提到过 GC,但当 Java 中引用的对象越来越多,会导致内存空间不足,最终会产生错误 OutOfMemoryError,并让应用程序终止。那为什么 GC 在此时不能多收集一些对象呢?这就和今天说的引用类型有关了。
健程之道
2019/11/02
5590
Android中的引用类型:Weak Reference, Soft Reference, Phantom Reference 和 WeakHashMap
在Android开发中,内存管理是一个非常重要的话题。为了更好地管理内存,Java和Android提供了多种引用类型,包括Weak Reference、Soft Reference、Phantom Reference以及WeakHashMap。这些引用类型在不同的场景下可以帮助我们更有效地管理内存,避免内存泄漏。
井九
2024/10/12
1390
相关推荐
基础篇:JAVA引用类型和ThreadLocal
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验