文章

Java强引用、软引用、软引用和虚引用

Java强引用、软引用、软引用和虚引用

强引用、软引用、软引用和虚引用

强引用、软引用、软引用和虚引用概述

从 JDK1.2 版本开始,把对象的引用分为四种级别,从而使程序更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用软引用弱引用虚引用

SoftReference, WeakReference 以及 PhantomReference 的构造函数都可以接收一个 ReferenceQueue 对象。当 SoftReference 以及 WeakReference 被清空的同时,也就是 Java 垃圾回收器准备对它们所指向的对象进行回收时,调用对象的 finalize() 方法之前,它们自身会被加入到这个 ReferenceQueue 对象中,此时可以通过 ReferenceQueue 的 poll() 方法取到它们。而 PhantomReference 只有当 Java 垃圾回收器对其所指向的对象真正进行回收时,会将其加入到这个 ReferenceQueue 对象中,这样就可以追综对象的销毁情况。

四种引用类型的表现以及在垃圾回收器回收清理内存时的表现:

  1. 强引用 (FinalReference), 这是最常用的引用类型。 JVM 系统采用 Finalizer 来管理每个强引用对象,并将其被标记要清理时加入 ReferenceQueue, 并逐一调用该对象的 finalize() 方法;不管内存紧张也罢,不足也罢,gc 永不回收强引用的对象,即使 jvm 出现 (内存溢出错误)OutOfMemoryError,使程序停止,也不会回收对象来提高内存代码(如果想中断或者回收强引用对象,可以显式地将引用赋值为 null,这样的话 JVM 就会在合适的时间,进行垃圾回收)
  2. 软引用 (SoftReference), 引用类型表现为当内存接近满负荷且该对象没有其他强引用, 或对象由 SoftReference.get() 方法的调用没有发生一段时间后 , 垃圾回收器将会清理该对象。在运行对象的 finalize 方法前 , 会将软引用对象加入 ReferenceQueue 中去。
  3. 弱引用 (WeakReference), 引用类型表现为当系统垃圾回收器开始回收时,没有其他强引用该对象 , 则立即会回收该对象的引用。 与软引用一样 , 弱引用也会在运行对象的 finalize 方法之前将弱引用对象加入 ReferenceQueue。
  4. 虚引用 (PhantomReference), 这是一个最虚幻的引用类型 。 无论是从哪里都无法再次返回被虚引用所引用的对象 。 虚引用在系统垃圾回收器开始回收对象时 , 将直接调用 finalize() 方法 , 但不会立即将其加入回收队列 。 只有在真正对象被 GC 清除时 , 才会将其加入 Reference 队列中去。

5tim0

强引用

1
String tag = new String("T");

此处的 tag 引用就称之为强引用。而强引用有以下特征:

  1. 强引用可以直接访问目标对象
  2. 强引用所指向的对象在任何时候都不会被系统回收
  3. 强引用可能导致内存泄漏

我们要讨论的这三种 Reference 较之于强引用而言都属于 “ 弱引用 “,也就是他们所引用的对象只要没有强引用,就会根据条件被 JVM 的垃圾回收器所回收,它们被回收的时机以及用法各不相同

软引用 SoftReference

SoftReference 所指向的对象,当没有强引用指向它时,会在内存中停留一段的时间,垃圾回收器会根据 JVM 内存的使用情况(内存的紧缺程度)以及 SoftReference 的 get() 方法的调用情况来决定是否对其进行回收

  • 软引用有以下特征:
  1. 软引用使用 get() 方法取得对象的强引用从而访问目标对象
  2. 软引用所指向的对象按照 JVM 的使用情况(Heap 内存是否临近阈值)来决定是否回收(内存空间足够可能不会回收)。
  3. 软引用可以避免 Heap 内存不足所导致的异常。

当垃圾回收器决定对其回收时,会先清空它的 SoftReference,也就是说 SoftReference 的 get() 方法将会返回 null,然后再调用对象的 finalize() 方法,并在下一轮 GC 中对其真正进行回收。

1
2
3
4
5
Object obj = new Object();
ReferenceQueue queue = new ReferenceQueue();
SoftReference reference = new SoftReference(obj, queue);
// 强引用对象滞空,保留软引用
obj = null;

弱引用 WeakReference

