Java中的强引用、软引用、弱引用、虚引用详解

一 概念简介

在JVM内存中,一个对象拥有不同的引用类型,那这个对象在进行垃圾回收时会被执行不同的操作,从而影响这个对象的生命周期

1.1 强引用

强引用是使用最普遍的引用,我们平时代码中定义的引用都是强引用。如果一个对象具有强引用,垃圾回收器绝不会回收它,即使是内存空间不足时,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

1.2 软引用

如果一个对象只具有软引用,那就类似于可有可物的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用一般用于实现缓存对象的操作
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中

1.3 弱引用

弱引用是比软引用强度更低一种引用方式。如果一个对象只具有弱引用,他的生命周期更加短暂,那么当垃圾回收器线程在扫描他所管理的内存区域时,不管内存空间是否足够,都会将这个对象回收。不过垃圾回收器线程优先级比较低,因此只有弱引用的对象不一定会很快的被回收。

弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

1.4 虚引用

虚引用又叫做幽灵引用,就想他的名字一样,虚引用是形同虚设的,也就是虚引用相当于没有引用。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用于跟踪一个对象被垃圾回收的过程。

虚引用必须和引用队列(ReferenceQueue)联合使用(因为虚引用无法获得引用对象,不配合引用队列使用的话将毫无意义)。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

二 涉及的类

在java.lang.ref包中提供了三个类:SoftReference类、WeakReference类和PhantomReference类,他们分别对应软引用、弱引用和虚引用。这是三个类的父类都是Reference类,他们的方法基本上都一样。

2.1 软引用(SoftReference)

2.1.1 构造器

// 1.生成一个指向refrent对象的软引用
public SoftReference(T referent)
// 2.使用引用队列
public SoftReference(T referent, ReferenceQueue<? super T> q)

2.1.2 如何获得引用对象

// 通过get方法得到软引用所指向的对象
public T get() 

如果这个引用指向的对象已经被回收,那么get( )将返回null

2.2 弱引用(WeakReference)

2.2.1 构造器

// 1.生成一个指向refrent对象的弱引用
public WeakReference(T referent)
// 2.使用引用队列
public WeakReference(T referent, ReferenceQueue<? super T> q)

2.2.2 如何获得引用对象

// 通过get方法得到弱引用所指向的对象
public T get() 

如果这个引用指向的对象已经被回收,那么get( )将返回null

2.3 虚引用(PhantomReference)

2.3.1 构造器

// 使用引用队列(虚引用必须配合引用队列使用,否则没有意义)
public WeakReference(T referent, ReferenceQueue<? super T> q)

2.3.2 如何获得引用对象

// 通过get方法得到null?
public T get() {
        return null;
    }

虚引用不会获得对象的引用,所以重载了Reference的get方法,直接返回null。

2.4 引用队列

public class ReferenceQueue<T> {
    public Reference<? extends T> poll()     //将队列顶端的元素抛出
    public Reference<? extends T> remove()   // 将队列元素删除
}

三 测试demo

3.1 测试程序

package com.example.demo.test;

import java.lang.ref.*;
import java.util.LinkedList;

public class ReferenceTest {

    private static ReferenceQueue<Source> rq = new ReferenceQueue<Source>();

    public static void checkQueue() {
        Reference<? extends Source> ref = null;
        while ((ref = rq.poll()) != null) {
            if (ref != null) {
                if(ref instanceof SourceSoftReference) {
                    System.out.println("In queue: " + ((SourceSoftReference) (ref)).id);
                } else if(ref instanceof SourceWeakReference) {
                    System.out.println("In queue: " + ((SourceWeakReference) (ref)).id);
                } else {
                    System.out.println("In queue: " + ((SourcePhantomReference) (ref)).id);
                }

            }
        }
    }

    public static void main(String args[]) {
//        soft();
//        weak();
        phantom();
    }
    public static void soft() {
        int size = 3;
        LinkedList<SourceSoftReference> softList = new LinkedList<SourceSoftReference>();
        for (int i = 0; i < size; i++) {
            softList.add(new SourceSoftReference(new Source("Soft " + i), rq));
            System.out.println("Just created soft: " + softList.getLast());

        }

        System.gc();
        try { // 下面休息几分钟,让上面的垃圾回收线程运行完成
            Thread.currentThread().sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < size; i++) {
            System.out.println("output: " + softList.get(i).get());

        }
        checkQueue();
    }
    public static void weak() {
        int size = 3;
        LinkedList<SourceWeakReference> weakList = new LinkedList<SourceWeakReference>();
        for (int i = 0; i < size; i++) {
            weakList.add(new SourceWeakReference(new Source("Weak " + i), rq));
            System.out.println("Just created weak: " + weakList.getLast());

        }

        System.gc();
        try { // 下面休息几分钟,让上面的垃圾回收线程运行完成
            Thread.currentThread().sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < size; i++) {
            System.out.println("output: " + weakList.get(i).get());

        }
        checkQueue();
    }

