强引用,弱引用,软引用,虚引用它们有什么区别?你知道吗?

news2024/11/28 9:28:54

目录

1. 先简单了解JVM内存模型

2. 强引用类型解析

2.1 强引用理论解释

2.2 强引用代码演示

3. 软引用类型解析

3.1 软引用理论解释

3.2 软引用与强引用的区别? 

3.3 软引用代码展示

3.4 软引用的使用场景?

4. 弱引用类型解析

4.1 弱引用理论解释

4.2 弱引用代码演示

5. 虚引用类型解析

5.1 虚引用构造器展示

5.2 虚引用与弱引用的比较

5.2 虚引用的使用场景?

 6. 小小总结


1. 先简单了解JVM内存模型

(注:如果已经了解JVM内存模型的同学可以直接看下面)

在讲解强弱软虚引用四种引用之前,我们先来回顾一下 JVM 虚拟机的内存模型,简单了解一下对象在  JVM 中的存放原理,也是为了让不了解Java虚拟机的同学在看此篇文章的时候不那么迷惑,下面开始正题。

Java 虚拟机运行在内存中,当虚拟机拿到了自己可支配的内存之后,会将内存分为五个部分,分别是 栈(JVM栈),堆,方法区(JDK1.8之后改名元空间),程序计数器,本地方法栈。

如下图所示

JVM栈:运行我们的程序,如 main 方法;

堆:存放对象,创建的对象都存放在堆中;

方法区(元空间):存放类加载器,静态变量静态方法,全部变量;

这里举个例子,如下代码所示,我定义一个 main 函数,打印一句话,那么该程序就会开辟一个新的JVM栈,当我们再定义另一个 main 方法时,就会再开辟一个新的JVM栈,JVM栈是每个线程私有的,但它们会共享堆中的对象和元空间中的全局静态变量。

2. 强引用类型解析

2.1 强引用理论解释

Java 中,我们通常都是通过 Object o = new Object() 的方式来创建一个对象,这个 new Object() 对象就存放在堆中,我们的 o 就存放在各自程序的JVM栈中是私有的,o 中保存了堆中对象的内存地址,如下图所示

当我们的程序想要操作对象的时候,就会通过 o 中保存的内存地址去堆中寻找该对象,然后对该对象进行操作。强引用对自然非常强,只要堆中的对象有变量指向它,它就不会被GC回收,只有没有人任何对象引用它的时候,它才会被GC垃圾回收器回收。

2.2 强引用代码演示

如下代码展示。写了注释,所以就不再赘述了

public class Test {
    // 重写 Test 类中的 finalize() 方法
    @Override
    public void finalize() throws Throwable{
        // 打印一句话作为标记,证明该方法被调用过
        System.out.println("finalize方法执行");
    }
    public static void main(String[] args) throws Exception {
        // 创建类对象 t
        Test t = new Test();
        System.out.println(t+"第一次获取对象");
        t = null;
        // 开启垃圾回收GC
        System.gc();
        // 因为GC垃圾回收是另外的垃圾回收线程,所以我们让主线程先睡两秒,避免造成误差
        Thread.sleep(2000);
        // 经过GC之后再次获取t对象
        System.out.println(t+"第二次获取对象");
    }
}

关于 finalize 方法我还是要说明一下。

finalize 方法是定义在 Object 类中的方法,每个对象都可以调用该方法,当对象被回收的时候,就会执行 finalize 方法;因为Java虚拟机的GC垃圾回收是在一定条件下才运行的,我们这里只把 t 对象赋值为 null ,不一定会启动 GC垃圾回收过程,所以我们通过 System.gc() 主动启动垃圾回收线程,我也在注释中也说明了,GC垃圾回收有它自己的线程,所以我们调用 sleep 方法让 main 方法的进行先睡一会,让GC完成之后再去重新获取。

运行之后如下图所示,在控制台中我们也可以看到,finalize 方法被执行打印出来了,说明对象被回收之后,会执行自身的 finalize 方法。

3. 软引用类型解析

3.1 软引用理论解释

首先我需要给大家明确一点,软引用本身其实是一个类,名为 "SoftReference",可以添加泛型。我们创建出该类的对象,那么该类的对象引用类型就是软引用。

创建软引用对象的方式为 SoftReference<?> m = new SoftReference<?>(new ?)