WeakReference 是弱于 SoftReference 的引用类型。弱引用的特性和基本与软引用相似,区别就在于弱引用所指向的对象只要进行系统垃圾回收,不管内存使用情况如何,永远对其进行回收(get() 方法返回 null)。

弱引用, 当一个对象仅仅被 weak reference(弱引用)指向, 而没有任何其他强引用指向该对象的时候, 如果这时 GC 运行, 那么这个对象就会被回收,不论当前的内存空间是否足够,这个对象都会被回收。

弱引用有以下特征:

  1. 弱引用使用 get() 方法取得对象的强引用从而访问目标对象
  2. 一旦系统内存回收,无论内存是否紧张,弱引用指向的对象都会被回收 (前提是该对象没有强引用引用该对象)
  3. 弱引用也可以避免 Heap 内存不足所导致的异常
1
2
3
4
5
6
7
8
public class WeakReference<T> extends Reference<T> {
    public WeakReference(T referent) {
        super(referent);
    }
    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}
  1. referent 要被弱引用的对象实例
  2. q ReferenceQueue 参数,在对象被回收后,会把弱引用对象,也就是 WeakReference 对象或者其子类的对象 (即 reference 引用的对象),放入队列 ReferenceQueue 中,注意不是被弱引用的对象,被弱引用的对象已经被回收了。
1
2
3
4
5
Object obj = new Object();
ReferenceQueue queue = new ReferenceQueue();
WeakReference reference = new WeakReference(obj, queue);
// 强引用对象滞空,保留软引用
obj = null;

对于那种匿名内部类的 Callback,我们不能在方法内直接用弱引用包裹着,方法执行过程中形参是强引用了 Callback,但方法弹出栈后,这个强引用就没了,随时会被系统 GC 给回收掉,除非这个 callback 弄了一个类的实例变量给引用着。

LeakCanary 就是利用弱引用来实现的内存泄漏监听

WeakReference 测试案例

不使用 ReferenceQueue 的 WeakReference

  • Student 类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Student {
    private String name;
    public Student(String name) {
        this.name = name;
    }
    /**
     * 覆盖finalize,在回收的时候会执行。
     */
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println(name + "对象正在回收finalize");
    }
}
  • Person 类:
1
2
3
4
5
6
7
8
/**
 * 这里Person继承WeakReference,将Student作为弱引用。注意到时候回收的是Student,而不是Person
 */
public class Person extends WeakReference<Student> {
    public Person(Student referent) {
        super(referent);
    }
}
  • 测试代码 1:
1
2
3
4
5
6
7
8
9
public static void main(String[] args) throws InterruptedException {
    Person person = new Person(new Student("hacket"));
    // 通过WeakReference的get()方法获取Student
    System.out.println("Student:" + person.get());
    System.gc();
    // 休眠一下,在运行的时候加上虚拟机参数-XX:+PrintGCDetails,输出gc信息,确定gc发生了。
    Thread.sleep(5000);
    System.out.println("gc回收之后的Student:" + person.get());
}

输出:

1
2
3
Student:com.example.引用.Student@7852e922
hacket对象正在回收finalize
gc回收之后的Student:null

执行 gc 后,Student 确实被回收了

  • 测试代码 2:
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws InterruptedException {
    Student hacket = new Student("hacket");
    Person person = new Person(hacket);
    // 通过WeakReference的get()方法获取Student
    System.out.println("Student:" + person.get());
    System.gc();
    // 休眠一下,在运行的时候加上虚拟机参数-XX:+PrintGCDetails,输出gc信息,确定gc发生了。
    Thread.sleep(5000);
    System.out.println("gc回收之后的Student:" + person.get());
}

输出:

1
2
Student:com.example.引用.Student@7852e922
gc回收之后的Student:com.example.引用.Student@7852e922
  • 测试代码 3:
1
2
3
4
5
6
7
8
9
10
11
12
public class TestReference {
    static Student hacket = new Student("hacket");
    public static void main(String[] args) throws InterruptedException {
        Person person = new Person(hacket);
        // 通过WeakReference的get()方法获取Student
        System.out.println("Student:" + person.get());
        System.gc();
        // 休眠一下,在运行的时候加上虚拟机参数-XX:+PrintGCDetails,输出gc信息,确定gc发生了。
        Thread.sleep(5000);
        System.out.println("gc回收之后的Student:" + person.get());
    }
}

