【基础篇】十三、强软弱虚引用、终结器引用

2024-01-06 06:12

本文主要是介绍【基础篇】十三、强软弱虚引用、终结器引用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 0、相关🖊
  • 1、强引用
  • 2、软引用
  • 3、弱引用
  • 4、虚引用
  • 5、终结引用

关于对象能否被回收:

  • 计数器
  • 可达性分析

还可以根据引用的类型,不同的引用类型,对应对象的不同GC回收规则。

0、相关🖊

📕【强软弱虚】

在这里插入图片描述

1、强引用

  • 默认强引用,即把一个对象赋值给一个变量(也叫引用)
Object o = new Object();
  • GC时,有强引用的对象不会被回收,即使OOM了

Demo:

public class Demo {public static void main(String[] args) {Demo demo = new Demo();System.out.println("GC前: " + demo);System.gc();System.out.println("GC后: " + demo);//断掉强引用demo = null;System.gc();System.out.println("断掉强引用并GC: " + demo);}
}

在这里插入图片描述

2、软引用

  • 内存足够时,不会被GC回收
  • 内存不足时,才被GC回收
  • 包装为软引用:new SoftReference<对象类型>(对象)
    在这里插入图片描述
public class SoftReferenceDemo {public static void main(String[] args) {byte[] byte1 = new byte[1024 * 1024 * 100];SoftReference<byte[]> softReference = new SoftReference<>(byte1);byte1 = null;System.gc();System.out.println("内存充足时:" + softReference.get());try {byte[] bytes = new byte[1024 * 1024 * 100];} catch (Error e) {e.printStackTrace();} finally {System.out.println("内存不足时:" + softReference.get());}}}
public class SoftReferenceDemo {public static void main(String[] args) {SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024 * 1024 * 100]);System.gc();System.out.println("内存充足时:" + softReference.get());try {byte[] bytes = new byte[1024 * 1024 * 100];} catch (Error e) {e.printStackTrace();} finally {System.out.println("内存不足时:" + softReference.get());}}}

注意上面两份代码的区别,前者必须加个byte1 = null,这是个强引用,不断掉,即使内存不够,byte1对象也不会被回收,soft Reference.get结果也就一直不为null。这个地方卡了半小时,想着怎么还不回收,看半天发现这儿有个强引用。设置-Xmx200m,运行:

在这里插入图片描述

以上代码,盒子里的东西已经没了(被包装的对象被回收,get得到结果为null了),盒子也就没必要再留了。 但盒子里的东西何时被回收不确定,不能直接写一句先把盒子干掉:

softReference = null;

软引用中的对象如果在内存不足时回收,SoftReference对象本身也需要被回收:
在这里插入图片描述

  • 创建软引用时,构造方法里再传入一个引用队列
  • 对象A被回收,外层的SoftReference对象会加入队列
  • 遍历干掉外层的SoftReference

Demo:

public class SoftReferenceDemo {public static void main(String[] args) {ReferenceQueue<byte[]> queue = new ReferenceQueue<>();ArrayList<SoftReference<byte[]>> list = new ArrayList<>();for (int i = 0; i < 10; i++) {byte[] byte1 = new byte[1024 * 1024 * 100];//包装对象时再传个队列SoftReference<byte[]> softReference = new SoftReference<>(byte1, queue);list.add(softReference);}int count = 0;//能从队里拿出来的,都是对象被回收的while (queue.poll() != null) {++count;}System.out.println(count);}}

设置JVM堆内存total和max为200M(实际可用约190M左右),循环十次,自然有九个byte[ ] 对象回收,queue长度应该是9:

在这里插入图片描述

poll弹出的就是被回收掉内存对象的SoftReference对象。

3、弱引用

  • 不管JVM内存是否够用,GC运行,弱引用对象均被回收。
  • 和软引用一样,也可搭配一个引用队列
  • 用于ThreadLocal应对内存泄漏

在这里插入图片描述

public class Demo {public static void main(String[] args) {WeakReference<Demo> reference = new WeakReference<>(new Demo());System.out.println("GC前: " + reference.get());System.gc();System.out.println("GC后: " + reference.get());}
}

在这里插入图片描述

4、虚引用

