`

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

分享到:
评论

相关推荐

    Java EE实用开发指南

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

    2015Java面试指南

    3、笔者一直想写一本Java相关的书,本文档的知识会穿插在未来的书中,所以文档中的内容不会全部免费公开(书写好后,文档会开源),但此文档中部分知识点是引用网上优秀的博客、论坛中内容,对于此部分内容是公开的...

    java并发工具包 java.util.concurrent中文版用户指南pdf

    2. 阻塞队列 BlockingQueue 3. 数组阻塞队列 ArrayBlockingQueue 4. 延迟队列 DelayQueue 5. 链阻塞队列 LinkedBlockingQueue 6. 具有优先级的阻塞队列 PriorityBlockingQueue 7. 同步队列 SynchronousQueue 8. 阻塞...

    Java并发工具包java.util.concurrent用户指南中英文对照阅读版.pdf

    java.util.concurrent - Java 并发工具包 2. 阻塞队列 BlockingQueue 3. 数组阻塞队列 ArrayBlockingQueue 4. 延迟队列 DelayQueue 5. 链阻塞队列 LinkedBlockingQueue 6. 具有优先级的阻塞队列 ...

    AIC的Java课程1-6章

    第5 版 清华大学出版社  “SCJP学习指南” 人民邮电出版社  “Java 编程思想” 第3版 机械工业出版社  教学内容和要求 知识点 重要程度 使用频度 难度 Java 入门 高 中 易 变量和运算符 高 ...

    现代 Java - Java 8 指南

    在简短的代码示例的支持下,您将学习如何使用默认接口方法、lambda 表达式、方法引用和可重复注释。在本文末尾,您将熟悉最新的API更改,例如流、功能接口、地图扩展和新的 Date API。没有文字墙,只有一堆带注释的...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part2

    第4章 hbm2java和hbm2ddl工具  4.1 创建对象-关系映射文件  4.1.1 定制持久化类  4.1.2 定制数据库表  4.2 建立项目的目录结构  4.3 运行hbm2java工具  4.4 运行hbm2ddl工具  4.5 使用XML格式的配置文件  ...

    Java并发工具包java.util.concurrent用户指南中英文对照阅读版

    2. 阻塞队列 BlockingQueue 3. 数组阻塞队列 ArrayBlockingQueue 4. 延迟队列 DelayQueue 5. 链阻塞队列 LinkedBlockingQueue 6. 具有优先级的阻塞队列 PriorityBlockingQueue 7. 同步队列 SynchronousQueue 8. 阻塞...

    Java语言规范

    , 本书由Java技术的发明者编写,是Java编程语言的权威性技术指南。如果你想知道Java语言构造的精确含义以及各种技术细节,本书是最好的资源。, 本书全面、准确而详细地讨论了Java编程语言,是Java语言最新版本的规范...

    Ranorex用户指南

    使用Validate类 强制一个测试用例失败 设置automation speed 访问测试用例和测试套件的上下文 高级代码示例 如何做基于图像的自动化 如何查找和比较图像 处理意外出现的对话框 [数据连接器] 管理数据源 数据...

    Java数据库编程宝典2

    第2部分 在两层客户/服务器结构中使用JDBC和SQL 第5章 使用JDBC和SQL创建表 5.1 创建数据库 5.2 使用表 5.2.1 记录和字段、行和列 5.2.2 SQL数据类型 5.2.3 完整性约束 5.3 创建表 5.4 使用JDBC创建表 ...

    MongoDB权威指南(中文版)高清

    1037.5 数据库引用 1047.5.1 什么是DBRef 1047.5.2 示例模式 1047.5.3 驱动对DBRef的支持 1057.5.4 什么时候该使用DBRef呢 106第8章 管理 1078.1 启动和停止MongoDB 1078.1.1 从命令行启动 1078.1.2...

    Java数据库编程宝典3

    第2部分 在两层客户/服务器结构中使用JDBC和SQL 第5章 使用JDBC和SQL创建表 5.1 创建数据库 5.2 使用表 5.2.1 记录和字段、行和列 5.2.2 SQL数据类型 5.2.3 完整性约束 5.3 创建表 5.4 使用JDBC创建表 ...

    ArcGIS Server 9.3 java adf 配置指南

    这篇文档主要是讲解如何进行java adf配置开发,如何模板引用地图进行配置adf 项目,图文并茂,说明详细,前提必须安装好ArcGIS Server 9.3 for java 软件。

    Velocity 模板使用指南

    它允许任何人仅仅简单的使用模板语言(template language)来引用由java代码定义的对象。 当Velocity应用于web开发时,界面设计人员可以和java程序开发人员同步开发一个遵循MVC架构的web站点,也就是说,页面设计...

    经典JAVA.EE企业应用实战.基于WEBLOGIC_JBOSS的JSF_EJB3_JPA整合开发.pdf

    第二部分详细讲解了jsf ri、jta、jndi、rmi、jms、javamail、ejb 3的session bean、message driven bean、jpa、jax-ws 2、jaas等java ee知识,这部分知识以jsf+ejb 3+jpa整合开发为重点,通过使用netbeans ide工具...

    阿里巴巴 Java 编码指南 pmd 实现和 IDE 插件

    [Mandatory]禁止使用已弃用的类或方法。 注意:例如,应使用decode(String source, Stringencode)而不是已弃用的方法decode(StringencodeStr)。一旦接口被弃用,接口提供者就有义务提供新的接口。同时,客户端程序员...

    泛微OA 二次开发指南

    泛微OA ecology 二次开发指南 完整文档说明 一、 ECOLOGY系统框架结构 二、 说明一个JSP页面,一个JAVA程序的基本组成,如何阅读JSP页面 三、 页面权限控制的说明,怎样在页面中引用权限,怎么样新增一个权限,如何在...

    Eclipse权威开发指南2.pdf

    3.2.6 使用快速修正功能来修改Java错误..... 82 3.2.7 重构..... 83 3.2.8 使用代码模板..... 86 3.2.9 字符串外部化..... 87 3.2.10 生成Javadoc.. 88 3.2.11 在不同的JRE下编写 Java代码..... 90 3.2.12 编译...

Global site tag (gtag.js) - Google Analytics