软引用内存图如下所示,m 对象在JVM程序栈中,软引用对象和软引用对象内部的数组对象都包含在堆中,m 对象与软引用对象之间是正常的强引用,软引用对象与内部的数组对象它们两个之间则是弱引用,在图中也表示为虚线,没有强引用那么强。

3.2 软引用与强引用的区别? 

刚才说到了,软引用没有强引用强,是如何体现的呢?

假设我们 Java 虚拟机的堆内存为20M,现在我定义了一个软引用的字节数组,大小为10M,接下来我还要创建一个大小为12M的数组,此时我们来看,10M+12M>20M,已经要内存溢出,所以按道理来说我们想要创建的数组是不可能创建成功的,但是由于我们先前创建的字节数组为软引用,那么此时堆就会把这个10M的字节数组从堆中清除,清除之后就有足够的内存空间容纳新创建的数组了

而假设我们要创建一个大小为5M的数组,此时 10+5<20,还没有超过内存大小,不会内存溢出,那么此时堆就不会把这个10M的字节数组清除,而是让它继续留在堆中

3.3 软引用代码展示

如下展示软引用对象被清除的代码,注释我都写的很清楚,应该不需要做过多的解释说明了。

public static void main(String[] args) throws InterruptedException {
        // 创建一个软引用对象 m,并在m软引用对象在定义一个 10M 的字节数组
        SoftReference<byte[]> m = new SoftReference<>(new byte[1024 * 1024 * 10]);
        // 通过get方法获取一下软引用对象中的字节数组对象
        System.out.println(m.get()+"---"+"第一次获取软引用字节数组对象");
        // 调用 GC,启动垃圾回收
        System.gc();
        // 让 main 线程睡 0.5秒
        Thread.sleep(500);
        // GC之后再获取软引用的字节数组对象,看是否能获取到
        System.out.println(m.get()+"---"+"GC之后第二次获取软引用字节数组对象");
        // 再创建一个大小为 12M 的字节数组
        byte[] b = new byte[1024 * 1024 * 12];
        // 获取新创建的数组,看是否被创建成功,看能否获取成功
        System.out.println(b+"---"+"获取新创建的字节数组对象b");
        // 此时再次获取先前的软引用对象 m,看是否还存在
        System.out.println(m.get()+"---"+"创建字节数组b之后再来获取软引用字节数组对象");
    }

在运行 main 函数之前,我们需要先对JVM栈做一个配置,如下图,点击该类配置项,

点击之后弹出如下界面,这里我们配置一下 VM 虚拟机的参数项,这里配置一下JVM内存大小为20M,每个人的程序由于你可能在当前模块定义了其他的类或者程序,或者堆中已经存放了其他对象,导致结果可能不太相同是正常现象,可以试着变换一下内存参数大小值或数组大小,这里我就是将内存设置为了20M,在调试运行程序之前也试过其他数值大小,但没有得出正确的结果,所以结果不一样你可以改变一下大小,多试几次,换个数值试一下即可,不一定是程序的问题。只要能让内存溢出的情况产生即可。

配置完成后如下所示,点击应用确认,然后关闭窗口即可运行 main 函数 

然后我们运行上述 main 方法,在控制台中得到如下结果,可以看到,在创建强引用硬数组之后,我们并没有手动将弱引用的数组对象赋值为 null,但是在第三次获取软引用字节数组的时候,它却变成了 null,这就是弱引用的特性,内存足够时,允许它留在堆中,对内存不够用的时候,就把它从堆内存中清除

测试过软引用对象的字节数组被清除的情况之后,我们再来测试一下不被清除的情况,很简单,把要创建强引用的数组大小变小一点即可,这里我改为5M,即[1024 * 1024 * 5],其他代码都不用动

重新运行 main 函数,在控制台得到了不一样的结果如下所示,可以看到,此时创建了一个小的数组,没有内存溢出的情况下,第三次获取软引用的数组时,可以获取得到

3.4 软引用的使用场景?

通过上面弱引用的特性,我们其实可以大概能了解它的一个使用场景,有什么东西我们可以满足内存足够时让它存在,内存不足时让它离开呢?

当然是缓存啦!!!