    public static void phantom() {
        int size = 3;
        LinkedList<SourcePhantomReference> phantomList = new LinkedList<SourcePhantomReference>();
        for (int i = 0; i < size; i++) {
            phantomList.add(new SourcePhantomReference(new Source("Phantom " + i), rq));
            System.out.println("Just created phantom: " + phantomList.getLast());

        }

        System.gc();
        try { // 下面休息几分钟,让上面的垃圾回收线程运行完成
            Thread.currentThread().sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//        System.gc();
//        try { // 下面休息几分钟,让上面的垃圾回收线程运行完成
//            Thread.currentThread().sleep(2000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        for (int i = 0; i < size; i++) {
            System.out.println("output: " + phantomList.get(i).get());

        }
        checkQueue();
    }

}

class Source {
    public String id;

    public Source(String id) {
        this.id = id;
    }

    protected void finalize() {
        System.out.println("Finalizing Source " + id);
    }
}
class SourceSoftReference extends SoftReference<Source> {
    public String id;

    public SourceSoftReference(Source Source, ReferenceQueue<Source> rq) {
        super(Source, rq);
        this.id = Source.id;
    }
}

class SourceWeakReference extends WeakReference<Source> {
    public String id;

    public SourceWeakReference(Source Source, ReferenceQueue<Source> rq) {
        super(Source, rq);
        this.id = Source.id;
    }
}

class SourcePhantomReference extends PhantomReference<Source> {
    public String id;

    public SourcePhantomReference(Source Source, ReferenceQueue<Source> rq) {
        super(Source, rq);
        this.id = Source.id;
    }

}

3.2 分析

3.2.1 运行soft()方法

输出:

Just created soft: com.example.demo.test.SourceSoftReference@eed1f14
Just created soft: com.example.demo.test.SourceSoftReference@7229724f
Just created soft: com.example.demo.test.SourceSoftReference@4c873330
output: com.example.demo.test.Source@119d7047
output: com.example.demo.test.Source@776ec8df
output: com.example.demo.test.Source@4eec7777

软引用仍然能获得对象的引用,且引用队列为空,说明内存充足,对象未被回收

3.2.2 运行weak()方法

Just created weak: com.example.demo.test.SourceWeakReference@eed1f14
Just created weak: com.example.demo.test.SourceWeakReference@7229724f
Just created weak: com.example.demo.test.SourceWeakReference@4c873330
Finalizing Source Weak 2
Finalizing Source Weak 1
Finalizing Source Weak 0
output: null
output: null
output: null
In queue: Weak 1
In queue: Weak 2
In queue: Weak 0

弱引用无法获得对象的引用,且引用队列中有3个弱引用,说明弱引用指向的对象已经被回收

3.2.2 运行phantom()方法

Just created phantom: com.example.demo.test.SourcePhantomReference@eed1f14
Just created phantom: com.example.demo.test.SourcePhantomReference@7229724f
Just created phantom: com.example.demo.test.SourcePhantomReference@4c873330
Finalizing Source Phantom 2
Finalizing Source Phantom 1
Finalizing Source Phantom 0
output: null
output: null
output: null

虚引用无法获得对象的引用属于正常现象,但是引用队列为空不正常。这是为什么呢?
问题出在了source对象的finalize方法上,注释掉finalize方法,再运行结果如下:

Just created phantom: com.example.demo.test.SourcePhantomReference@eed1f14
Just created phantom: com.example.demo.test.SourcePhantomReference@7229724f
Just created phantom: com.example.demo.test.SourcePhantomReference@4c873330
output: null
output: null
output: null
In queue: Phantom 2
In queue: Phantom 0
In queue: Phantom 1

这是为什么???????

原因简单叙述如下:

在垃圾回收时:
弱引用:一旦探测对象只有弱引用,就会被插入到ReferenceQueue
虚引用:只有对象确实被GC销毁,才会被插入到ReferenceQueue
也就是说上例中虚引用指向的对象并没有被销毁

为什么执行了 System.gc()后对象没有被销毁??

因为对象重载了finalize方法,需要执行2轮gc才能回收。

如何修改

把phantom()方法的注释解开,执行2次GC后,输出如我们想象中一样

Just created phantom: com.example.demo.test.SourcePhantomReference@eed1f14
Just created phantom: com.example.demo.test.SourcePhantomReference@7229724f
Just created phantom: com.example.demo.test.SourcePhantomReference@4c873330
Finalizing Source Phantom 0
Finalizing Source Phantom 2
Finalizing Source Phantom 1
output: null
output: null
output: null
In queue: Phantom 1
In queue: Phantom 0
In queue: Phantom 2

三 总结

不知道大家发现没有,通过虚引用的demo,我们不难发现,弱引用的demo中的Source对象也未必被回收掉了(一轮gc肯定不会被回收掉)。
所以,慎重重写finalize方法,它会延缓对象被销毁的速度。

猜你喜欢

转载自blog.csdn.net/weixin_37891479/article/details/79897175
今日推荐