`

Java 2 引用类使用指南

阅读更多

学习如何有效地使用 SoftReference、WeakReference 和 PhantomReference

 

Java 2 平台引入了 java.lang.ref 包,其中包括的类可以让您引用对象,而不将它们留在内存中。这些类还提供了与垃圾收集器(garbage collector)之间有限的交互。Peter Haggar 在本文中分析了 SoftReference 、 WeakReference 和 PhantomReference 类的功能和行为,并就这些类的使用给出了一些编程风格上的建议。

当在 Java 2 平台中首次引入 java.lang.ref 包(其中包含 SoftReference 、 WeakReference 和 PhantomReference 类)时,它的实用性显然被过分夸大了。它包含的类可能是有用的,但这些类具有的某些局限性会使它们显得不是很有吸引力,而且其应用程序也将特别局限于解决一类特定的问题。

垃圾收集概述

引用类的主要功能就是能够引用仍可以被垃圾收集器回收的对象。在引入引用类之前,我们只能使用强引用(strong reference)。举例来说,下面一行代码显示的就是强引用 obj :

Object obj = new Object();
 


obj 这个引用将引用堆中存储的一个对象。只要 obj 引用还存在,垃圾收集器就永远不会释放用来容纳该对象的存储空间。

当 obj 超出范围或被显式地指定为 null 时,垃圾收集器就认为没有对这个对象的其它引用,也就可以收集它了。然而您还需要注意一个重要的细节:仅凭对象可以被收集并不意味着垃圾收集器的一次指定运行就能够回收它。由于各种垃圾收集算法有所不同,某些算法会更频繁地分析生存期较短的对象,而不是较老、生存期较长的对象。因此,一个可供收集的对象可能永远也不会被回收。如果程序在垃圾收集器释放对象之前结束,这种情况就可能会出现。因此,概括地说,您永远无法保证可供收集的对象总是会被垃圾收集器收集。

这些信息对于您分析引用类是很重要的。由于垃圾收集有着特定的性质,所以引用类实际上可能没有您原来想像的那么有用,尽管如此,它们对于特定问题来说还是很有用的类。软引用(soft reference)、弱引用(weak reference)和虚引用(phantom reference)对象提供了三种不同的方式来在不妨碍收集的情况下引用堆对象。每种引用对象都有不同的行为,而且它们与垃圾收集器之间的交互也有所不同。此外,这几个新的引用类都表现出比典型的强引用“更弱”的引用形式。而且,内存中的一个对象可以被多个引用(可以是强引用、软引用、弱引用或虚引用)引用。在进一步往下讨论之前,让我们来看看一些术语:

强可及对象(strongly reachable):可以通过强引用访问的对象。
软可及对象(softly reachable):不是强可及对象,并且能够通过软引用访问的对象。
弱可及对象(weakly reachable):不是强可及对象也不是软可及对象,并且能够通过弱引用访问的对象。
虚可及对象(phantomly reachable):不是强可及对象、软可及对象,也不是弱可及对象,已经结束的,可以通过虚引用访问的对象。
清除:将引用对象的 referent 域设置为 null ,并将引用类在堆中引用的对象声明为 可结束的。
SoftReference 类

SoftReference 类的一个典型用途就是用于内存敏感的高速缓存。 SoftReference 的原理是:在保持对对象的引用时保证在 JVM 报告内存不足情况之前将清除所有的软引用。关键之处在于,垃圾收集器在运行时可能会(也可能不会)释放软可及对象。对象是否被释放取决于垃圾收集器的算法以及垃圾收集器运行时可用的内存数量。

WeakReference 类

WeakReference 类的一个典型用途就是规范化映射(canonicalized mapping)。另外,对于那些生存期相对较长而且重新创建的开销也不高的对象来说,弱引用也比较有用。关键之处在于,垃圾收集器运行时如果碰到了弱可及对象,将释放 WeakReference 引用的对象。然而,请注意,垃圾收集器可能要运行多次才能找到并释放弱可及对象。

PhantomReference 类

PhantomReference 类只能用于跟踪对被引用对象即将进行的收集。同样,它还能用于执行 pre-mortem 清除操作。 PhantomReference 必须与 ReferenceQueue 类一起使用。需要 ReferenceQueue 是因为它能够充当通知机制。当垃圾收集器确定了某个对象是虚可及对象时, PhantomReference 对象就被放在它的 ReferenceQueue 上。将 PhantomReference 对象放在 ReferenceQueue 上也就是一个通知,表明 PhantomReference 对象引用的对象已经结束,可供收集了。这使您能够刚好在对象占用的内存被回收之前采取行动。


 

垃圾收集器和引用交互

垃圾收集器每次运行时都可以随意地释放不再是强可及的对象占用的内存。如果垃圾收集器发现了软可及对象,就会出现下列情况:

SoftReference 对象的 referent 域被设置为 null ,从而使该对象不再引用 heap 对象。
SoftReference 引用过的 heap 对象被声明为 finalizable 。
当 heap 对象的 finalize() 方法被运行而且该对象占用的内存被释放, SoftReference 对象就被添加到它的 ReferenceQueue (如果后者存在的话)。
如果垃圾收集器发现了弱可及对象,就会出现下列情况:

WeakReference 对象的 referent 域被设置为 null ,从而使该对象不再引用 heap 对象。
WeakReference 引用过的 heap 对象被声明为 finalizable 。
当 heap 对象的 finalize() 方法被运行而且该对象占用的内存被释放时, WeakReference 对象就被添加到它的 ReferenceQueue (如果后者存在的话)。
如果垃圾收集器发现了虚可及对象,就会出现下列情况:

PhantomReference 引用过的 heap 对象被声明为 finalizable 。
与软引用和弱引用有所不同, PhantomReference 在堆对象被释放之前就被添加到它的 ReferenceQueue 。(请记住,所有的 PhantomReference 对象都必须用经过关联的 ReferenceQueue 来创建。)这使您能够在堆对象被回收之前采取行动。
请考虑清单 1 中的代码。图 1 说明了这段代码的执行情况。


清单 1. 使用 WeakReference 及 ReferenceQueue 的示例代码
//Create a strong reference to an object
MyObject obj = new MyObject();                  //1
//Create a reference queue
ReferenceQueue rq = new ReferenceQueue();       //2
 
//Create a weakReference to obj and associate our reference queue
WeakReference wr = new WeakReference(obj, rq);  //3
 


图 1. 执行了清单 1 中行 //1、//2 和 //3 的代码之后的对象布局


图 1 显示了每行代码执行后各对象的状态。行 //1 创建 MyObject 对象,而行 //2 则创建 ReferenceQueue 对象。行 //3 创建引用其引用对象 MyObject 的 WeakReference 对象,还创建它的 ReferenceQueue 。请注意,每个对象引用( obj 、 rq 及 wr )都是强引用。要利用这些引用类,您必须取消对 MyObject 对象的强引用,方法是将 obj 设置为 null 。前面说过,如果不这样做,对象 MyObject 永远都不会被回收,引用类的任何优点都会被削弱。

每个引用类都有一个 get() 方法,而 ReferenceQueue 类有一个 poll() 方法。 get() 方法返回对被引用对象的引用。在 PhantomReference 上调用 get() 总是会返回 null 。这是因为 PhantomReference 只用于跟踪收集。 poll() 方法返回已被添加到队列中的引用对象,如果队列中没有任何对象,它就返回 null 。因此,执行清单 1 之后再调用 get() 和 poll() 的结果可能是:

wr.get();   //returns reference to MyObject
rq.poll();  //returns null
 


现在我们假定垃圾收集器开始运行。由于 MyObject 对象没有被释放,所以 get() 和 poll() 方法将返回同样的值; obj 仍然保持对该对象进行强引用。实际上,对象布局还是没有改变,和图 1 所示的差不多。然而,请考虑下面的代码:

obj = null;
System.gc();  //run the collector
 


在这段代码执行后,对象布局就如图 2 所示:


图 2. obj = null; 和垃圾收集器运行后的对象布局


现在,调用 get() 和 poll() 将产生与前面不同的结果:

wr.get();   //returns null
rq.poll();  //returns a reference to the WeakReference object
 


这种情况表明, MyObject 对象(对它的引用原来是由 WeakReference 对象进行的)不再可用。这意味着垃圾收集器释放了 MyObject 占用的内存,从而使 WeakReference 对象可以被放在它的 ReferenceQueue 上。这样,您就可以知道当 WeakReference 或 SoftReference 类的 get() 方法返回 null 时,就有一个对象被声明为 finalizable ,而且可能(不过不一定)被收集。只有当 heap 对象完全结束而且其内存被回收后, WeakReference 或 SoftReference 才会被放到与其关联的 ReferenceQueue 上。清单 2 显示了一个完整的可运行程序,它展示了这些原理中的一部分。这段代码本身就颇具说明性,它含有很多注释和打印语句,可以帮助您理解。


清单 2. 展示引用类原理的完整程序
import java.lang.ref.*;
class MyObject
{
  protected void finalize() throws Throwable
  {
    System.out.println("In finalize method for this object: " +
                       this);
  }
}
class ReferenceUsage
{
  public static void main(String args[])
  {
    hold();
    release();
  }
  public static void hold()
  {
    System.out.println("Example of incorrectly holding a strong " +
                       "reference");
    //Create an object
    MyObject obj = new MyObject();
    System.out.println("object is " + obj);
    //Create a reference queue
    ReferenceQueue rq = new ReferenceQueue();
    //Create a weakReference to obj and associate our reference queue
    WeakReference wr = new WeakReference(obj, rq);
    System.out.println("The weak reference is " + wr);
    //Check to see if it's on the ref queue yet
    System.out.println("Polling the reference queue returns " +
                       rq.poll());
    System.out.println("Getting the referent from the " +
                       "weak reference returns " + wr.get());
   
    System.out.println("Calling GC");
    System.gc();
    System.out.println("Polling the reference queue returns " +
                       rq.poll());
    System.out.println("Getting the referent from the " +
                       "weak reference returns " + wr.get());
  }
  public static void release()
  {
    System.out.println("");
    System.out.println("Example of correctly releasing a strong " +
                       "reference");
    //Create an object
    MyObject obj = new MyObject();
    System.out.println("object is " + obj);
    //Create a reference queue
    ReferenceQueue rq = new ReferenceQueue();
    //Create a weakReference to obj and associate our reference queue
    WeakReference wr = new WeakReference(obj, rq);
    System.out.println("The weak reference is " + wr);
    //Check to see if it's on the ref queue yet
    System.out.println("Polling the reference queue returns " +
                       rq.poll());
    System.out.println("Getting the referent from the " +
                       "weak reference returns " + wr.get());
   
    System.out.println("Set the obj reference to null and call GC");
    obj = null;
    System.gc();
    System.out.println("Polling the reference queue returns " +
                       rq.poll());
    System.out.println("Getting the referent from the " +
                       "weak reference returns " + wr.get());
  }
}
 


用途和风格

这些类背后的原理就是避免在应用程序执行期间将对象留在内存中。相反,您以软引用、弱引用或虚引用的方式引用对象,这样垃圾收集器就能够随意地释放对象。当您希望尽可能减小应用程序在其生命周期中使用的堆内存大小时,这种用途就很有好处。您必须记住,要使用这些类,您就不能保留对对象的强引用。如果您这么做了,那就会浪费这些类所提供的任何好处。

另外,您必须使用正确的编程风格以检查收集器在使用对象之前是否已经回收了它,如果已经回收了,您首先必须重新创建该对象。这个过程可以用不同的编程风格来完成。选择错误的风格会导致出问题。请考虑清单 3 中从 WeakReference 检索被引用对象的代码风格:


清单 3. 检索被引用对象的风格
obj = wr.get();
if (obj == null)
{
  wr = new WeakReference(recreateIt());  //1
  obj = wr.get();                        //2
}
//code that works with obj
 


研究了这段代码之后,请看看清单 4 中从 WeakReference 检索被引用对象的另一种代码风格:


清单 4. 检索被引用对象的另一种风格
obj = wr.get();
if (obj == null)
{
  obj = recreateIt();                    //1
  wr = new WeakReference(obj);           //2
}
//code that works with obj
 


请比较这两种风格,看看您能否确定哪种风格一定可行,哪一种不一定可行。清单 3 中体现出的风格不一定在所有情况下都可行,但清单 4 的风格就可以。清单 3 中的风格不够好的原因在于, if 块的主体结束之后 obj 不一定是非空值。请考虑一下,如果垃圾收集器在清单 3 的行 //1 之后但在行 //2 执行之前运行会怎样。 recreateIt() 方法将重新创建该对象,但它会被 WeakReference 引用,而不是强引用。因此,如果收集器在行 //2 在重新创建的对象上施加一个强引用之前运行,对象就会丢失, wr.get() 则返回 null 。

清单 4 不会出现这种问题,因为行 //1 重新创建了对象并为其指定了一个强引用。因此,如果垃圾收集器在该行之后(但在行 //2 之前)运行,该对象就不会被回收。然后,行 //2 将创建对 obj 的 WeakReference 。在使用这个 if 块之后的 obj 之后,您应该将 obj 设置为 null ,从而让垃圾收集器能够回收这个对象以充分利用弱引用。清单 5 显示了一个完整的程序,它将展示刚才我们描述的风格之间的差异。(要运行该程序,其运行目录中必须有一个“temp.fil”文件。


清单 5. 展示正确的和不正确的编程风格的完整程序
import java.io.*;
import java.lang.ref.*;
class ReferenceIdiom
{
  public static void main(String args[]) throws FileNotFoundException
  {
    broken();
    correct();
  }
  public static FileReader recreateIt() throws FileNotFoundException
  {
    return new FileReader("temp.fil");
  }
  public static void broken() throws FileNotFoundException
  {
    System.out.println("Executing method broken");
    FileReader obj = recreateIt();
    WeakReference wr = new WeakReference(obj);
    System.out.println("wr refers to object " + wr.get());
    System.out.println("Now, clear the reference and run GC");
    //Clear the strong reference, then run GC to collect obj.
    obj = null;
    System.gc();
    System.out.println("wr refers to object " + wr.get());
    //Now see if obj was collected and recreate it if it was.
    obj = (FileReader)wr.get();
    if (obj == null)
    {
      System.out.println("Now, recreate the object and wrap it
        in a WeakReference");
      wr = new WeakReference(recreateIt());
      System.gc();  //FileReader object is NOT pinned...there is no
                    //strong reference to it.  Therefore, the next
                    //line can return null.
      obj = (FileReader)wr.get();
    }
    System.out.println("wr refers to object " + wr.get());
  }
  public static void correct() throws FileNotFoundException
  {
    System.out.println("");
    System.out.println("Executing method correct");
    FileReader obj = recreateIt();
    WeakReference wr = new WeakReference(obj);
    System.out.println("wr refers to object " + wr.get());
    System.out.println("Now, clear the reference and run GC");
    //Clear the strong reference, then run GC to collect obj
    obj = null;
    System.gc();
    System.out.println("wr refers to object " + wr.get());
    //Now see if obj was collected and recreate it if it was.
    obj = (FileReader)wr.get();
    if (obj == null)
    {
      System.out.println("Now, recreate the object and wrap it
        in a WeakReference");
      obj = recreateIt();
      System.gc();  //FileReader is pinned, this will not affect
                    //anything.
      wr = new WeakReference(obj);
    }
    System.out.println("wr refers to object " + wr.get());
  }
}
 


总结

如果使用得当,引用类还是很有用的。然而,由于它们所依赖的垃圾收集器行为有时候无法预知,所以其实用性就会受到影响。能否有效地使用它们还取决于是否应用了正确的编程风格;关键在于您要理解这些类是如何实现的以及如何对它们进行编程。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/rujielaisusan/archive/2009/05/23/4209697.aspx

分享到:
评论

相关推荐

    Java2 学习指南.rar

    2. 数据类型:包括基本类型(如`int`、`char`、`boolean`)和引用类型(如类、接口、数组)。 3. 控制流:包括条件语句(如`if`、`switch`)和循环(如`for`、`while`)。 四、异常处理 Java中的异常处理是通过`try...

    Java se编程指南

    7. **网络编程**:Java的Socket编程允许我们创建客户端和服务器应用,理解TCP和UDP协议的区别,以及ServerSocket和Socket类的使用,能够帮助开发者构建网络应用程序。 8. **Java SE 6.0的新特性**:在Java SE 6.0...

    Java2_认证考试指南

    Java2_认证考试指南主要针对的是想要通过Java SE(标准版)认证的开发者,这个指南将涵盖Java语言的基础知识、核心特性以及高级编程概念。Java2代表的是Java平台的一个历史版本,现在通常指的是Java 2 Platform ...

    java 考试复习指南

    【Java 考试复习指南】 1. Java 编译过程:Java 源程序文件(扩展名为 .java)经过Java编译器编译后,会生成字节码文件(扩展名为 .class)。这是Java程序运行的基础,因为JVM(Java虚拟机)执行的是字节码。 2. ...

    Java2 学习指南

    本指南针对初学者,旨在帮助你掌握Java2的基础知识,并逐步深入到高级特性。 1. **Java语言基础**: - **变量与数据类型**:Java支持基本数据类型(如int、char、boolean)以及引用数据类型(如类、接口、数组)。...

    Java程序员开发指南

    Java程序员开发指南旨在帮助初学者和有一定经验的开发者深入理解并掌握Java编程语言,从而提升在实际项目中的应用能力。本指南将涵盖以下几个核心领域: 1. **Java基本语法**:Java是一种静态类型的、强类型的语言...

    Java高效编程指南.doc

    Java高效编程指南主要关注如何优化Java代码的性能和设计,以提高程序的效率和可维护性。以下是一些关键知识点的详细说明: 1. **创建和销毁对象** - **静态工厂方法**:使用静态工厂方法代替构造函数,因为它们...

    Java概论(面试指南)

    Java概论(面试指南)是一本专门针对有一定Java基础的求职者而准备的书籍,目的在于帮助他们梳理和掌握Java面试中的重点知识点。考虑到应聘者的不同水平,从初级到中高级,本书提供了一个实用的复习框架,重点突出,...

    Java SCJP中文学习指南

    Java SCJP,全称为Sun Certified Programmer for the Java 2 Platform, Standard Edition,是Oracle公司针对Java初学者和专业开发者的一项认证考试。这个“Java SCJP中文学习指南”旨在帮助中文使用者深入理解Java...

    Java8开发指南

    9. **类型接口(Type Interface)**:Java 8允许在接口中定义静态方法,增强了接口的功能,使得接口可以作为工具类使用,如`Collections`和`Objects`。 10. **构造函数引用**:类似于方法引用,构造函数引用允许...

    Java避坑指南:Java高手笔记代码篇.rar

    了解强引用、软引用、弱引用和虚引用的区别,有助于优化内存使用。 3. **多线程编程**:Java提供了丰富的并发工具,如synchronized关键字、volatile变量、ThreadLocal、ExecutorService等。理解和熟练运用这些工具...

    2015Java面试指南

    - **Java实现浅克隆与深克隆**:浅克隆复制对象本身及含有引用的对象地址,而深克隆则复制了对象本身及所有成员变量的值。 - **枚举可以序列化吗**:枚举类型默认实现了`Serializable`接口,因此可以直接进行序列化...

    java开发技术全程指南

    《Java开发技术全程指南》是针对Java编程语言的详尽学习资源,涵盖了多个章节的实践代码示例。这些章节的命名以"CodeChap"开头,数字表示章节顺序,这通常意味着它们按照教学顺序排列,从基础到进阶,帮助读者逐步...

    阿里巴巴 Java 编码指南,Alibaba Java Coding Guidelines​,兼容 Idea 2023.3+

    《阿里巴巴 Java 编码指南》是业界广泛采用的编码规范,旨在提高代码质量和开发效率,尤其对于使用 IntelliJ IDEA 的开发者来说,此指南的兼容性更新至 2023.3+ 版本,确保了最新的开发环境支持。这份指南在 2024 年...

    java程序员面试指南(代码

    1. **Java基础知识**:这是面试的起点,包括基本语法、数据类型(如原始类型与引用类型)、变量、运算符、流程控制(if-else、switch、for、while循环)以及方法。 2. **面向对象编程**:理解类、对象、封装、继承...

    Java企业开发指南

    7. **JNDI(Java Naming and Directory Interface)**:这是一种目录访问协议,使得开发者能够通过名字查找和引用资源。 #### 应用部署与管理 Java EE应用通常被部署在应用服务器上,如Tomcat、GlassFish或...

    Velocity java开发指南

    ### Velocity Java 开发指南知识点详解 #### 一、Velocity简介 **Velocity** 是一款基于 Java 的模板引擎,它能够高效地将数据模型与界面展示分离,使得开发人员能够轻松地生成动态网页或者其他任何形式的文本输出...

    Java EE实用开发指南

    《Java EE实用开发指南:基于Weblogic+EJB3+Struts2+Hibernate+Spring》是一本讲解如何使用Weblogicl0.3+EJB3+JPA+Struts2+Hibernate+Spring开发Java Web应用程序的实用性图书,书中在具体讲解SSH2开发技术的同时,...

Global site tag (gtag.js) - Google Analytics