你知道Java四种引用吗?以及他们是如何回收的?

Java四种引用

  • 强引用:有GC ROOT直接引用的对象,当没有再被GCROOT引用的时候,可以被垃圾回收
  • 软引用:当被引用的对象只被软引用时,当发生垃圾回收且内存空间匮乏时会删除软引用所引用的对象,可以通过引用队列来释放引用自身
  • 弱引用:当被引用的对象只被弱引用时,当发生垃圾回收时就会被回收弱引用所引用的对象,可以通过引用队列来释放引用自身
  • 虚引用:一般在ByteBuffer开辟直接内存联系,当ByteBuffer本身被垃圾回收之后,但是对于其开辟的内存空间无法被回收(这是因为直接内存不属于JVM所管理的内存,而属于操作系统内存)。因此虚引用会记录该直接内存地址,并且在虚引用的内部会有一个Cleaner对象,当ByteBuffer被垃圾回收之后,虚引用一定会进入引用队列,而该队列会有一个ReferenceHandler线程去调用队列中对象的clean方法去释放直接内存。

引用队列

引用队列 】用于将软引用和弱引用、虚引用的引用者放入该队列,当他们所引用的对象被垃圾回收之后,Reference引用关系就会进入该队列等待被线程回收。其中软引用和弱引用可以不配合引用队列使用,但是虚引用一定需要配合该队列。

实例程序

前提:为了测出效果,这里把堆内存的大小修改为20M,在启动参数中添加-Xmx20m;

- 强引用
public static void main(String[] args) {
    List list = new ArrayList<>();    
    for (int i = 0; i < 4; i++) {
        list.add(new byte[1024 * 1024 * 5]);    
    }
}
运行结果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

说明:
这种是强引用的案例,我们日常工作中大多数对象都是被强引用。即GC Roots所能直接或间接的引用到的对象。这部分在GC时不会被回收,因为这些byte[]始终被生命周期更长的list引用着。因此当byte[]太多之后势必造成堆OOM。

- 软引用
public static void soft() {
    List> lists = new ArrayList<>();    
    for (int i = 0; i < 5; i++) {
        lists.add(new SoftReference<>(new byte[1024 * 1024 * 4]));
        System.out.println("==============================");
        for (SoftReference list : lists) {
             System.out.println(list.get()); 
        }
    }
}
运行结果:
==============================
[B@682a0b20
==============================
[B@682a0b20
[B@3d075dc0
==============================
[B@682a0b20
[B@3d075dc0
[B@214c265e
==============================
[B@682a0b20
[B@3d075dc0
[B@214c265e
[B@448139f0
==============================
null
null
null
null
[B@7cca494b

说明:软引用在发生GC时,且堆内存空间不足时,就会释放被软引用所引用的对象。

- 弱引用
public static void weak() {
    List> lists = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        lists.add(new WeakReference<>(new byte[1024*1024*4]));
        System.out.println("========================");
        for (WeakReference list : lists) {
            System.out.println(list.get());
        }
   }
}
运行结果:
========================
[B@682a0b20
========================
[B@682a0b20
[B@3d075dc0
========================
[B@682a0b20
[B@3d075dc0
[B@214c265e
========================
null
null
null
[B@448139f0
========================
null
null
null
[B@448139f0
[B@7cca494b

说明:软引用在GC时,不管内存是否够用都会删除只被软引用所引用的对象。

- 虚引用
public static void fake() throws IOException {
    Runnable runnable = () -> System.out.println("虚引用");
    Object obj = new Object();
    Cleaner.create(obj, runnable);
    System.in.read();
    obj = null;
    System.gc();
    System.in.read();
}
运行结果:
虚引用

说明:如上是一个虚引用的小Demo,也是ByteBuffer开辟直接内存的一个回收缩影。上面的核心类为Cleaner类,该类继承PhantomReference类,所以其本质就是一个虚引用。上述的create方法就是使用虚引用来引用obj对象,当obj对象被垃圾释放时,引用关系会被放入到ReferenceQueue队列中,之后会有一个线程ReferenceHandler线程一直从Queue中取出Reference对象并调用clean方法,而clean方法就会调用create传入的第二个线程参数。
源码如下:

  • 调用Cleaner.create方法时,会构造Cleaner对象,并将传入的Runnable赋值给全局变量thunk:


    1485_0.png
  • clean方法被调用时,会调用thunk线程的run执行用户自定义的释放动作:


    1487_0.png
  • Reference中有一个线程以最低优先级一直在运行:


    1489_0.png
  • 如下是上述图片中的tryHandlePending方法,方法比较长,这里只截取一段,其中c是Cleaner的实例:


    1491_0.png
  • 这里你可能有个疑问,就是那么多的Cleaner对象,他怎么知道调用哪个Cleaner对象的clean方法呢?


    1493_0.png

    看来这一切都是这个pending的变量才是源头,看一下这个变量的定义吧,注释说的还是很清晰的:


    1495_0.png

回顾引用队列

在了解了上述背景之后,你可能想问。在软、弱引用。在被引用的对象在相应的场景被释放之后。那么这一层Reference引用关系是如何被清理的呢?仔细看一下下一段源码:


1497_0.png

可以看到,ReferenceHandler线程会把垃圾回收器带来的Reference对象置空以下一次被垃圾回收(这个discovered就是Reference实例)。

你可能感兴趣的:(你知道Java四种引用吗?以及他们是如何回收的?)