一文讲透java弱引用以及使用场景

概念

大部分情况下我们看到是强引用,比如下面这一行:

String str1 = new String("abc");

变量str1被用来存放一个string对象的强引用上。强引用在你正在使用时这个对象时,一般是不会被垃圾回收器回收的。当出现内存空间不足时,虚拟机不会释放强引用的对象占用的空间,而是选择抛出异常(OOM)。

什么时候会回收强引用的空间呢,就是没有引用的时候,比如你这样写:

str1 = null

GC在适当的时候就会回收str1指向的空间。

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

java中使用弱引用的语法是:

String str1 = new String("abc");
WeakReference<String> weakReference = new WeakReference<>(str1);

深入原理

我们先来通过一个案例,看下gc对于弱引用的回收策略。

public class App {public static WeakReference<String> weakReference1;public static void main(String[] args) {test1();//test1外部,hello对象作用域结束,没有强引用指向"value"了。只有一个弱引用指向"value"System.out.println("未进行gc时,只有弱引用指向value内存区域:" + weakReference1.get());//此时gc时会回收弱引用System.gc();//此时输出都为nuillSystem.out.println("进行gc时,只有弱引用指向value内存区域:" + weakReference1.get());}public static void test1() {//hello对象强引用"value"String hello = new String("value");//weakReference1对象弱引用指向"value"weakReference1 = new WeakReference<>(hello);//在test1内部调用gc,此时gc不会回收弱引用,因为hello对象强引用"value"System.gc();System.out.println("进行gc时,强引用与弱引用同时指向value内存区域:" + weakReference1.get());}}

运行的结果:

进行gc时,强引用与弱引用同时指向value内存区域:value
未进行gc时,只有弱引用指向value内存区域:value
进行gc时,只有弱引用指向value内存区域:null

这里有个前置知识说下,当要获得WeakReference的object时, 首先需要判断它是否已经被GC回收,若被收回,则下列返回值为空:

weakReference1.get();

根据这个结果,我们可以得出这样的结论:

