强引用、软引用、弱引用、虚引用

Java中的引用分为四种:强软弱虚。

finalize 方法

在 Object对象中有个 finalize 方法,当gc回收对象时,会去执行 finalize 销毁对象,这里我们重写此方法,并打印日志,来用来判定对象是否被gc回收:

public class O {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("o finalize...");
    }
}

强引用 StrongReference

// 在Java中, 默认创建的对象的引用都是强引用。拥有强引用的对象始终不会被gc回收(即使发生OOM),除非他们不再拥有强引用

O o = new O();
o = null; // 这样 new O()对象不再拥有任何引用
System.gc(); // 执行gc,会打印o finalize...,说明对象被回收

System.in.read();

软引用 SoftReference

软引用,当Java程序内存不足,即OOM即将发生时,堆内存中虚引用指向的对象都会被gc二次回收,以防发生OOM异常。

被软引用指向的对象通常是一些有用但是不是必须的对象,比如内存缓存。

创建虚引用需要借助SoftReference对象:

// 注意:运行时需要指定对打堆空间为20M  -Xmx20M
SoftReference<byte[]> m = new SoftReference<>(new byte[1024*1024*10]); // 创建一个10M的虚引用对象
System.out.println(m.get()); // 有值
System.gc(); // gc
TimeUnit.SECONDS.sleep(1);
System.out.println(m.get()); // 有值,没有被gc回收

byte[] b = new byte[1024*1024*11]; // 创建一个11M大小的byte数组,此时总共21M,heap装不下,将会进行垃圾回收。如果回收之后仍然不够,则会回收软引用对象
System.out.println(m.get()); // null

弱引用 WeakReference

弱引用在遭遇gc时,会被直接回收。他通常用于修饰集合元素,比如WeakHashMap、ThreadLocal等。

eakReference<O> m = new WeakReference<>(new O());
System.out.println(m.get()); // 有值
System.gc(); // full gc
System.out.println(m.get()); // 无值,并打印 o finalize...

虚引用 PhantomReference

一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。虚引用和弱引用对关联对象的回收都不会产生影响,如果只有虚引用活着弱引用关联着对象,那么这个对象就会被回收。它们的不同之处在于弱引用的get方法,虚引用的get方法始终返回null,弱引用可以使用ReferenceQueue,虚引用必须配合ReferenceQueue使用。

jdk中直接内存的回收就用到虚引用,由于jvm自动内存管理的范围是堆内存,而直接内存是在堆内存之外,所以直接内存的分配和回收都是有Unsafe类去操作,java在申请一块直接内存之后,会在堆内存分配一个对象保存这个堆外内存的引用,这个对象被垃圾收集器管理,一旦这个对象被回收,相应的用户线程会收到通知并对直接内存进行清理工作。

事实上,虚引用有一个很重要的用途就是用来做堆外内存的释放,DirectByteBuffer就是通过虚引用来实现堆外内存的释放的。

private static final List<Object> LIST = new LinkedList<>();
private static final ReferenceQueue<O> QUEUE = new ReferenceQueue<>();

public static void main(String[] args) throws Exception {
    // 创建一个虚引用需要一个对象和一个队列
    PhantomReference<O> phantomReference = new PhantomReference<>(new O(), QUEUE);

    // 线程1负责不断的向List中添加数据,每次添加1M数据
    new Thread(() -> {
        while (true) {
            LIST.add(new byte[1024 * 1024]);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                Thread.currentThread().interrupt();
            }
            System.out.println(phantomReference.get());
        }
    }).start();
    // 线程2则不断的从QUEUE中取数据,当虚引用对象 phantomReference 被回收时,它会被存放在QUEUE中
    new Thread(() -> {
        while (true) {
            Reference<? extends O> poll = QUEUE.poll();
            if (poll != null) {
                System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll); // poll对象就是 phantomReference 对象
            }
        }
    }).start();
    Thread.sleep(500);
}

下面来看打印结果:

null // 虚引用对象使用get永远取不到对象
null
...
null
o finalize... // gc一旦执行,虚引用对象就会被立即回收
null
...
null
--- 虚引用对象被jvm回收了 ---- java.lang.ref.PhantomReference@2a1759c2 // 虚引用对象被jvm回首时,会在QUEUE中发现他
null
null
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space // 不断向LIST添加数据,导致OOM
    at com.yangsx95.notes.Test.lambda$main$0(Test.java:38)
    at com.yangsx95.notes.Test$$Lambda$1/2114694065.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:745)

最后更新于