·天新网首页·加入收藏·设为首页
首页|笔记本|手机|数码相机|摄像机|MP3/MP4|主板|内存|显示器|办公|打印机|下载|开发|汽车|学院|业界
硬件|台式机|数码|数字家庭|投影仪|GPS/CPU|显卡|硬盘|服务器|网络|一体机|驱动|源码|游戏|考试|报价
Java 2 引用类使用指南(2)
http://dev.21tx.com 2005年04月26日 Java中文网

Java 2 引用类使用指南(2)

请考虑清单 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());
}
}


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

上一篇: 完全掌握java中的包机制
下一篇: 用内嵌类减少 JAVA 程序设计中的混乱

Google
 
热点文章
关于我们 | 联系我们 | 广告服务 | 工作机会 | 版权声明 | 欢迎投稿 | 网站地图
Copyright © 2000-2008 , www.21tx.com , All Rights Reserved .
晨新科技 版权所有 Created by TXSite.net