  • 幽灵引用/幻影引用
  • 虚,形同虚设的意思
  • 和其他几种引用不一样,它不影响对象的回收规则
  • 仅有虚引用指向的对象,随时可能会被回收
  • 唯一的用途是当对象被垃圾回收器回收时可以接收到对应的通知
  • 虚引用get方法返回结果总为null

虚引用的一个应用场景是直接内存的释放问题:

public class Demo {public static final int size = 1024 * 1024 * 10;public static void main(String[] args) {/*** allocateDirect方法创建DirectByteBuffer对象* DirectByteBuffer对象构造方法里向操作系统申请了直接内存*/ByteBuffer directBuffer = ByteBuffer.allocateDirect(size);//干掉强引用directBuffer = null;System.gc();System.out.println();}
}

DirectByteBuffer对象被回收的时候,需要收到一个消息,去把直接内存的空间也释放了(不能只GC把堆里的DirectByteBuffer对象空间释放了,GC主要是处理堆,不是处理直接内存的)

在这里插入图片描述

往下跟:

在这里插入图片描述

Cleaner类继承了虚引用类,这里传入要监控的ByteBuffer对象,告诉虚引用我要监控这个对象的回收,接下来会有一个线程去监控这个对象的回收,

在这里插入图片描述

当ByteBuffer对象被回收,就调用Deallocator类(实现了Runnable接口)的run方法,run方法里干的活儿就是释放了直接内存:

在这里插入图片描述

贴个清晰点的Demo:

public class ReferenceDemo {public static void main(String[] args) {MyObject myObject = new MyObject();ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();PhantomReference<MyObject> phantomReference = new PhantomReference<>(myObject, referenceQueue);List<byte[]> list = new ArrayList<>();new Thread(() -> {while (true){list.add(new byte[1024 * 1024]); //1M//歇500ms,写1M进Listtry {TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}//验证下每次get都是nullSystem.out.println(phantomReference.get() + " list add OK.");}},"t1").start();new Thread(() -> {while (true){Reference<? extends MyObject> reference = referenceQueue.poll();if(reference != null){System.out.println("有虚引用对象被回收,加入了队列");//break;}}},"t2").start();}}

5、终结引用

  • 对象需要被回收时,终结器引用会关联这个对象,并放入Finalizer类的引用队列
  • (无需手动编码,其内部配合引用队列使用)
  • 稍后会由FinalizerThread线程从队列中获取这个对象,并执行它的finalize方法
  • 在这个对象该第二次被回收时,才真正干掉这个对象

总结就是第一次包装一下扔到引用队列+执行finalize方法,第二次GC它时,抬走。根据这个特点,如果在第三步里的finalize方法里给变为null的对象,重新赋一个强引用,岂不是可以让这个对象复活。 Demo:

public class FinalizeReferenceDemo {public static FinalizeReferenceDemo reference = null;/*** 存活性验证*/public void alive() {System.out.println("当前对象还存活...");}@Overrideprotected void finalize() throws Throwable {try {System.out.println("finalize方法执行===");//设置强引用自救reference = this;} finally {super.finalize();}}@SneakyThrowspublic static void test() {reference = null;System.gc();//执行finalize方法的优先级低,这里等一会儿再往下走Thread.sleep(500);if (reference != null) {  //若上面finalize方法执行,则这里不会为null了reference.alive();} else System.out.println("对象已被回收!");}public static void main(String[] args) {reference = new FinalizeReferenceDemo();test();test();}
}

运行结果:

在这里插入图片描述

test方法第一次调用,对象引用被置为null,并手动GC,该被回收了,此时进入引用队列并在稍后执行finalize方法。重写的finalize方法里给引用重新赋值,不为null了,test方法调alive方法发现对象又活了。

接着再第二次调test方法,按理说和第一次调test方法是一个流程,但finalize方法源码有说明:

在这里插入图片描述

即finalize方法最多被同一个JVM调用调用一次,对于一个被放弃的对象。所以第二次调test把引用又置为null并GC后,不会再调finalize方法,因此休眠500ms后,引用依然为null,对象被回收。

这篇关于【基础篇】十三、强软弱虚引用、终结器引用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/575440

相关文章

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

AI基础 L9 Local Search II 局部搜索

Local Beam search 对于当前的所有k个状态,生成它们的所有可能后继状态。 检查生成的后继状态中是否有任何状态是解决方案。 如果所有后继状态都不是解决方案,则从所有后继状态中选择k个最佳状态。 当达到预设的迭代次数或满足某个终止条件时,算法停止。 — Choose k successors randomly, biased towards good ones — Close

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

C 语言基础之数组

文章目录 什么是数组数组变量的声明多维数组 什么是数组 数组,顾名思义,就是一组数。 假如班上有 30 个同学,让你编程统计每个人的分数,求最高分、最低分、平均分等。如果不知道数组,你只能这样写代码: int ZhangSan_score = 95;int LiSi_score = 90;......int LiuDong_score = 100;int Zhou

c++基础版

c++基础版 Windows环境搭建第一个C++程序c++程序运行原理注释常亮字面常亮符号常亮 变量数据类型整型实型常量类型确定char类型字符串布尔类型 控制台输入随机数产生枚举定义数组数组便利 指针基础野指针空指针指针运算动态内存分配 结构体结构体默认值结构体数组结构体指针结构体指针数组函数无返回值函数和void类型地址传递函数传递数组 引用函数引用传参返回指针的正确写法函数返回数组

【QT】基础入门学习

文章目录 浅析Qt应用程序的主函数使用qDebug()函数常用快捷键Qt 编码风格信号槽连接模型实现方案 信号和槽的工作机制Qt对象树机制 浅析Qt应用程序的主函数 #include "mywindow.h"#include <QApplication>// 程序的入口int main(int argc, char *argv[]){// argc是命令行参数个数,argv是

【MRI基础】TR 和 TE 时间概念

重复时间 (TR) 磁共振成像 (MRI) 中的 TR(重复时间,repetition time)是施加于同一切片的连续脉冲序列之间的时间间隔。具体而言,TR 是施加一个 RF(射频)脉冲与施加下一个 RF 脉冲之间的持续时间。TR 以毫秒 (ms) 为单位,主要控制后续脉冲之前的纵向弛豫程度(T1 弛豫),使其成为显著影响 MRI 中的图像对比度和信号特性的重要参数。 回声时间 (TE)

Java基础回顾系列-第七天-高级编程之IO

Java基础回顾系列-第七天-高级编程之IO 文件操作字节流与字符流OutputStream字节输出流FileOutputStream InputStream字节输入流FileInputStream Writer字符输出流FileWriter Reader字符输入流字节流与字符流的区别转换流InputStreamReaderOutputStreamWriter 文件复制 字符编码内存操作流(

Java基础回顾系列-第五天-高级编程之API类库

Java基础回顾系列-第五天-高级编程之API类库 Java基础类库StringBufferStringBuilderStringCharSequence接口AutoCloseable接口RuntimeSystemCleaner对象克隆 数字操作类Math数学计算类Random随机数生成类BigInteger/BigDecimal大数字操作类 日期操作类DateSimpleDateForma