一、Java 引用概述

Java 中出现四种引用是为了更加灵活地管理对象的生命周期,以便在不同场景下灵活地处理对象的回收问题。不同类型的引用在垃圾回收时的处理方式不同,可以用来实现不同的垃圾回收策略。Java 目前将其分成四类,类图如下:

Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

    @Deprecated(since="9")protected void finalize() throws Throwable { }
}

传统的 finalize() 方法在 Java 9 中已经被标记为废弃(deprecated),而且在 Java 11 中已经被删除(推荐使用 Cleaner是 Java 新一代的内存回收处理机制)。这是因为 finalize() 方法存在许多问题,包括但不限于以下几点:

  1. finalize() 方法并不能保证被及时地调用,这可能会导致内存泄漏等问题。
  2. finalize() 方法的执行时机是不确定的,可能会导致程序的不可预测行为。
  3. finalize() 方法是被 JVM 调用,手动调用容易出错,不够灵活。

=>Cleaner 的执行过程,如下:

在 JDK9 中,Java 提供了一种新的机制来代替传统的 finalize() 方法来清理对象,就是 Cleaner。Cleaner 是一个 Java 类,用于注册需要进行清理的对象以及相关的清理方法。它使用了本地内存技术,可以在 Java 对象释放后及时进行清理。

当一个 Java 对象不再被引用时,其所占用的内存并不会立即释放,而是会被标记为“可回收”状态。JVM 会在后台开启一个线程,定期扫描这些“可回收”对象,清理其中已经没有引用的对象。Cleaner 就是在这个扫描过程中对于对象的清理操作。

使用 Cleaner 机制时,开发人员需要创建一个 Cleaner 对象,并使用该对象对需要清理的对象进行注册,同时定义一个清理方法。当被注册的对象被回收时,JVM 会在后台的扫描过程中调用注册的清理方法,对对象进行清理操作。相较于传统的 finalize() 方法,Cleaner 机制的清理操作更加灵活,可以在多种情况下触发,并且不会受到 finalize() 方法的不稳定性的影响。

下面是一个使用 Cleaner 的例子:

public class Resource implements AutoCloseable {private static final Cleaner cleaner = Cleaner.create();private final Cleaner.Cleanable cleanable;private ByteBuffer buffer;public Resource(int size) {buffer = ByteBuffer.allocate(size);cleanable = cleaner.register(this, new ResourceCleaner(buffer));}@Overridepublic void close() throws Exception {cleanable.clean();}private static class ResourceCleaner implements Runnable {private ByteBuffer buffer;public ResourceCleaner(ByteBuffer buffer) {this.buffer = buffer;}@Overridepublic void run() {System.out.println("Cleaner is cleaning the resource");buffer = null;}}
}

在上面的例子中,Resource 类使用了 Cleaner 来释放资源。Resource 类中的 buffer 对象在实例化时会被分配内存。当使用完 Resource 实例时,需要调用 close() 方法来释放资源。close() 方法调用了 Cleaner.Cleanable 的 clean() 方法来触发资源的释放。同时,ResourceCleaner 类实现了 Runnable 接口,run() 方法被用来进行资源的清理操作。在这个例子中,资源的清理操作是将 buffer 设置为 null。这样,一旦实例被垃圾收集器回收,clean() 方法就会自动被调用,从而释放资源。

  1. 一般来说,实现了 CloseableAutoCloseable 接口的类,在使用完后都需要手动调用 close() 方法来释放资源。但是有些情况下,JVM 会自动调用 close() 方法,比如在 try-with-resources 语句中,当 try 代码块执行完后,JVM会自动调用相应资源的 close() 方法来释放资源,而无需手动调用。但是,如果在 try-with-resources 语句中使用了多个资源,需要注意它们的释放顺序,确保后打开的资源先关闭,以避免可能出现的资源泄露问题。
  2. ResourceCleaner 是由 JVM 的垃圾回收器自动触发的,当一个对象不再被引用时,它的 clean() 方法就会被自动调用。具体来说,当垃圾回收器扫描到一个对象时,如果这个对象实现了 Cleaner.Cleanable 接口,那么垃圾回收器会把它注册到一个全局的 ReferenceHandler 队列中,这个队列中的线程会定期触发这些对象的 clean() 方法。这个过程是由 JVM 自动进行的,程序员无需手动触发。

二、强、软、弱、虚引用简单介绍

1、强引用(Reference 默认)

是最常见的引用类型,也是默认的引用类型。当内存不足,JVM 开始垃圾回收,对于强引用的对象,就算是出现 OOM 也不会对该对象进行回收,死都不放。