各位同学想一下,如果一张图片非常的大,或者其他资源,加载需要时间比较久,我们就可以把它定义为弱引用,提前加载到内存中,在需要的时候直接访问,当内存不够的时候,再把它从内存中清除,需要的时候在读取到内存中。这就是软引用的其中一个使用场景。

4. 弱引用类型解析

4.1 弱引用理论解释

弱引用与软引用类似,都是一个了类,Java中叫 "WeakReference",该类创建的对象的引用方式便是弱引用,创建对象的方法也是与软引用相同,这里就不举了了,下面演示代码的时候也可以看到。

弱引用与强引用在遇到GC垃圾回收时的情况恰恰相反,强引用的对象不管哪次GC垃圾回收时,都不会被清除,而弱引用对象只要遇到GC,都会被回收;

4.2 弱引用代码演示

代码如下所示,注释都已经说明,不需要做过多解释

public class WeakReferenceTest {
    public static void main(String[] args) throws InterruptedException {
        // 通过弱引用创建一个字符串对象
        WeakReference<String> m = new WeakReference<>(new String("我是弱引用"));
        // 打印弱引用对象的字符串对象
        System.out.println(m.get()+"---"+"GC之前第一次获取弱引用字符串对象");
        // 手动开启GC
        System.gc();
        // 让 main 线程睡1秒,防止GC未结束,main函数先运行完导致结果异常
        Thread.sleep(1000);
        // GC之后再来获取弱引用对象,看能否获取到
        System.out.println(m.get()+"---"+"GC之后第二次获取弱引用字符串组对象");
    }
}

然后我们运行上述 main 方法,在控制台中得到如下结果

5. 虚引用类型解析

5.1 虚引用构造器展示

虚引用同样也是一个类,它的构造方法需要我们传递两个参数,如下图所示

第一个参数是虚引用对象,第二个参数需要我们传递一个队列

5.2 虚引用与弱引用的比较

虚引用与弱引用相似的是:被虚引用指向的对象仍旧会被GC垃圾回收器回收。

虚引用与弱引用不同的是:虚引用内部的对象我们永远无法通过 get() 方法获取到其中的值,上面我们可以看到强软弱三种引用都可以获取到其对象,虚引用则不可以。

5.2 虚引用的使用场景?

那么既然获取不到虚引用的对象,要它有什么用呢?

虚引用最主要的一个场景就是管理堆外内存。如下图所示

Java 虚拟机是运行在操作系统的内存之中的,当我们要处理一份数据的时候,

第一步:操作系统先读取到自己的内存中;

第二步:然后再拷贝到我们的JVM内存中;

第三步:JVM处理完毕之后,再拷贝回操作系统内存中;

第四步:再由操作系统返回至外界;

中间经过了操作系统这一媒介,效率可想而知,是比较低的,所以 Java 就提供了虚引用,它可以直接得到并直接操作操作系统的内存,提高了效率

但是随着虚引用可以管理堆外内存,一个新的问题产生。

我的虚引用对象存放在JVM的堆中,虚引用所指向的对象则是在操作系统的内存中。假如说我的虚引用在JVM内存中已经赋值为空,虚引用对象与操作系统中的对象断开了联系。虚引用之前所指向的操作系统内存中的对象是不能被JVM垃圾回收器回收的,因为虚引用指向的对象并没有存放在JVM内存的堆中,而是直接存放在操作系统的内存中,所以JVM是无法将其回收的,一旦长时间如此,操作系统的内存终究会出现大量没有引用的对象而且无法被清除,造成内存泄露

为了避免内存泄漏相框的发生,就引入了队列这一属性,它最大的作用就是当虚引用的对象被回收的时候,就会给队列发一个信号,说明一下虚引用的对象引用失效了,此时JVM内部的GC垃圾回收就会对队列做判断,如果满足一定的条件,那么JVM的垃圾回收器就会把JVM内存中的垃圾对象堆外操作系统中的垃圾对象一并回收,就不会造成内存泄露的情况了。

(这里补充一点,Java的GC垃圾回收是由C++语言编写的,可以直接操控计算机底层,不仅可以清除JVM内存的垃圾,也可以清除总操作系统内存的垃圾)

 6. 小小总结

讲解到了这里,各位同学应该对强软弱虚四种引用有一些初步的了解了,那么我们来简单的总结一下吧!

强引用:就是不同的引用,平常创建对象的方式就是强引用,被强引用指向的对象不能被垃圾回收器回收。

