Java引用类型

Java中有两种类型,值类型和引用类型。其中引用类型有点类似指针,它保存着对象的地址。通过引用,可以对堆中的对象进行操作。

《深入理解Java虚拟机 JVM高级特性与最佳实践》一书3.2.3节中对引用有如下描述:

在JDK 1.2之前,Java中的引用的定义很传统:如果reference类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用……
在JDK 1.2之后,Java对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种,这四种引用强度依次逐渐减弱。

强引用(StrongReference)

《Java程序性能优化》一书3.4节中描述,强引用具备以下特点:

  • 强引用可以直接访问目标对象
  • 强引用所指向的对象在任何时候都不会被系统回收。JVM宁愿抛出OOM异常,也不回收所指向的对象。
  • 强引用可能导致内存泄露。

软引用(SoftReference)

用于描述一些还有用,但是非必需的对象。OOM异常之前,会将这些对象列入回收范围之中进行第二次回收。如果回收之后还是没有足够内存,就抛出OOM异常。

弱引用(WeakReference)

弱引用关联的对象只能生存到下一次垃圾收集发生之前。也就是,垃圾回收器线程开始扫描所管辖的内存区域时,一旦发现只具备弱引用的对象,不管当前内存空间是否足够,都会回收它的内存。但是由于垃圾回收器线程优先级很低,因此不一定会很快发现只具有弱引用的对象。

这篇博客还提到:

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

虚引用(PhantomReference)

虚引用是最弱的一种引用(也称为幽灵引用),虚引用不会决定对象的生命周期。

虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。

阅读源码注释

查看Android源码里面JDK的目录中的java\lang\ref,我们可以看到有如下几个文件:

  • Reference.java
  • WeakReference.java
  • SoftReference.java
  • PhantomReference.java
  • FinalizerReference.java
  • ReferenceQueue.java

首先查看Reference.java文件,注释描述:

Provides an abstract class which describes behavior common to all reference
objects. It is not possible to create immediate subclasses of
Reference in addition to the ones provided by this package. It is
also not desirable to do so, since references require very close cooperation
with the system’s garbage collector. The existing, specialized reference
classes should be used instead.

大概意思是说:

提供了一个描述所有引用对象的共同行为的抽象类。不可能创建引用的直接子类,也不希望这么做。因为引用需要与系统垃圾回收器紧密合作。应该使用现有的,指定的引用类。

Reference类似一个泛型的抽象类,里面定义了引用的对象,引用队列等,并定义了引用队列入队等方法。

SoftReference继承自Reference,我们也可以看看里面的注释(原文比较长,不贴出来了),大致意思:

实现软引用,它是三种引用(不包括强引用)中least-weak的引用。一旦垃圾收集器扫描到一个对象obj是softly-reachable,下面的情况就有可能立即或者以后出现:

  • 一个引用的集合ref是确定的,ref包含下面的元素
    • 所有指向obj的软引用。
    • 所有指向可以从它到obj是strongly reachable的软引用。
  • 所有在ref中的引用自动被清除;
  • 在同一时间或者未来的某一时间,所有在ref中的引用将入队到他们相关的引用队列中,如果有的话。

系统可能延迟清除或者入队软引用,但是所有指向软可到对象的软引用在运行时抛出OutOfMemoryError前被清除。
软引用适合那些从外部不再继续引用,应该删除掉,且需要内存的时候的情况下用作缓存。
SoftReference和WeakReference的不同的是什么时候决定清除或者入队引用:

  • SoftReference应该尽可能晚的清除或者入队,就是说,VM快要内存耗尽的时候。
  • WeakReference可能在当清除或者入队只要weakly-referenced。

查看Android官网上对SoftReference的介绍,其中提到这样一个问题:

Avoid Soft References for Caching

