Java中的四种引用

参考文章:译文:理解Java中的弱引用

Java中的对象和引用

我们先看《Java编程思想》中的一句话:
“每种编程语言都有自己的数据处理方式。有些时候,程序员必须注意将要处理的数据是什么类型。你是直接操纵元素,还是用某种基于特殊语法的间接表示(例如C/C++里的指针)来操作对象。所有这些在 Java 里都得到了简化,一切都被视为对象。因此,我们可采用一种统一的语法。尽管将一切都“看作”对象,但操纵的标识符实际是指向一个对象的“引用”(reference)。”
再结合下面一行代码就能分清两者区别:

    Person person;
    person = new Person("Lisi");

实际上在java中new是用来在heap(堆)上创建对象的,那person就是一个引用。

四种引用

在介绍四种引用前我们先看Object类中的finalize()方法,API里面是这样描述的:

Called by the garbage collector on an object when garbage collection determines that there are no more references to the object.

简单理解就是垃圾回收期在回收对象时会调用该方法,我们先来尝试一下,代码如下:

Model model = new Model();
Car car = new Car(model);
car = null;
model = null;
System.gc();

Car和Model类:

public class Car {
    private Model model;

    public Car(Model model) {
        this.model = model;
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("car 被回收了:" + this);
    }
}
public class Model {
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("Model 被回收了:" + this);
    }
}

结果:

car 被回收了:reference.Car@1ce22c97
Model 被回收了:reference.Model@1225c17b

我们再改一下代码:

Model model = new Model();
Car car = new Car(model);
model = null;
System.gc();

我们将model置为null,那这个时候我们在第一行代码创建的Model对象会被回收吗?答案是不会,因为在第二行代码里我们创建的Car对象仍然持有对model的引用,如果Car对象一直不被回收,那Model对象也不会被回收,如果Car对象是一个生命周期很长的对象,那就会产生内存泄露了。那我们怎样避免这种情况了,这就要开始进入Java四种引用的话题了。

强引用

只要引用存在,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。

    Object obj = new Object();

如果想中断强引用和某个对象之间的关联,可以将引用赋值为null。

软引用

如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;
如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
软引用可用来实现内存敏感的高速缓存,比如网页缓存、图片缓存等。使用软引用能防止内存泄露,增强程序的健壮性。
SoftReference的特点是它的一个实例保存对一个Java对象的软引用, 该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。
也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。
另外,一旦垃圾线程回收该Java对象之 后,get()方法将返回null。
举个例子:

    Object obj = new Object();
     SoftReference<Object> softRef = new SoftReference<Object>(obj);

此时对于Object这个对象,有两个引用路径,一个是SoftReference对象的软引用,一个是变量obj的强引用。
我们可以通过代码来结束Object实例的强引用。

    obj = null;

此时Object对象成为软引用对象,如果垃圾收集线程进行内存垃圾收集,并不会因为有一个SoftReference对该对象的引用而始终保留该对象。在内存充足的情况下,即使调用gc,该对象也不会被回收。

        Object obj = new Object();
        SoftReference<Object> softRef = new SoftReference<Object>(obj);
        System.out.println("强引用:" + obj);
        System.out.println("软引用:" + softRef.get());
        obj = null;
        System.gc();
        System.out.println("=======垃圾回收=======");
        System.out.println("强引用:" + obj);
        System.out.println("软引用:" + softRef.get());

结果:

强引用:java.lang.Object@6ff3c5b5
弱引用:java.lang.Object@6ff3c5b5
=======垃圾回收=======
强引用:null
弱引用:java.lang.Object@6ff3c5b5

弱引用

弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。

       Object obj = new Object();
        WeakReference<Object> weakRef = new WeakReference<Object>(obj);
        System.out.println("强引用:" + obj);
        System.out.println("弱引用:" + weakRef.get());
        obj = null;
        System.gc();
        System.out.println("=======垃圾回收=======");
        System.out.println("强引用:" + obj);
        System.out.println("弱引用:" + weakRef.get());

结果:

强引用:java.lang.Object@6ff3c5b5
弱引用:java.lang.Object@6ff3c5b5
=======垃圾回收=======
强引用:null
弱引用:null

和软引用的结果对比,可以明显的看到在垃圾回收后软引用对象被回收了,所以在实际中使用软引用对象需要做好空判断。