软引用:通过创建软引用类对象来实现,内存足够时允许停留在内存中,内存不够时就将其从内存中清除给其他对象腾出空间,可以作为缓存来使用。

软引用:比强引用弱,就算有引用指向它,只要发生GC垃圾回收过程,软引用对象就会被清除。

虚引用:比弱引用还要若,通常用作管理对外内存。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows无法安装到这个磁盘怎么办?

在固态硬盘&#xff08;SSD&#xff09;上安装Windows 11/10/8/7会使您的计算机运行速度更加快速&#xff0c;程序响应速度更加迅捷&#xff0c;如果您遇到了和上述案例相似的情况&#xff0c;Windows无法安装到固态硬盘上&#xff0c;也不用太过苦恼&#xff0c;造成此问题的原…

iOS设备自动备份软件哪个更好用?

备份是在使用iOS设备时经常用到一个功能。有很多种方法可以自动备份iOS设备&#xff0c;有通过iCloud进行自动备份的方法&#xff0c;有通过iTunes进行的&#xff0c;而今天小编再给大家介绍一个通过iOS设备管理软件iMazing进行自动备份的方法。这三种方法究竟哪一个更适合大家…

ElasticSearch自学笔记

es学习内容 day01 概念区别 Mysql和es概念对比 mysql:擅长事务类型的操作&#xff0c;可以确保数据的安全和一致性ES:擅长海量数据的搜索&#xff0c;分析和计算 mapping 属性 创建index的时候值为true就是参与搜索 索引库修改 PUT /库名/_mapping 字段名必须是新字段名文…

3dmax渲染内存不足,这样解决!

在3dmax工作时显示以下错误消息&#xff0c;3ds Max 中“发生内存不足错误”或者是3dmax渲染内存不足&#xff0c;可能还注意到系统性能很慢。 3dmax渲染内存不足原因&#xff1a; 发生此错误的原因是计算机在完成您请求的操作之前耗尽了可用内存。执行任何过程时都可能会出…

DP7340——192KHz双声道输入24位AD 转换器

DP7340是一款完整的采样、模数音频信号转换、抗混叠滤波的芯片&#xff0c;在串行格式下以每声道最高200kHz采样率高达24位宽&#xff0c;并支持大部分的音频数据格式。 DP7340基于一个带线性模拟低通滤波器的五阶Multi-BitΔ∑调制器&#xff0c;自动检测信号频率和主时钟频率…

记:CN 470-510MHz Band

目录 CN470-510 序头格式 CN470-510频道频率 CN470-510 数据速率和终端输出功率编码 CF470-510 JoinResp CFList CN470-510 LinkADRReq命令 CN470-510 最大载荷大小 CN470-510接收窗口 CN470-510默认参数 CN470-510 序头格式 应使用以下同步字&#xff1a; 调制同步字…

Pyside6:开发一个自适应的图片控件

在Qt中最简单展示图片用的控件是QLabel&#xff0c;但这个控件使用起来非常不方便&#xff0c;没有添加自适应的时候&#xff1a; 可以发现该图片是按真实像素来展示的&#xff0c;因此图片如果过大&#xff0c;只能展示局部。那么添加自适应后&#xff0c;变化又会出现&#x…

简单但好用:4种Selenium截图方法了解一下!

前言 我们执行UI自动化操作时&#xff0c;大多数时间都是不在现场的&#xff0c;出现错误时&#xff0c;没有办法第一时间查看到&#xff0c;这时我们可以通过截图当时出错的场景保存下来&#xff0c;后面进行查看报错的原因&#xff0c;Selenium中提供了几种截图的方法&#…

spring bean实例化过程及顺序

spring bean的初始化从doCreateBean方法开始&#xff0c;依次会调用下面三个方法执行bean的初始化。大部分方法都在AbstractAutowireCapableBeanFactory类中。 实例化 createBeanInstance()方法根据BeanDef获取bean对应的class通过反射调用构造函数进行bean的实例化。 这里会…

C语言的stdio.h的介绍

C语言的stdio.h的介绍 C语言的stdio.h的介绍 C语言的stdio.h的介绍C语言stdio.h的介绍 C语言stdio.h的介绍 这个含义是导入标准输入输出库 包含头文件.h&#xff0c;std标准库&#xff0c;io是input output输入输出库 <>代表系统库&#xff0c;自定义的话用""…