  • 当有强引用指向value内存区域时,即使进行gc,弱引用也不会被释放,对象空间不回被回收。
  • 当无强引用指向value内存区域是,进行gc,弱引用会被释放,对象空间将会执行回收流程。

我们接着从源码层面看下弱引用。首先引入一个概念叫gc的可达性。因为本篇文章并不是专门讲gc的,所以我这里并不打算展开太多这部分,知识一句话概括下gc可达性的概念。

GC决定一个对象是否可被回收,其基本思路是从GC Root开始向下搜索,如果对象与GC Root之间存在引用链,则对象是可达的,GC会根据是否可到达与可到达性决定对象是否可以被回收。

public class WeakReference<T> extends Reference<T> {/*** Creates a new weak reference that refers to the given object.  The new* reference is not registered with any queue.** @param referent object the new weak reference will refer to*/public WeakReference(T referent) {super(referent);}/*** Creates a new weak reference that refers to the given object and is* registered with the given queue.** @param referent object the new weak reference will refer to* @param q the queue with which the reference is to be registered,*          or <tt>null</tt> if registration is not required*/public WeakReference(T referent, ReferenceQueue<? super T> q) {super(referent, q);}}

WeakReference源码很简单,所以大部分逻辑都在父类Reference中。Reference类中有个核心的类是ReferenceQueue,这类的注释是这样写的:

Reference queues, to which registered reference objects are appended by the garbage collector after the appropriate reachability changes are detected.

翻译过来大概意思是,在检测到对象的可达性发生改变后,垃圾回收器就将已注册的引用对象添加到Reference queues队列中。

这个类有三个public方法,enqueue,poll和remove。标准的队列操作,这个很简单不展开。

然后再回到Reference类,它是这样定义的:

/*** Abstract base class for reference objects.  This class defines the* operations common to all reference objects.  Because reference objects are* implemented in close cooperation with the garbage collector, this class may* not be subclassed directly.** @author   Mark Reinhold* @since    1.2*/public abstract class Reference<T> {

首先它是一个抽象类,意味着你不能直接创建此类的实例。注释的意思是,这是引用对象的抽象基类,这个类定义了引用对象的常用操作。引用对象的实现一般都和垃圾回收器密切相关。

这里有个很重要的信息就是这个类和垃圾回收密切相关。

一个reference的实例有四种状态,

Active

这是一个受会受到GC的特别关注的状态,当GC察觉到引用的可达性变化为“合适”的状态之后,reference实例的状态将变化为Pending或Inactive,到底转化为Pending状态还是Inactive状态取决于此Reference对象创建时是否注册了queue.如果注册了queue,则将添加此实例到pending-Reference list中。而新创建的Reference实例的状态是Active。

Pending

在pending-Reference list中等待着被Reference-handler 线程入队列queue中的元素处于pending状态。没有注册queue的实例是永远不可能到达这一状态。

Enqueued

当实例创建的时候加入了队列后的状态。当实例被从ReferenceQueue中移除时,它的状态变为Inactive。没有注册ReferenceQueue的不可能到达这一状态的。

Inactive

终态。一旦一个实例变为Inactive,则这个状态永远都不会再被改变了。

掌握了这四个状态,继续往下看源码,

volatile ReferenceQueue<? super T> queue;

这个queue是通过构造函数传入的,表示创建一个Reference实例时,要将其注册到那个queue上。

然后继续看有个很重要的类,

private static class ReferenceHandler extends Thread {
...static {ThreadGroup tg = Thread.currentThread().getThreadGroup();for (ThreadGroup tgn = tg;tgn != null;tg = tgn, tgn = tg.getParent());Thread handler = new ReferenceHandler(tg, "Reference Handler");/* If there were a special system-only priority greater than* MAX_PRIORITY, it would be used here*/handler.setPriority(Thread.MAX_PRIORITY);handler.setDaemon(true);handler.start();// provide access in SharedSecretsSharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {@Overridepublic boolean tryHandlePendingReference() {return tryHandlePending(false);}});}...

根据代码,我们知道这个线程处理类具有最高的优先级,并且是daemon状态在跑。这个线程的逻辑就是:不断的从Reference构成的pending链表上获取Reference对象,如果pending不为null,则将pending的对象进行clean,如果注册的时候有queue就进行enqueue,否则线程进行wait状态。

基于以上分析,我们可以总结下Reference的机制:

pending是由JVM来赋值的,当Reference内部的referent对象的可达状态发生改变时,JVM会将Reference对象放入到pending链表中。然后启动一个ReferenceHandler线程来处理,处理的逻辑就是调用Cleaner#clean,然后根据注册时候是否有队列决定是否调用ReferenceQueue#enqueue方法进行处理。

应用案例解析

弱引用一般用在什么场合呢?我们可以通过一些常见的组件的源码来分析下。来看看常见的threadlocal的源码关于弱引用的使用。

threadLocal是一个线程本地变量,每个线程维护自己的变量副本,它的实现原理简单来讲就是每个线程Thread类都有个属性ThreadLocalMap,Map是自定义实现的Entry[]数组结构,所以可以维护该线程的多个ThreadLocal变量。

图中的虚线,表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key的。

既然说是Entry,必然有key和value。其中Key即是ThreadLocal变量本身,Value则是具体该线程中的变量真实的副本值。代码如下:

static class ThreadLocalMap {/*** The entries in this hash map extend WeakReference, using* its main ref field as the key (which is always a* ThreadLocal object).  Note that null keys (i.e. entry.get()* == null) mean that the key is no longer referenced, so the* entry can be expunged from table.  Such entries are referred to* as "stale entries" in the code that follows.*/static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}

然后你会注意到,Entry的Key即ThreadLocal对象是采用弱引用引入的。为什么ThreadLocalMap使用弱引用存储ThreadLocal呢?

还是看上面那张图。

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程迟迟不结束的话(因为大部分时候我们用的都是线程池,核心线程都是长期驻留的),这些key为null的Entry的value就会一直存在一条强引用链:

Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value

永远无法回收,造成内存泄漏。这样看,似乎是弱引用导致了内存泄漏?

事实上是,无论这里使用强引用还是弱引用,都有可能造成内存泄漏。如果key 使用强引用:引用的ThreadLocal的对象如果置为null,被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。

反过来,如果key使用了弱引用,当jvm发现内存不足时,会自动回收弱引用指向的实例内存,也就是回收对ThreadLocal对象。但是这个时候value还是存在的。不过没有关系,看源码你会发行在调用get或者set操作的时候,都有机会执行回收无效entry的操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/107703.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

99%的人都不知道的免费在线制作电子画册的网站

你是否曾经想过自己制作一本精美的电子画册&#xff1f; 现在&#xff0c;小编给大家分享一款免费在线制作电子画册的工具&#xff0c;可以帮助你轻松实现。这个网站不仅提供了丰富的模板和素材&#xff0c;还让你在制作过程中可以随时预览和编辑&#xff0c;无需任何排版设计…

详解梯度下降从BGD到ADAM - [北邮鲁鹏]

文章目录 参考文章及视频导言梯度下降的原理、过程一、什么是梯度下降&#xff1f;二、梯度下降的运行过程 批量梯度下降法(BGD)随机梯度下降法(SGD)小批量梯度下降法(MBGD)梯度算法的改进梯度下降算法存在的问题动量法(Momentum)动量法还有什么效果&#xff1f; 自适应梯度(Ad…

RJ45网络信号浪涌保护器解决方案

RJ45网络信号浪涌保护器是一种用于保护网络设备免受雷击或其他高压电流干扰的装置&#xff0c;它可以有效地吸收和释放信号线路上的过电压&#xff0c;从而避免设备损坏或数据丢失。 RJ45信号浪涌保护器的应用领域和施工方案如下&#xff1a; 地凯科技RJ45网络信号浪涌保护器…

PCL 计算字符型ply文件的法向量

文章目录 ply格式计算法向量意义具体代码 ply格式 PLY&#xff08;Polygon File Format&#xff09;是一种用于存储三维模型数据的文件格式。在PLY文件中&#xff0c;法向量是指每个顶点或面片的法向量&#xff0c;用于描述表面的朝向和光照计算。 在PLY文件中&#xff0c;法…

Unity之创建第一个2D游戏项目

一 Unity环境配置 1.1 Untity资源官网下载&#xff1a;https://unity.cn/releases 1.2 Unity Hub集成环境&#xff0c;包含工具和项目的管理 1.3 Unity Editor编辑器 1.4 Visual Studio 2022脚本编辑器 1.5 AndroidSKD&#xff0c;JDK&#xff0c;NDK工具&#xff0c;用于and…

Gartner 2023API管理市场指南重磅发布,得帆云iPaaS标杆入榜

中国API管理-市场指南 Market Guide for API Management, China 是由全球最具权威的IT咨询与研究机构Gartner发布、聚焦中国API管理市场领域的专业研究报告&#xff0c;通过对市场概况以及代表厂商的分析&#xff0c;为企业决策者提供重要依据与参考。 得帆云iPaaS融合集成平台…

PPT架构师架构技能图

PPT架构师架构技能图 目录概述需求&#xff1a; 设计思路实现思路分析1.软素质2.核心输出&#xff08;office输出&#xff09; 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,ma…

算法:移除数组中的val的所有元素---双指针[2]

文章来源&#xff1a; https://blog.csdn.net/weixin_45630258/article/details/132689237 欢迎各位大佬指点、三连 1、题目&#xff1a; 给你一个数组 nums和一个值 val&#xff0c;你需要原地移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用…

通过 Blob 对二进制流文件下载实现文件保存下载

原理&#xff1a;前端将二进制文件做转换实现下载: 请求后端接口->接收后端返回的二进制流(通过二进制流&#xff08;Blob&#xff09;下载,把后端返回的二进制文件放在 Blob 里面)->再通过file-saver插件保存 页面上使用&#xff1a; <span click"downloadFil…

上海亚商投顾:三大指数小幅下跌 光刻机概念股午后走强

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 三大指数昨日小幅调整&#xff0c;创业板指走势较弱。减肥药概念股继续大涨&#xff0c;常山药业2连板&#x…

MySQL日常使用记录

1.时间 1.1.时间格式化 yyyy-MM-dd HH:mm:ss格式&#xff0c;如下&#xff1a; select date_format(now(), %Y-%m-%d %H:%i:%s) from dual;date_format函数是将date类型按照指定的格式转换成varchar类型 1.2.日期加减 当前天 1 天 select date_format(now(), %Y-%m-%d), …

概念解析 | 揭秘视觉与语言交叉模型:CLIP和BLIP的介绍

注1:本文系“概念解析”系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:CLIP和BLIP模型。 揭秘视觉与语言交叉模型:CLIP和BLIP的介绍 🎯 [LB: 0.45836] ~ BLIP+CLIP | CLIP Interrogator | Kaggle 大纲: 背景介绍原理介绍和推导 CLIP模型BLIP模…