In practice, soft references are inefficient for caching. The runtime doesn’t have enough information on which references to clear and which to keep. Most fatally, it doesn’t know what to do when given the choice between clearing a soft reference and growing the heap.
The lack of information on the value to your application of each reference limits the usefulness of soft references. References that are cleared too early cause unnecessary work; those that are cleared too late waste memory.

Most applications should use an android.util.LruCache instead of soft references. LruCache has an effective eviction policy and lets the user tune how much memory is allotted.

意思大致是:

软引用在实践中用来做缓存是低效的,因为运行时并没有关于哪个引用要清除或者保留的足够的信息,最要命的是当要选择清除软引用还是增长栈的时候,它不知道怎么做。
你的程序的引用的信息的缺失导致软引用用处的局限性。过早清除的引用会导致无用功,太晚清除又会浪费内存。
大多数程序应该使用android.util.LruCache来替代软引用,LruCache有着更高效的回收策略,并让用户协调要分配多少内存。

对于WeakReference:

WeakReference是三种引用中中间的一个。一旦垃圾收集器决定某个对象obj是weakly-reachable,下面就会发生:

  • A set ref of references is determined.ref contains the following elements:
    • All weak references pointing to obj.
    • All weak references pointing to objects from which obj is either strongly or softly reachable.
  • All objects formerly being referenced by ref become eligible for finalization.
  • At some future point, all references in ref will be enqueued with their corresponding reference queues, if any.

对于PhantomReference:

PhantomReference是三种引用中最弱的引用。一旦垃圾收集器确定一个对象obj是phantom-reachable,它就要入队。
在相关的队列里面,它的referent(引用的对象)是不明确的。这就是说,虚引用的引用队列必须明确的在程序代码中进行理。因此,不与任何引用队列相关联的虚引用就不会产生任何影响。
虚引用适合实现对象在垃圾回收之前的必要的cleanup操作。它有时候比Object的finalize()方法更灵活。

我们看源码的时候,会注意到PhantomReference的get()方法:

@Override
public T get() {
    return null;
}

这里方法永远返回null。这里就会产生疑惑,返回null的引用有何用?其实这就是它的特点,所以虚引用唯一的用处就是跟踪referent何时被enqueue到ReferenceQueue中。因为 PhantomReference是在finalize方法执行后回收的,所以可以避免在finalize方法执行后再创建强引用导致无法回收的问题。

另外,参考这篇文章,了解GC、 Reference 与 ReferenceQueue 的交互:
>
A、 GC无法删除存在强引用的对象的内存。
B、 GC发现一个只有软引用的对象内存,那么:
① SoftReference对象的 referent 域被设置为 null ,从而使该对象不再引用 heap 对象。
② SoftReference引用过的 heap 对象被声明为 finalizable 。
③ 当 heap 对象的 finalize() 方法被运行而且该对象占用的内存被释放, SoftReference 对象就被添加到它的 ReferenceQueue (如果后者存在的话)。
C、 GC发现一个只有弱引用的对象内存,那么:
① WeakReference对象的 referent 域被设置为 null , 从而使该对象不再引用heap 对象。
② WeakReference引用过的 heap 对象被声明为 finalizable 。
③ 当heap 对象的 finalize() 方法被运行而且该对象占用的内存被释放时, WeakReference 对象就被添加到它的 ReferenceQueue (如果后者存在的话)。
D、 GC发现一个只有虚引用的对象内存,那么:
① PhantomReference引用过的 heap 对象被声明为 finalizable 。
② PhantomReference在堆对象被释放之前就被添加到它的 ReferenceQueue 。

ReferenceQueue有什么用处呢?
前面提到,弱引用和虚引用都可以配合ReferenceQueue使用,这样的话,就可以使用ReferenceQueue来判断是否有弱引用和虚引用的对象要被回收,以便及时做出如数据清理等额外的处理。

最后,我们再借一张图,总结4种引用的对比:
图片来自:http://developer.51cto.com/art/200906/128189.htm
这里写图片描述

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