Java对象的四种引用类型

Java对象有四种应用类型,分别是强引用(Strong Reference),软引用(Soft Reference),弱引用(Weak Reference),虚引用(Phantom Reference),不同的引用类型,表现出了对象不同的可达性状态和垃圾收集的影响。
以上说的四种引用按GC回收的可能性是从小到大排列的,而他的可达性状态切换大致如下图所示:


image.png

强引用

Java中默认强引用,强引用只是通过System.gc()不能回收,只有对象不再引用才回收,或者手动将对象引用赋为null才能被回收;
强引用是最常见的引用:例如StringBuffer buffer = new StringBuffer();
创建了一个StringBuffer类的对象,并用一个变量buffer存储对这个对象的引用。这就是个强引用。
变量持有的是这个对象的引用。
通常,引用是一个对象的存储地址。Java不像C或者C++一样,Java没有取地址符号&,也没有解引用符号或者->。
引用不同于指针,引用不能与整形进行互相转换,也不能进行增减操作。强引用是和垃圾回收机制相关的。

一般的,如果一个对象可以通过一系列的强引用引用到,那么就说明它是不会被垃圾回收机制(Garbage Collection)回收的。因为垃圾回收器是不会回收你正在使用的对象的。

软引用

软引用通过System.gc()也不能回收,只用内存不够时才回收;
软引用可以用来实现一些内存敏感的缓存(Soft references are for implementing memory-sensitive caches),只要内存空间足够,对象就会保持不被回收。反之,当宿主进程的内存空间不足时,对象就会被GC回收。
JVM会在抛出OutOfMemoryError之前,清理软引用指向的对象,所以SoftReference意味着:hold on until you can’t。

弱引用

当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候你就可以使用弱引用。或者说弱引用可以用来实现一些非强制性的映射关系,如果试图获取时对象还在,就直接使用,不在了就重新实例化。常见的应用比如规范化映射(WeakHashMap)。

弱引用调System.gc()就回收了。这种引用不会在对象的垃圾回收判断中产生任何附加的影响。

虚引用

虚引用也叫做幻象引用,他比较特殊,它的get方法总是返回null,所以你得不到它引用的对象实例。虚引用不会对对象的生存时间有任何影响,只是能在这个对象被回收时收到一个系统通知。
虚引用不调gc(),new完后就直接回收了。
最后我们看一下四种引用与gc的关系,如下图所示:


image.png

什么时候强引用会太强了?

有时候应用会使用一些不能被继承的类,比如一个final的类,或者一个工厂方法返回的接口,并不知道有多少具体实现。

而我们想给这个类增加一个字段,比如给每一个对象一个序列号,于是我们用了HashMap,把这个类的对象作为key,一个序列号作为value。

这时候我们就必须100%确定地知道一个特定对象的序列号什么时候不再需要(比如对象的生命周期已经结束,就不再需要它的序列号属性),这样我们就可以从map中移除它的entry。

如果我们在应当移除引用的时候没有移除,垃圾回收将一直不会回收这个对象,引起内存泄露。而如果我们过早地移除了我们还在使用的对象的引用,又会发现自己丢失了信息。这些都是C/C++程序员经常会遇到的问题。而我们用的是Java,我们还要考虑这些,岂不是闹复杂了?

强引用另一个常见的问题是缓存问题。比方说,图像的缓存。图像缓存应当阻止我们重复载入图像。
所以图像缓存保存有内存中已有的所有图像的引用,如果使用通常的强引用,强引用本身会使得图像一直存留在内存中,这样就使得程序员像上面一样,必须自己决定什么时候移除缓存中的引用,这样对象才能被垃圾回收机制回收。这样你就又放弃了让GC自己管理垃圾回收的机制,而开始手动地管理内存。

引用对象类

Java的引用对象类在包java.lang.ref下,他定义了除强引用外其他的三种引用类型(也即是Reference类的三个子类):

SoftReference

WeakReference

PhantomReference
以上三种类型的引用定义了三种不同层次的可达性级别,依次从强到弱,越弱表示对垃圾回收器的限制越少,对象越容易被回收。

一个引用对象(reference object)(即以上三种引用类型的对象)封装了一个对其他对象的引用(称作referent)。

引用对象提供了对referent的clean和get操作,但是不提供set操作。引用对象本身可以像其他一般的对象一样被检查和操纵。

WeakReference应用实例

除了强引用以外,弱引用在这些引用类型中算使用比较频繁的了,以下我们通过一段示例代码对弱引用的使用做个示例:

public static class Sub{
    @Override
    public String toString(){
        return "sub";
    }
}
public static class Parent extends Sub{
    public Parent(Sub sub){
        super();
    }
    public Parent(){
        
    }
    @Override
    public String toString(){
        return "parent";
    }
}
public static void main(String[] args) {
    Sub sub = new Sub();
    ReferenceQueue rq = new ReferenceQueue();
    WeakReference wr = new WeakReference(new Parent(sub),rq);
//        Parent wr = new Parent(sub);
    sub = null;
    System.gc();
    System.out.println(wr.get());//null 因为被gc了
    System.out.println((WeakReference)rq.poll());//java.lang.ref.WeakReference@15db9742
}

有一个类Parent,有一个子类Sub,现在如果我们这样写:
Sub sub = new Sub();
Parent parent = new Parent(sub);
那这就是强引用,当我们写到
sub = null;
System.gc();

这样sub再去访问sub自然是null,但是sub指向的堆空间的对象是null吗,parent指向的堆空间的对象是null吗?不是的,想要parent也被gc怎么办呢?我们在sub=null;后面加上:parent = null,sub指向的堆空间和parent指向的堆空间就会因为不可达而被gc。

为了说明弱引用的使用方法,先创建一个引用队列ReferenceQueue:
ReferenceQueue rq = new ReferenceQueue rq();
再创建一个弱引用:
WeakReference wr = new WeakReference(new Parent(sub),rq);
当sub=null时,弱引用就会被gc,然后出现在引用队列,所以rq.poll()就可以获取到已经被gc的弱引用类型。
那为什么这里wr会被gc呢?因为当sub=null时,sub之前指向的堆里的对象就只被一个弱引用wr依赖了,然而弱引用wr没有被引用,因此wr被gc,被wr依赖的sub之前指向的堆对象也被gc。这就是弱引用的好处,垃圾回收器会帮你来决定引用的对象何时回收并且将对象从内存移除。

你可能感兴趣的:(Java对象的四种引用类型)