Java 引用类型(Reference)

81 阅读4分钟

一、概述

Java的垃圾回收(GC)是虚拟机自动管理的,

Java内存管理分为内存分配和内存回收,都不需要程序员负责,垃圾回收的机制主要是看对象是否有引用指向它。

Java对象的引用类型包括:强引用,软引用,弱引用,虚引用,Java提供这四种引用类型主要有两个目的:

  • 可以让程序员通过代码的方式决定某些对象的生命周期。
  • 更有利于JVM进行垃圾回收。

二、四种引用类型

1. 强引用(StrongReference)
强引用是指创建一个对象把它赋值给一个引用,引用是存在于JVM的栈中的。
强引用的特性是只要有强引用存在,被引用的对象就不会被垃圾回收。
例如:

@Test
fun testStrongReferences(){
    var obj: Any? = Any()
}

2. 软引用(SoftReference)
软引用的对象只有在内存不足的情况下,被引用的对象才会被回收。
SoftReference的定义:

public class SoftReference<T> extends Reference<T>

SoftReference继承自Reference。有两种构造方法:

public SoftReference(T referent) 
public SoftReference(T referent, ReferenceQueue<? super T> q) 
  • 第一个参数就是软引用的对象。
  • 第二个参数叫做ReferenceQueue,是用来存储封装的待回收Reference对象的,ReferenceQueue中的对象是由Reference类中的ReferenceHandler内部类进行处理的。 例如:
@Test
fun testSoftReference() {
    var obj: Any? = Any()
    val soft = SoftReference(obj)
    obj = null
    println("${soft.get()}")
    System.gc()
    println("${soft.get()}")
}
输出结果:
    java.lang.Object@3b764bce
    java.lang.Object@3b764bce

可以发现在内存充足的情况下,SoftReference引用的对象是不会被回收的。

3. 弱引用(WeakReference) WeakReferencee引用的对象只要垃圾回收执行,就会被回收,而不管是否内存不足。

WeakReference继承自Reference。有两种构造方法:

public WeakReference(T referent)public WeakReference(T referent, ReferenceQueue<? super T> q)

含义和SoftReference一致。

例如:

@Test
fun testWeakReference() {
    var obj: Any? = Any()
    val weak = WeakReference(obj)
    obj = null
    println(" ${weak.get()}")
    System.gc()
    println(" ${weak.get()}")
}
输出结果:
    java.lang.Object@3b764bce
    null

我们看到gc过后,弱引用的对象就会被回收掉了。

4. 虚引用(PhantomReference)

作用是跟踪垃圾回收器收集对象的活动,在GC的过程中,如果发现有PhantomReference,GC则会将引用放到ReferenceQueue中,由程序员自己处理,当程序员调用ReferenceQueue.poll()方法,将引用ReferenceQueue移除之后,Reference对象会变成Inactive状态,意味着被引用的对象可以被回收了。

和SoftReference,WeakReference不同,PhantomReference只有一个构造函数,必须传入ReferenceQueue:

public PhantomReference(T referent, ReferenceQueue<? super T> q)

例如:

@Test
fun testPhantomReference(){
    var obj: Any? = Any()
    val queue = ReferenceQueue<Any>()
    val phantom = PhantomReference<Any>(obj,queue)
    obj = null
    println(" ${phantom.get()}")
    System.gc()
    println(" ${queue.poll()}")
}
输出结果:
    null
    java.lang.ref.PhantomReference@3b764bce

可以看到get()值是null,而GC过后,poll是有值的。

因为PhantomReference引用的是需要被垃圾回收的对象,所以在类的定义中,get一直返回null:

public T get() { return null; }

三、Reference和ReferenceQueue

讲完上面的四种引用,接下来我们谈一下他们的父类Reference和ReferenceQueue的作用。

Reference是一个抽象类,每个Reference都有一个指向的对象,在Reference中有5个非常重要的属性:referent,next,discovered,pending,queue。

每个Reference都可以看成是一个节点,多个Reference通过next,discovered和pending这三个属性进行关联。

先用一张图来对Reference有个整体的概念:

reference.png

referent就是Reference实际引用的对象。

通过next属性,可以构建ReferenceQueue。

通过discovered属性,可以构建Discovered List。

通过pending属性,可以构建Pending List。

1. 关于状态说明

因为Reference有两个构造函数,一个带ReferenceQueue,一个不带。

对于带ReferenceQueue的Reference,GC会把要回收对象的Reference放到ReferenceQueue中,后续该Reference需要程序员自己处理(调用poll方法)。

不带ReferenceQueue的Reference,GC自己处理,待回收的对象Reference状态会变成Inactive。

  • 创建好的Reference,就进入active状态。

  • active状态下,如果引用对象的可到达状态发送变化就会转变成Inactive或Pending状态。

  • 在到达Inactive状态的Reference状态不能被改变,会等待GC回收。

  • 在Pending状态的等待进入Queue,Reference内部有个ReferenceHandler,会调用enqueue方法,将Pending状态的对象放入到Queue中。

  • 进入Queue的对象,其状态就变成了Enqueued。

  • Enqueued状态的对象,如果调用poll方法从ReferenceQueue拿出,则该Reference的状态就变成了Inactive,等待GC的回收。

这就是Reference的一个完整的生命周期。

2. 一个Queue,三个List

有了上面四个状态的概念,我们接下来讲一个Queue,三个List:ReferenceQueue,discovered List和pending List。

  • ReferenceQueue在讲状态的时候已经讲过了,它本质是由Reference中的next连接而成的。用来存储GC待回收的对象。

  • pending List就是待入ReferenceQueue的list。

  • discovered List有点特别,在Pending状态时候,discovered List就等于pending List。

  • 在Active状态的时候,discovered List实际上维持的是一个引用链。通过这个引用链,我们可以获得引用的链式结构,当某个Reference状态不再是Active状态时,需要将这个Reference从discovered List中删除。