ReferenceQueue

作为一个Java对象,WeakReference对象除了具有保存软引用的特殊性之外,也具有Java对象的一般性。所以,当软可及对象被回收之后,虽然这个WeakReference对象的get()方法返回null,但这个WeakReference对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量WeakReference对象带来的内存泄漏。在java.lang.ref包里还提供了ReferenceQueue。如果在创建WeakReference对象的时候,使用了一个ReferenceQueue对象作为参数提供给WeakReference的构造方法,如:

        Object obj = new Object();
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        WeakReference<Object> weakRef = new WeakReference<Object>(obj, queue);
        System.out.println("强引用:" + obj);
        System.out.println("弱引用:" + weakRef.get());
        obj = null;
        System.gc();
        System.out.println("=======垃圾回收=======");
        System.out.println("强引用:" + obj);
        System.out.println("弱引用:" + weakRef.get());
        System.out.println("引用序列:" + queue.poll());

结果:

强引用:java.lang.Object@6ff3c5b5
弱引用:java.lang.Object@6ff3c5b5
=======垃圾回收=======
强引用:null
弱引用:null
引用序列:java.lang.ref.WeakReference@3764951d

那么当这个WeakReference所软引用的Object对象被垃圾收集器回收的同时,weakRef所强引用的WeakReference对象被列入ReferenceQueue。也就是说,ReferenceQueue中保存的对象是Reference对象,而且是已经失去了它所弱引用的对象的Reference对象。另外从ReferenceQueue这个名字也可以看出,它是一个队列,当我们调用它的poll()方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个Reference对象。
在任何时候,我们都可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。利用这个方法,我们可以检查哪个WeakReference所弱引用的对象已经被回收。于是我们可以把这些失去所软引用的对象的WeakReference对象清除掉。常用的方式为:

        Reference ref;
        while ((ref = queue.poll()) != null) {
            System.out.println(ref);
            ref = null;
        }

软引用也同样适用。

虚引用

与软引用,弱引用不同,虚引用指向的对象十分脆弱,我们不可以通过get方法来得到其指向的对象。它的唯一作用就是当其指向的对象被回收之后,自己被加入到引用队列,用作记录该引用指向的对象已被销毁。

当弱引用的指向对象变得弱引用可到达,该弱引用就会加入到引用队列。这一操作发生在对象析构或者垃圾回收真正发生之前。理论上,这个即将被回收的对象是可以在一个不符合规范的析构方法里面重新复活。但是这个弱引用会销毁。虚引用只有在其指向的对象从内存中移除掉之后才会加入到引用队列中。其get方法一直返回null就是为了阻止其指向的几乎被销毁的对象重新复活。

虚引用使用场景主要由两个。它允许你知道具体何时其引用的对象从内存中移除。而实际上这是Java中唯一的方式。这一点尤其表现在处理类似图片的大文件的情况。当你确定一个图片数据对象应该被回收,你可以利用虚引用来判断这个对象回收之后在继续加载下一张图片。这样可以尽可能地避免可怕的内存溢出错误。

第二点,虚引用可以避免很多析构时的问题。finalize方法可以通过创建强引用指向快被销毁的对象来让这些对象重新复活。然而,一个重写了finalize方法的对象如果想要被回收掉,需要经历两个单独的垃圾收集周期。在第一个周期中,某个对象被标记为可回收,进而才能进行析构。但是因为在析构过程中仍有微弱的可能这个对象会重新复活。这种情况下,在这个对象真实销毁之前,垃圾回收器需要再次运行。因为析构可能并不是很及时,所以在调用对象的析构之前,需要经历数量不确定的垃圾收集周期。这就意味着在真正清理掉这个对象的时候可能发生很大的延迟。这就是为什么当大部分堆被标记成垃圾时还是会出现烦人的内存溢出错误。

使用虚引用,上述情况将引刃而解,当一个虚引用加入到引用队列时,你绝对没有办法得到一个销毁了的对象。因为这时候,对象已经从内存中销毁了。因为虚引用不能被用作让其指向的对象重生,所以其对象会在垃圾回收的第一个周期就将被清理掉。

显而易见,finalize方法不建议被重写。因为虚引用明显地安全高效,去掉finalize方法可以虚拟机变得明显简单。当然你也可以去重写这个方法来实现更多。这完全看个人选择。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值