强引用只要还指向一个对象,就表示对象还活着,垃圾收集器不会碰这些对象。在 Java 最常见的就是把一个对象赋值给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用引用时,它处理可达状态,它不可能被垃圾回收器回收,即使该对象后面永远都不会被使用,JVM 也不会进行回收。因此强引用是造成 Java 内存泄露的主要原因之一。

比如下面这个例子:

class MyObject {public byte[] buff = new byte[1000 * 1000 * 3];@Overrideprotected void finalize() throws Throwable {System.out.println(">>>>>>调用 finalize 清理资源...");}
}
public class ReferenceDemo {public static void main(String[] args) {MyObject myObject = new MyObject();System.out.println("gc before myObject = " + myObject);try {byte[] bytes = new byte[1000 * 1000 * 8];System.gc();} finally {System.out.println("gc after myObject = " + myObject);}}

首先通过 -Xms10m -Xmx10m 命令把 JVM 堆内存修改成 10M 方便测试,然后定义一个类 MyObject 里面分配一个数组占用堆内存 3M 空间,然后再 main() 方法中又分配一个 8M 大小数组,这样加起来肯定超过 10M 堆内存,会 OOM,但是可以手动调用 System.gc 触发垃圾回收,但是这里并不是回收(finalize() 方法肯定不会被调用),因为这里是属于强引用,就算 OOM 也不会被回收。

在 JVM 准备垃圾回收之前会先去调用 finalize() 方法做一些清理工作,所以只需要覆写该方法演示效果(工作中不会这样干),然后手动调用 System.gc() 让他触发 finalize() 方法调用。
垃圾回收线程是一个后台守护线程,定时在回收垃圾,这里提供 System.gc() 方便手动触发 GC 垃圾回收

效果如下:

gc before myObject = main.juc.MyObject@5ebec15
gc after myObject = main.juc.MyObject@5ebec15
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat main.juc.ReferenceDemo.main(ReferenceDemo.java:27)

那么怎么可以让他回收呢?可以用最简单的方式,将强引用直接赋值 null。修改之后的代码如下:

class MyObject {public byte[] buff = new byte[1000 * 1000 * 1];@Overrideprotected void finalize() throws Throwable {System.out.println(">>>>>>调用 finalize 清理资源...");}
}
public class ReferenceDemo {public static void main(String[] args) {MyObject myObject = new MyObject();System.out.println("gc before myObject = " + myObject);try {byte[] bytes = new byte[1000 * 1000 * 7];myObject = null;System.gc();System.out.println(">>>>>>垃圾回收..."+myObject);} finally {System.out.println("gc after myObject = " + myObject);}}
}

输出结果如下:

gc before myObject = main.juc.MyObject@5ebec15
>>>>>>调用 finalize 清理资源...
>>>>>>垃圾回收...null
gc after myObject = nullProcess finished with exit code 0

myObject = null 可以帮助 JVM 进行垃圾回收。

2、软引用(SoftReference 装饰对象)

如果一个对象只具有软引用,则在系统内存不足时,垃圾回收器会尝试回收该对象。这种引用类型通常用于缓存中,以便在内存不足时自动释放缓存,可以通过 SoftReference 类来创建软引用。例子如下:

class MyObject {public byte[] buff = new byte[1000 * 1000 * 3];@Overrideprotected void finalize() throws Throwable {System.out.println(">>>>>>调用 finalize 清理资源...");}
}
public class ReferenceDemo {public static void main(String[] args) {SoftReference<MyObject> softReference = new SoftReference<>(new MyObject());try {System.out.println("gc before myObject = " + softReference.get());byte[] buf = new byte[1000*1000*3];System.gc();Thread.sleep(3000);System.out.println("gc after myObject = " + softReference.get());} finally {System.out.println(">>>>>>内存不够 myObject="+softReference.get());}}
}

首先通过 -Xms10m -Xmx10m 命令把 JVM 堆内存修改成 10M 方便测试,然后把需要标识为软引用的对象通过 SoftReference 关键字进行包装。MyObject 类中首先分配一个数组,数组占堆内存大约 3M 空间,mian() 方法 3M 空间,远远还没有超过 10M 分配内存大小,然后手动调用 gc,效果如下:

gc before myObject = main.juc.MyObject@5ebec15
gc after myObject = main.juc.MyObject@5ebec15
>>>>>>内存不够 myObject=main.juc.MyObject@5ebec15

明显在内存足够的情况下,不会触发 GC 垃圾回收。现在将代码修改成下面这样,代码如下:

class MyObject {public byte[] buff = new byte[1000 * 1000 * 3];@Overrideprotected void finalize() throws Throwable {System.out.println(">>>>>>调用 finalize 清理资源...");}
}
public class ReferenceDemo {public static void main(String[] args) {SoftReference<MyObject> softReference = new SoftReference<>(new MyObject());try {System.out.println("gc before myObject = " + softReference.get());byte[] buf = new byte[1000*1000*7];System.gc();Thread.sleep(3000);System.out.println("gc after myObject = " + softReference.get());} finally {System.out.println(">>>>>>内存不够 myObject="+softReference.get());}}
}

效果如下:

gc before myObject = main.juc.MyObject@5ebec15
gc after myObject = null
>>>>>>内存不够 myObject=null

明显在内存不足时,会触发 GC 垃圾回收,将 SoftReference 引用的内存空间释放,这个就是 SoftReference 引用的好处。内存足,不回收,反之,回收。

3、弱引用( WeakReference 装饰对象)

如果一个对象有弱引用,只要当垃圾回收器扫描到这个对象时,无论内存是否充足,都会回收该对象。弱引用通常用于实现缓存、内存敏感的高速缓存、监视器等功能。可以通过 WeakReference 类来创建弱引用。通过 -Xms10m -Xmx10m 命令把 JVM 堆内存修改成 10M 方便测试。代码如下:

通过 -Xms10m -Xmx10m 命令把 JVM 堆内存修改成 10M 方便测试。

class MyObject {public byte[] buff = new byte[1000 * 1000 * 3];@Overrideprotected void finalize() throws Throwable {System.out.println(">>>>>>调用 finalize 清理资源...");}
}
public class ReferenceDemo {public static void main(String[] args) {WeakReference<MyObject> softReference = new WeakReference<>(new MyObject());try {System.out.println("gc before myObject = " + softReference.get());System.gc();Thread.sleep(3000);System.out.println("gc after myObject = " + softReference.get());} catch (Exception e) {System.out.println(">>>>");} finally {System.out.println(">>>>>>内存不够 myObject="+softReference.get());}}
}

输出结果如下:

gc before myObject = main.juc.MyObject@5ebec15
>>>>>>调用 finalize 清理资源...
gc after myObject = null
>>>>>>内存不够 myObject=null

看到不管内存是否还有,只要 GC 扫描(System.gc() 可以出发 GC 扫描)到就会直接被回收。

补充:通过弱引用举个实际案例如下:

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;public class ImageLoader {private Map<String, WeakReference<Image>> cache = new HashMap<>();public Image loadImage(String filename) {Image image = null;WeakReference<Image> ref = cache.get(filename);if (ref != null) {image = ref.get();}if (image == null) {image = loadImageFromDisk(filename);cache.put(filename, new WeakReference<>(image));}return image;}private Image loadImageFromDisk(String filename) {// load image from diskreturn null;}
}

在这个示例中,ImageLoader 类使用一个 HashMap 来缓存已加载到内存中的图片,每个图片对应一个弱引用。当需要加载图片时,首先从缓存中查找图片是否已经加载到内存中,如果是,则返回弱引用所引用的图片对象;否则,从磁盘中加载图片,并将其添加到缓存中,同时返回图片对象。

由于缓存中的每个图片对象都是弱引用,因此在内存不足时,垃圾回收器会自动回收这些图片对象所占用的内存空间,从而实现内存管理。

补充:ThreadLocal 中为什么需要使用弱引用?

ThreadLocal 使用弱引用是为了防止内存泄漏。由于 ThreadLocalMap 是存在 Thread 中的,如果使用强引用,一旦ThreadLocal 对象被回收,它在 ThreadLocalMap 中对应的 Entry 对象也不会被回收,这样就会导致 Entry 对象中的value对象长时间得不到回收,最终导致内存泄漏。
使用弱引用可以让 ThreadLocal 对象被回收后,Entry 对象中的 value 对象在下一次 ThreadLocalMap 的操作时被顺便回收,从而避免了内存泄漏的问题。

4、虚引用(PhantomReference 装饰对象)

也称为幽灵引用,如果一个对象只具有虚引用,那么这个对象就和没有引用一样,在任何时候都可能被垃圾回收器回收。虚引用通常用于跟踪对象被垃圾回收器回收的状态,可以通过 PhantomReference 类来创建虚引用。

顾明思义,就是形同虚设,与其他几种引用都不同,虚拟引用并不会决定对象的声明周期。

如果一个对象仅持有虚引用,那么他就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独实用也不能通过虚引用访问对象,虚引用必须和引用队列 ReferenceQueue 一起使用。

虚引用的主要作用就是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被 finalize() 以后,可以做某些操作。PhantomReference.get() 方法总是会返回 null,因此无法访问对应的引用对象。比如:在这个对象被垃圾收集器回收的时候收一个系统通知或者做进一步的处理。举个例子:

通过 -Xms10m -Xmx10m 命令把 JVM 堆内存修改成 10M 方便测试。

class MyObject {@Overrideprotected void finalize() throws Throwable {System.out.println(">>>>>>调用 finalize 清理资源...");}
}public class ReferenceDemo {public static void main(String[] args) throws InterruptedException {ReferenceQueue<MyObject> queue = new ReferenceQueue<>();PhantomReference<MyObject> softReference = new PhantomReference<>(new MyObject(), queue);List<byte[]> list = new ArrayList<>();new Thread(() -> {while (true) {try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}list.add(new byte[1000 *  1000  * 1]);System.out.println("softReference.get() = " + softReference.get());}}).start();new Thread(() -> {while (true) {if (queue.poll() != null) {System.out.println(">>>>>>引用队列有值啦...");}}}).start();Thread.sleep(8000);}
}

输出结果如下:

softReference.get() = null
softReference.get() = null
softReference.get() = null
softReference.get() = null
softReference.get() = null
softReference.get() = null
>>>>>>调用 finalize 清理资源...
softReference.get() = null
softReference.get() = null
>>>>>>引用队列有值啦...java.lang.ref.PhantomReference@5c4efc23>>>type=class java.lang.ref.PhantomReference
softReference.get() = nullException: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "Thread-0"
Java HotSpot(TM) 64-Bit Server VM warning: Exception java.lang.OutOfMemoryError occurred dispatching signal SIGINT to handler- the VM may need to be forcibly terminated

上述代码中,通过 PhantomReference 创建出虚引用,并且传入 ReferenceQueue 队列。线程1不断地在堆内存中申请空间,每次 1M 大小,因为虚引用包裹的对象,通过 softReference.get() 都是返回 null。线程1一直这样添加,直到内存不足时,虚引用对象会被放到 ReferenceQueue 队列中,ReferenceQueue 队列可以帮助我们在虚引用对象被回收时及时得到通知,从而进行必要的清理工作。其实就是表示这个虚引用在死之前想要留一些遗言,人类就可以监控到这个引用队列看到谁在死之前还有遗言,去帮他实现一下。

补充:

虚引用(Phantom Reference)是 Java 引用类型中最弱的一种,虚引用对象被GC回收的时候会放入一个由 ReferenceQueue管理的队列中。

虚引用的一个常见用途是管理 DirectByteBuffer 对象,它可以让我们对 DirectByteBuffer 对象的回收时间进行监控,一旦 DirectByteBuffer 对象被GC回收,就可以通知我们进行必要的资源释放操作,比如释放内存映射文件等。

以下是一个使用虚引用管理DirectByteBuffer的简单示例代码:

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.nio.ByteBuffer;public class DirectByteBufferTest {private static final int BUFFER_SIZE = 1024 * 1024;private static final int MAX_BUFFERS = 10;private static final ByteBuffer[] buffers = new ByteBuffer[MAX_BUFFERS];private static final ReferenceQueue<ByteBuffer> queue = new ReferenceQueue<>();static {for (int i = 0; i < MAX_BUFFERS; i++) {buffers[i] = ByteBuffer.allocateDirect(BUFFER_SIZE);new PhantomReference<>(buffers[i], queue);}}public static void main(String[] args) throws InterruptedException {for (int i = 0; i < MAX_BUFFERS; i++) {ByteBuffer buffer = buffers[i];System.out.println("buffer " + i + ": " + buffer);buffers[i] = null;buffer = null;}System.gc();Thread.sleep(1000);Reference<? extends ByteBuffer> ref;while ((ref = queue.poll()) != null) {ByteBuffer buffer = ref.get();System.out.println("buffer " + buffer + " is released");// Release resources here}}
}

该示例程序通过创建10个 DirectByteBuffer 对象并将它们的虚引用对象放入队列中来管理这些 DirectByteBuffer 对象。在程序运行的过程中,首先会打印出10个 DirectByteBuffer 对象的地址,然后将它们的引用全部置为 null,这样它们就可以被GC回收。程序接着调用 System.gc() 来触发一次垃圾回收,然后调用 Thread.sleep() 让程序睡眠1秒钟等待垃圾回收完成。最后程序从 ReferenceQueue 中取出所有被回收的 DirectByteBuffer 对象的虚引用,进行必要的资源释放操作。

JVM 学习(2)—简单理解Java 四大引用(强、软、弱、虚)相关推荐

  1. JVM的四种引用:强,软,弱,虚(与gc有关)

    JVM的四种引用 强 软 弱 虚 强 gc时不回收 软 软引用对象在gc时,在内存溢出前,会回收; 弱 弱引用对象在gc时,不论内存使用情况都会回收; 虚 虚引用对象在gc后,会发送一条通知给 Ref ...

  2. Java四大引用(强、软、弱、虚)

    目录 强引用 软引用 弱引用 虚引用 强引用 也是我们平时用得最多的, new 一个对象就是强引用,例如 Object obj = new Object(); 当JVM的内存空间不足时,宁愿抛出Out ...

  3. 什么是java四大引用?

    相信现在有很多人正在学习java编程语言,其中java的四大引用相信很多同学都不是非常熟悉,那么什么是java四大引用?来看看下面的详细介绍. 什么是java四大引用?是JDK1.2版本开始引入,把对 ...

  4. 稀疏表示和字典学习的简单理解

    稀疏表示和字典学习的简单理解 特征分类 稀疏表示 字典学习 特征分类 相关特征:对当前有用的属性 冗余特征:所包含的信息有时能从其他特征中推演出来.如若某个冗余特征恰好对应了学习任务所需"中 ...

  5. 简单直白教你理解Java中四大引用强引用,软引用,弱引用,虚引用

    我属于自学型的,所以知识不够系统,只能是一边儿工作一边查漏补缺,在此要对那些写技术文章的人由衷的说句谢谢,谢谢各位大神们的分享 ONE,强引用(StrongReference) 概念介绍: 在此说明一 ...

  6. JVM学习笔记(一):Java内存区域

    由于Java程序是交由JVM执行的,所以我们在谈Java内存区域划分的时候事实上是指JVM内存区域划分.在讨论JVM内存区域划分之前,先来看一下Java程序具体执行的过程: 首先Java源代码文件(. ...

  7. Java的四大引用强、软、弱、虚

    一.什么是引用 首先要明白什么是一个引用呢?Object o = new Object()这就是一个引用了,一个变量指向new出来的对象,这个变量就叫一个引用,引用这个东西,在java里面分4种,普通 ...

  8. Java四大引用详解:强引用、软引用、弱引用、虚引用

    Java引用 从JDK 1.2版本开始,对象的引用被划分为4种级别,从而使程序能更加灵活地控制对象的生命周期,这4种级别由高到低依次为:强引用.软引用.弱引用和虚引用. 强引用 强引用是最普遍的引用, ...

  9. GuavaCache学习笔记二:Java四大引用类型回顾

    前言 上一篇已经讲了,如何自己实现一个LRU算法.但是那种只是最基本的实现了LRU的剔除策略,并不能在生产中去使用.因为Guava Cache中使用的是SoftReference去做的value实现, ...

最新文章

  1. c++ mysql 取出数据,c++从数据库的表中读取数据
  2. Asp.net 内置对象
  3. django1.4.5配置静态文件(img,css,js)访问
  4. i-usb-storer android,i usb storer
  5. python读取文件with open_python 文件读写操作open和with的用法
  6. python 中的 for-else 和 while-else 语句
  7. 深入浅出Docker(三):Docker开源之路
  8. Android 6.0 超级简单的权限申请 (Permission)
  9. 支付宝App采用华为方舟编译器几乎秒开?支付宝回应:华为好棒,加油
  10. CCF NOI1072 爬楼梯
  11. spring boot 相关快捷内置类和配置
  12. ELK logstash grok匹配失败存另外的es表
  13. python图像拼接_图像拼接_图像拼接算法_python图像拼接 - 云+社区 - 腾讯云
  14. dorado关于下拉框的的onSelect()方法
  15. centos漏洞系列(三):Google Android libnl权限提升漏洞
  16. 简易图片打像素标签工具
  17. HTML5触摸事件(多点、单点触控)
  18. Rails启动项一些参数的调整
  19. 【HBase】HBase入门详解(二)
  20. 矩阵相关操作和矩阵快速幂

热门文章

  1. 千锋深圳校区相亲会 双蛋之夕不再孤单
  2. Halcon识别金属上的雕刻字符
  3. NPDP知识推送-第六章市场研究(4)
  4. MER:1.8万字带你系统了解宏组学实验与分析(高通量测序应用于病原体和害虫诊断——综述与实用性建议)...
  5. 3U VPX T2080通信处理板卡
  6. 计算机无法连接到手机热点,电脑搜不到手机的热点是为什么_电脑无法发现手机热点的处理方法...
  7. 机器学习常用专业术语(英汉)
  8. 【英语六级】【仔细阅读】(1)
  9. kali中如何更新python_怎么在线更新kali linux
  10. C语言计算三角形的面积