java 其他引用类型

122 阅读5分钟

概述

我们知道,在Java中,如果变量是一个对象类型的,那么它实际上存放的是对象的引用,但是如果是一个基本类型,那么存放的就是基本类型的值。实际上我们平时代码中类似于Object o = new Object()这样的的引用类型,细分之后可以称为强引用。

如果方法中存在这样的强引用类型,现在需要回收强引用所指向的对象,那么要么此方法运行结束,要么引用连接断开,否则被引用的对象是无法被判定为可回收的,因为我们说不定后面还要使用它。

所以,当JVM内存空间不足时,JVM宁愿抛出OutOfMemoryError使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。

除了强引用之外,Java也为我们提供了三种额外的引用类型。

软引用

软引用不像强引用那样不可回收,当 JVM 认为内存不足时,会去试图回收软引用指向的对象,即JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。当然,如果内存充足,那么是不会轻易被回收的。

我们可以通过以下方式来创建一个软引用:

image.png

// 强引用写法:Object obj = new Object();
// 软引用写法:
SoftReference<Object> reference = new SoftReference<>(new Object());
//使用get方法就可以获取到软引用所指向的对象了
System.out.println(reference.get());

可以看到软引用还存在一个带队列的构造方法,软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

这里我们来进行一个测试,首先我们需要设定一下参数,来限制最大堆内存为10M,并且打印GC日志:

public static void main(String[] args) {

    // 引用队列
    ReferenceQueue<Object> queue = new ReferenceQueue<>();
    // 软引用
    SoftReference<Object> reference = new SoftReference<>(new Object(), queue);
    // 获取软引用对象
    System.out.println(reference);

    try{
        List<Byte[]> list = new ArrayList<>();

        while (true) {
            // 每次1m
            list.add(new Byte[1024*1024]);
        }

    } catch (Throwable t){
        System.out.println("发生了内存溢出!"+t.getMessage());
        System.out.println("软引用对象:"+reference.get());
        System.out.println(queue.poll());
    }

}

添加虚拟机参数

-XX:+PrintGCDetails -Xms10m -Xmx10m 

image.png

运行结果如下:

java.lang.ref.SoftReference@1b6d3586
[GC (Allocation Failure) [PSYoungGen: 1744K->488K(2560K)] 5840K->4784K(9728K), 0.0010459 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 488K->496K(2560K)] 4784K->4848K(9728K), 0.0008931 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 496K->0K(2560K)] [ParOldGen: 4352K->4701K(7168K)] 4848K->4701K(9728K), [Metaspace: 3251K->3251K(1056768K)], 0.0042986 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 4701K->4701K(9728K), 0.0003501 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 4701K->4683K(7168K)] 4701K->4683K(9728K), [Metaspace: 3251K->3251K(1056768K)], 0.0038947 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
发生了内存溢出!Java heap space
软引用对象:null
java.lang.ref.SoftReference@1b6d3586
Heap
 PSYoungGen      total 2560K, used 85K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 4% used [0x00000000ffd00000,0x00000000ffd15510,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 7168K, used 4683K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 65% used [0x00000000ff600000,0x00000000ffa92fc8,0x00000000ffd00000)
 Metaspace       used 3274K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 354K, capacity 388K, committed 512K, reserved 1048576K

进程已结束,退出代码0

可以看到,当内存不足时,软引用所指向的对象被回收了,所以get()方法得到的结果为null,并且软引用对象本身被丢进了队列中。

弱引用

弱引用比软引用的生命周期还要短,在进行垃圾回收时,不管当前内存空间是否充足,都会回收它的内存。

我们可以像这样创建一个弱引用:

WeakReference<Object> reference = new WeakReference<>(new Object());

System.out.println(reference.get());

使用方法和软引用是差不多的,但是如果我们在这之前手动进行一次GC:

SoftReference<Object> softReference = new SoftReference<>(new Object());
WeakReference<Object> weakReference = new WeakReference<>(new Object());

//手动GC
System.gc();

System.out.println("软引用对象:"+softReference.get());
System.out.println("弱引用对象:"+weakReference.get());

运行结果如下:

软引用对象:java.lang.Object@1b6d3586
弱引用对象:null

WeakHashMap正是一种类似于弱引用的HashMap类,如果Map中的Key没有其他引用那么此Map会自动丢弃此键值对。

Integer a = new Integer(1);

WeakHashMap<Integer, String> weakHashMap = new WeakHashMap<>();
weakHashMap.put(a, "test");

System.out.println(weakHashMap);

a = null;

System.gc();

System.out.println(weakHashMap);

输出结果如下:

{1=test}
{}

可以看到,当变量a的引用断开后,这时只有WeakHashMap本身对此对象存在引用,所以在GC之后,这个键值对就自动被舍弃了。所以说这玩意,就挺适合拿去做缓存的。

虚引用(鬼引用)

虚引用相当于没有引用,随时都有可能会被回收。

看看它的源码,非常简单:

public class PhantomReference<T> extends Reference<T> {

    /**
     * Returns this reference object's referent.  Because the referent of a
     * phantom reference is always inaccessible, this method always returns
     * <code>null</code>.
     *
     * @return  <code>null</code>
     */
    public T get() {
        return null;
    }

    /**
     * Creates a new phantom reference that refers to the given object and
     * is registered with the given queue.
     *
     * <p> It is possible to create a phantom reference with a <tt>null</tt>
     * queue, but such a reference is completely useless: Its <tt>get</tt>
     * method will always return null and, since it does not have a queue, it
     * will never be enqueued.
     *
     * @param referent the object the new phantom reference will refer to
     * @param q the queue with which the reference is to be registered,
     *          or <tt>null</tt> if registration is not required
     */
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }

}

也就是说我们无论调用多少次get()方法得到的永远都是null,因为虚引用本身就不算是个引用,相当于这个对象不存在任何引用,并且只能使用带队列的构造方法,以便对象被回收时接到通知。

价值:当gc 回收一个对象时,如果gc 发现此对象还有一个虚引用,就会将虚引用放入到引用队列中,之后(当虚引用出队后)再去回收改对象。

GC->如果有虚引用->虚引用入队->虚引用出队-> 回收该对象。

public static void main(String[] args) throws InterruptedException {


    Object my = new Object();

    ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();

    PhantomReference<Object> phantomReference =
            new PhantomReference<>(my,referenceQueue);

    // 打印my 对象
    System.out.println("my = " + my);

    // 打印虚引用对象
    System.out.println("虚引用对象 = " + phantomReference);

    my = null;
    // gc 之前,队列为空
    System.out.println("gc之前获取队列值为:" +  referenceQueue.poll());

    // 开始gc,这个不一定立马就gc
    System.gc();
    // 增加延时,确保gc
    Thread.sleep(20);

    // gc 后,被回收对象入队
    System.out.println("gc之后获取队列值为:" + referenceQueue.poll());


}

输出如下:

my = java.lang.Object@1b6d3586
虚引用对象 = java.lang.ref.PhantomReference@4554617c
gc之前获取队列值为:null
gc之后获取队列值为:java.lang.ref.PhantomReference@4554617c

总结

最后,Java中4种引用的级别由高到低依次为:  强引用  >  软引用  >  弱引用  >  虚引用