输出:

1
2
Student:com.example.引用.Student@7852e922
gc回收之后的Student:com.example.引用.Student@7852e922
  • 测试代码 3:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class TestWeakRef2 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countdownLatch = new CountDownLatch(1);
        request(new Callback() {
            @Override
            public void onSucceed(String data) {
                System.out.println("onSucceed -->> " + data);
                countdownLatch.countDown();
            }
            @Override
            public void onFailed(Throwable throwable) {
                System.out.println(throwable.getMessage());
                countdownLatch.countDown();
            }
        });
        System.gc(); // 这里加了GC,Callback就没有了强引用,就会被回收掉了。在这之前还没有弹出栈,还是强引用着Callback
        System.out.println("main await 前");
        countdownLatch.await();
        System.out.println("main await 后");
    }
    private static void request(Callback callback) {
        WeakReference<Callback> ref = new WeakReference<>(callback);
        System.out.println("execute前 callback:" + ref.get());
        execute(ref);
        System.out.println("execute后,gc前 callback:" + ref.get());
//        callback = null;
        System.out.println("execute后,gc后 callback:" + ref.get());
    }
    private static void execute(final WeakReference<Callback> ref) {
        System.out.println("execute before.:" + ref.get());
        Executors.newSingleThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("模拟请求,5秒后返回 " + ref.get());
                    Thread.sleep(5_000L);
                    Callback callback = ref.get();
                    if (callback != null) {
                        callback.onSucceed("请求完毕,返回数据 " + ref.get());
                    }
                    System.out.println("请求完毕,返回数据 " + ref.get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    if (ref.get() != null) {
                        Objects.requireNonNull(ref.get()).onFailed(e);
                    }
                }
            }
        });
        System.out.println("execute after." + ref.get());
    }
    public interface Callback {
        void onSucceed(String data);
        void onFailed(Throwable throwable);
    }
}

输出:

1
2
3
4
5
6
7
8
execute前 callback:com.example.引用.TestWeakRef2$1@7852e922
execute before.:com.example.引用.TestWeakRef2$1@7852e922
execute after.com.example.引用.TestWeakRef2$1@7852e922
execute后,gc前 callback:com.example.引用.TestWeakRef2$1@7852e922
execute后,gc后 callback:com.example.引用.TestWeakRef2$1@7852e922
模拟请求,5秒后返回 com.example.引用.TestWeakRef2$1@7852e922
main await 前
请求完毕,返回数据 null

ReferenceQueue 的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class TestWeakReference2 {

    public static void main(String[] args) throws Exception {
        ReferenceQueue<Student> referenceQueue = new ReferenceQueue<>();
        WeakReference<Student> weakReference1 = new WeakReference<>(new Student("张三"), referenceQueue);
        WeakReference<Student> weakReference2 = new WeakReference<>(new Student("李四"), referenceQueue);

        // 不会输出,因为没有回收被弱引用的对象,并不会加入队列中
        System.out.println("gc调用前的referenceQueue:" + referenceQueue.poll());
        System.out.println(weakReference1.get());
        System.out.println(weakReference2.get());

        System.out.println("=====调用gc=====");
        System.gc();

        Thread.sleep(5000);

        System.out.println("=====gc调用后=====");
        // 下面两个输出为null,表示对象被回收了
        System.out.println(weakReference1.get());
        System.out.println(weakReference2.get());

        // 输出结果,再次证明对象被回收了
        // 如果使用继承的方式就可以包含其他信息了
        Field queueLengthField = referenceQueue.getClass().getDeclaredField("queueLength");
        queueLengthField.setAccessible(true);
        long queueLength = (long) queueLengthField.get(referenceQueue);
        System.out.println("appleReferenceQueue(大小:" + queueLength + ")中:" + referenceQueue.poll());
    }
}

输出:

1
2
3
4
5
6
7
8
9
10
gc调用前的referenceQueue:null
com.example.引用.Student@7852e922
com.example.引用.Student@4e25154f
=====调用gc=====
李四对象正在回收finalize
张三对象正在回收finalize
=====gc调用后=====
null
null
appleReferenceQueue(大小:2)中:java.lang.ref.WeakReference@5c647e05

虚引用 PhantomReference