Android Jetpack Compose之确定重组范围并优化重组

目录 1.概述2.确定Composable重组的范围3.优化重组的性能3.1 Composable 位置索引3.2 通过Key添加索引信息3.3 使用注解Stable优化重组 1.概述 前面的文章提到Compose的重组是智能的&#xff0c;Composable函数在进行重组时会尽可能的跳过不必要的重组&#xff0c;只对需要变化…

C语言中结构体,枚举,联合相关介绍

本次重点&#xff1a; 1、结构体 &#xff1a; &#xff08;1&#xff09;结构体类型的声明 &#xff08;2&#xff09;结构的自引用 &#xff08;3&#xff09;结构体变量的定义和初始化 &#xff08;4&#xff09;结构体内存对齐 &#xff08;5&#xff09;结构体传参 …

干细胞液氮容器选择与使用

干细胞液氮容器的使用非常重要&#xff0c;以确保干细胞样品在冷冻和储存过程中的有效性和安全性。以下是使用干细胞液氮容器时需要注意的事项&#xff1a; 1、容器选择&#xff1a;选择合适的容器非常重要。容器应具有良好的密封性能和耐腐蚀性&#xff0c;以避免外部空气和污…

01-Zookeeper特性与节点数据类型详解

上一篇&#xff1a; 在了解Zookeeper之前&#xff0c;需要对分布式相关知识有一定了解&#xff0c;什么是分布式系统呢&#xff1f;通常情况下&#xff0c;单个物理节点很容易达到性能&#xff0c;计算或者容量的瓶颈&#xff0c;所以这个时候就需要多个物理节点来共同完成某项…

Oracle VM VirtualBox安装并下载安装CentOS7

Oracle VM VirtualBox安装并下载安装CentOS7 Oracle VM VirtualBox下载CentOS创建虚拟机 Oracle VM VirtualBox VM下载链接 https://www.oracle.com/cn/virtualization/virtualbox/ 点击链接直接下载就行&#xff0c;下载完默认安装或者更改一下安装目录。 下载CentOS http://…

服务网格概述

引言 2016 年前后&#xff0c;"服务网格"这个词出现在微服务、云计算和 DevOps 的领域。Buoyant 团队在 2016 年用这个词来解释他们的产品 Linkerd。服务网格的到来主要是由于 IT 领域内的一场风暴。开发人员开始使用多语言&#xff08;polyglot&#xff09;方法构建…

古彝文识别:文化遗产的数字化之旅

目录 &#x1f345;前言&#x1f353;古彝文介绍&#x1f353;古彝文识别的重难点&#x1f352;原籍难以获取&#xff0c;传统翻译过程繁琐&#xff0c;周期长。&#x1f352;版式多样&#xff0c;笔画相近。&#x1f352;图像质量差&#xff0c;手写识别难。&#x1f352;古彜…

第二证券:迎政策助力,新型工业化爆发,德恩精工3日涨超60%

新式工业化概念26日盘中大幅拉升&#xff0c;到发稿&#xff0c;德恩精工、精伦电子、天永智能等涨停&#xff0c;固高科技涨约8%&#xff0c;亚威股份涨逾6%&#xff0c;金自天正、创世纪涨约5%。 值得注意的是&#xff0c;精伦电子已接连5个交易日涨停&#xff0c;公司昨日晚…

Mac菜单栏图标管理工具:Bartender 5 完美兼容MacOS Sonoma 14系统

Bartender 5 是一款流行的软件程序&#xff0c;专为酒店行业的调酒师和专业人士设计。它提供了一系列功能和工具来简化酒吧或餐厅的饮料订单、库存和客户偏好的管理流程。Bartender 5 的一些主要功能包括&#xff1a; 1. 饮料配方&#xff1a;该软件包括一个全面的饮料配方数据…

计算机丢失msvcp140_1.dll的解决办法,丢失msvcp140_1.dll的原因

丢失 msvcp140_1.dll 是一个常见的错误信息&#xff0c;通常会在尝试运行某些程序时出现。msvcp140_1.dll 是一个动态链接库文件&#xff0c;它包含了许多 C标准库函数的实现&#xff0c;这些函数在许多程序中都是必需的。因此&#xff0c;如果丢失了该文件&#xff0c;程序可能…