PhantomReference 是所有 “ 弱引用 “ 中最弱的引用类型。不同于软引用和弱引用,虚引用无法通过 get() 方法来取得目标对象的强引用从而使用目标对象,观察源码可以发现 get() 被重写为永远返回 null。

虚引用主要被用来跟踪对象被垃圾回收的状态,通过查看引用队列中是否包含对象所对应的虚引用来判断它是否 即将被垃圾回收,从而采取行动。它并不被期待用来取得目标对象的引用,而目标对象被回收前,它的引用会被放入一个 ReferenceQueue 对象中,从而达到跟踪对象垃圾回收的作用。

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {   
    ReferenceQueue<String> refQueue = new ReferenceQueue<String>();   
    PhantomReference<String> referent = new PhantomReference<String>(new String("T"), refQueue);   
    System.out.println(referent.get());// null   
    
    System.gc();   
    System.runFinalization();   
    
    System.out.println(refQueue.poll() == referent); //true   
}

值得注意的是,对于引用回收方面,虚引用类似强引用不会自动根据内存情况自动对目标对象回收,Client 需要自己对其进行处理以防 Heap 内存不足异常。

虚引用有以下特征:

  1. 虚引用永远无法使用 get() 方法取得对象的强引用从而访问目标对象
  2. 虚引用所指向的对象在被系统内存回收前,虚引用自身会被放入 ReferenceQueue 对象中从而跟踪对象垃圾回收
  3. 虚引用不会根据内存情况自动回收目标对象。

虚引用 PhantomReference, 在 <<深入理解Java虚拟机>> 一文中,它唯一的目的就是为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知

引用队列 ReferenceQueue

ReferenceQueue 配合 Reference 的子类等使用,当引用对象所指向的对象被垃圾回收后,该 Reference 则被追加到 ReferenceQueue 的末尾

1
2
3
public class ReferenceQueue<T> {
    private volatile Reference<? extends T> head = null;
}
  1. 存元素 enqueue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
    synchronized (lock) {
        // Check that since getting the lock this reference hasn't already been
        // enqueued (and even then removed)
        ReferenceQueue<?> queue = r.queue; // 取出引用中的队列
        if ((queue == NULL) || (queue == ENQUEUED)) {
            return false;
        }
        assert queue == this;
        r.queue = ENQUEUED;
        r.next = (head == null) ? r : head; // head为null队列中还没对象,next指向自己;head不为null时,说明已经有一个引用前面了,next指向head
        head = r; // 队列的head保存为当前r
        queueLength++;
        if (r instanceof FinalReference) {
            sun.misc.VM.addFinalRefCount(1);
        }
        lock.notifyAll();
        return true;
    }
}
  1. 取出元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Reference<? extends T> poll() {
    if (head == null)
        return null;
    synchronized (lock) {
        return reallyPoll();
    }
}
private Reference<? extends T> reallyPoll() {       /* Must hold lock */
    Reference<? extends T> r = head; 
    if (r != null) {
        head = (r.next == r) ? // 如果r.next = r,说明队列中只有一个数据,那么取出来后,head置为null;否则取第一个head
            null :
            r.next; // Unchecked due to the next field having a raw type in Reference
        r.queue = NULL;
        r.next = r;
        queueLength--;
        if (r instanceof FinalReference) {
            sun.misc.VM.addFinalRefCount(-1);
        }
        return r;
    }
    return null;
}

总结

  1. 对于强引用,平时在编写代码时会经常使用
  2. 被软引用关联的对象只有在内存不足时才会被回收,而被弱引用关联的对象在 JVM 进行垃圾回收时总会被回收(前提都是没用强引用的对象)
    mydm0
  3. 垃圾回收时对比

2njsi

用途

WeakReference 用途

ThreadLocal.ThreadLocalMap.Entry 的 key

ThreadLocal 中的 ThreadLocalMap.Entry 就是个 WeakReference,把 ThreadLocal 作为 key 传递给了 WeakReference,也就是说 ThreadLocal 存储在 ThreadLocalMap 中是以弱引用存储的

WeakHashMap

WeakHashMap 的 key 是用的 WeakReference,在没有其它强引用的情况下,下一次 GC 时才会被垃圾回收;和 ThreadLocalMap 类似

Ref

本文由作者按照 CC BY 4.0 进行授权