Java中管理资源的引用队列相关原理解析

当对象改变其可达性状态时,对该对象的引用就可能会被置于引用队列(reference queue)中。这些队列被垃圾回收器用来与我们的代码沟通有关对象可达性变化的情况。这些队列是探测可达性变化的最佳方式,尽管我们也可以通过检查get方法的返回值是不是null来探测对象的可达性变化。

  引用对象在构造时可以与特定队列建立关联。Reference的每一个子类都提供了如下形式的构造器:

  .public Strength Reference (T referent, ReferenceQueueq):该方法用给定的指称对象创建新的引用对象,并且把该引用对象注册到给定的队列中。弱引用和软引用在垃圾回收器确定它们的指称对象进人它们所表示的特定可达性状态之后,就会被插人到队列中,并且这两种引用在插人队列前都会被清除。虚引用也会在垃圾回收器确定它的指称对象进入虚可达状态之后,被插入到队列中,但是它们不会被清除。一旦引用对象被垃圾回收器插人到队列中,其get方法的返回值就肯定会是null,因此该对象就再也不能复活了。

  将引用对象注册到引用队列中并不会创建队列和引用对象之间的引用。如果我们的引用对象本身变成了不可达的,那么它就不能插人队列了。因此我们的应用需要保持对所有引用对象的强引用。

  ReferenceQueue类提供了三个用来移除队列中引用的方法:

  1.   .public Reference < ? extends下>poll ():用于移除并返回该队列中的下一个引用对象,如果队列为空,则返回null.
  2.   .public Referenceremove ()throws InterruptedException:用于移除并返回该队列中的下一个引用对象,该方法会在队列返回可用引用对象之前一直阻塞。
  3.   .public Referenceremove (long timeout) throws interrupte-dException:用于移除并返回队列中的下一个引用对象。该方法会在队列返回可用引用对象之前一直阻塞,或者在超出指定超时后结束。如果超出指定超时,则返回null.如果指定超时为0,意味着将无限期地等待。

  poll方法使得线程可以查询某个引用是否在队列中,并且在该引用存在于队列中时执行特定的动作。remove方法可以处理更复杂(更少见)的情况,在该方法中有一个专门的线程负责从队列中移除引用,并执行恰当的动作。这些方法的阻塞行为和object.wait中定义的阻塞行为相同。对于特定的引用,我们可以通过其isEnqueued方法来查询它是否在队列中,也可以通过调用其enqueue方法将其强制插入到队列中,但是通常这种插人是由垃圾回收器完成的。

  引用队列中的虚引用可以用来确定对象何时可以被回收。我们不可能通过虚引用来访问任何对象,即使该对象通过其他方式是可达的也是如此,因为虚引用的get方法总是返回null,事实上,用虚引用来查找要回收的对象是最安全的方法,因为弱引用和软引用在对象可终结之后会被插人到队列中,而虚引用是在指称对象被终结之后插人到队列中的,即在该对象可以执行某些操作的最后时刻之后插人队列的,所以它是绝对安全的。如果可以的话,应该总是使用虚引用,因为其他引用会存在finalize方法使用可终结对象的可能性。

  考虑一个资源管理器的例子,它可以控制外部资源集合的访问。对象可以请求访问某项外部资源,并且直至操作完成才结束访问,在此之后,它们应该将所用资源返回给资源管理器。如果这项资源是共享的,它的使用权就会在多个对象之间传递,甚至可能会在多个线程之间传递,因此我们很难确定哪个对象是这项资源最后的使用者,从而也就很难确定哪段代码将负责返回这项资源。为了处理这种情况,资源管理器可以通过将资源关联到被称为键( key)的特殊对象上,实现这项资源的自动回收。只要键对象是可达的,我们就认为这项资源还在使用中;只要键对象可以被当作垃圾回收,这项资源就会被自动释放。下面的代码是对上述资源的抽象表示:

  interface Resource{

  void use(Object key, Object…args);

  void release();

  }

  当获得某项资源时,其键对象必须提供给资源管理器。对于交还的Resource实例,只有在它得到了其对应的键时,才可以使用这项资源。这样就可以确保在键被回收之后,它所对应的资源就不能再被使用了,即便表示这项资源的Resource对象本身可能仍然是可达的。请注意,Resource对象并未存储对键对象的强引用,这一点很重要,因为这可以防止键对象变为不可达的,从而造成资源不能收回。Resource的实现可以嵌套在资源管理器中:

  private static class ResourceImpl implements Resource{

  int keyHash;

  boolean needsRelease=false

  ResourceImpl(Object key){

  keyHash=System.identityHashCode(key);

  //=set up the external resource

  needsRelease=true; }

  public void use(Object key,Object... args){

  if (System.identityHashCode(key)!=keyHash)

  throw new IlleqalArgumentException("wrong key"

  //...use the resource

  }

  public synchronized void release(){

  if (needsRelease){

  needsRelease=false:

  //=release the resource

  }

  }

  }

  在资源被创建时就存储了键对象的散列码,并且无论何时调用use方法,它都会检查是否提供了相同的键。对资源的实际使用可能还需要进行同步,但是为了简单起见,我们在这里把它们省略了。release方法负责释放资源,它可以由资源的使用者在使用结束之后直接调用,也可 以由资源管理器在键对象不再被引用时调用。因为我们将使用独立的线程来监视引用队列,所以release方法必须是synchronized的,并且必须允许多次调用。

  实际的资源管理器具有如下形式:

  public final class ResourceManager{

  final ReferenceQueue

  键对象可以是任意对象,与让资源管理器分配键对象相比,这赋予了资源使用者极大的灵活性。在调用getResource方法时,会创建一个新的Resource工mpl对象,同时会把提供给该方法的键传递给这个新的ResourceImpl对象。然后会创建一个虚引用,其指称对象就是传递给该方法的键,之后这个虚引用会被插人到资源管理器的引用队列中。最后所创建的虚引用和引用对象会被存储到映射表中,这个映射表有两个用途:一是保持所有的虚引用对象都是可达的,二是可以提供便捷的方法来查询每个虚引用所关联的实际的Resource对象。(另一种方式是子类化PhantomReference并将Resource对象存在一个字段中。)

  如果键对象变成了不可达的,那么资源管理器会使用独立的“收割机”(reaper)线程来处理资源。shutdown方法通过终止收割机线程(以响应中断)从而导致getResource方法抛出Ille-llleStateException异常来“关闭”资源管理器。在这个简单的设计中,任何在资源管理器关闭之后插人队列的引用都不会得到处理。实际的收割机线程如下:

  class ReaperThread extends Thread{

  public void run(){

  //run until interrupted

  while (true){

  try{

  Reference ref=queue.remove();

  Resource res=null;

  synchronized(ResourceManager.this){

  res=refs.get(ref);

  refs . remove(ref);

  }

  res .release();

  ref.clear();

  }

  catch (InterruptedException ex){

  break;//all done

  }

  }

  }

  }

  ReaperThread是内部类,并且给定的收割机线程会一直运行,直至与其相关联的资源管理器关闭。该线程会在remove方法上阻塞,直至与特定键相关联的虚引用被插人到引用队列中为止。这个虚引用可以从映射表中获取对Resource对象的引用,然后这一项“键一引用”对将会从映射表中移除。紧接着,在Resource对象上调用其release方法来释放这项资源。最后,

  虚引用被清除,使得键可以被回收。

  作为一种替代使用独立线程的方案,凡是在引用队列上调用poll方法并释放其键已经变为不可达的所有资源的操作都可以用getResourc“方法来替代,shutdow”方法也可以用来执行最后的poll操作。而资源管理器的语义将依赖于实际的资源类型和资源使用的模式。

  使用引用队列的设计与直接使用终结(特别是使用虚引用)的设计相比,要可靠得多。但是我们要记住,对于引用对象插入到引用队列中的确切时间和位置都是不能确定的,我们也不能确定在应用程序终止的时候,所有可插人的引用是否都匕经插人到了引用队列中。如果我们需要确保所有资源在应用程序终止之前都能够被释放掉,就必须要安装必要的关闭挂钩或者使用由应用程序定义的其他协议来确保实现这一点。

(0)

相关推荐

  • java引用jpython的方法示例

    本文实例讲述了java引用jpython的方法.分享给大家供大家参考,具体如下: import java.util.ArrayList; import java.util.List; import org.python.core.PyException; import org.python.core.PyInteger; import org.python.core.PyObject; import org.python.util.PythonInterpreter; public class

  • 用python删除java文件头上版权信息的方法

    在使用他人代码时,为不保留文件头部版权信息,需要一个个删掉,费时费力, 写了个脚本,简单清除掉目录下所有的文件的头部版权信息. # -*- coding: utf8 -*- ''''' 删除java文件头部的版权等注释 package之上的部分 ''' import os import sys def delHeader(filepath): if os.path.exists(filepath) : file = open(filepath) lines = file.readlines()

  • 浅析Java方法传值和传引用问题

    某门户网站的一道笔试题 复制代码 代码如下: public class Test {     public static void stringUpd(String str) {         str = str.replace("j", "l");         System.out.println(str);     }        public static void stringBufferUpd(StringBuffer bf) {        

  • java及C++中传值传递、引用传递和指针方式的理解

    java的值传递理解: 代码1: public class Test { /** * @param args */ public static void main(String[] args) { StringBuffer buffer= new StringBuffer("colin"); SChange(buffer); System.out.println( buffer); } public static void SChange (StringBuffer str) { st

  • python调用java模块SmartXLS和jpype修改excel文件的方法

    本文实例讲述了python调用java模块SmartXLS和jpype修改excel文件的方法.分享给大家供大家参考.具体实现方法如下: # -*- coding: utf8 -*- """ 使用java的模块SmartXLS和jpype修改excel 和xlrd,xlwt不同的是它可以生成和保持图表 """ from __future__ import print_function, division import os import jpyp

  • 全面解析Java中的GC与幽灵引用

    Java 中一共有 4 种类型的引用 : StrongReference. SoftReference. WeakReference 以及 PhantomReference (传说中的幽灵引用 呵呵), 这 4 种类型的引用与 GC 有着密切的关系,  让我们逐一来看它们的定义和使用场景 : 1. Strong ReferenceStrongReference 是 Java 的默认引用实现,  它会尽可能长时间的存活于 JVM 内, 当没有任何对象指向它时 GC 执行后将会被回收 Java代码

  • Java中的对象和对象引用实例浅析

    本文实例讲述了Java中的对象和对象引用.分享给大家供大家参考.具体分析如下: 在Java中,有一组名词经常一起出现,它们就是"对象和对象引用",很多朋友在初学Java的时候可能经常会混淆这2个概念,觉得它们是一回事,事实上则不然.今天我们就来一起了解一下对象和对象引用之间的区别和联系. 1.何谓对象? 在Java中有一句比较流行的话,叫做"万物皆对象",这是Java语言设计之初的理念之一.要理解什么是对象,需要跟类一起结合起来理解.下面这段话引自<Java编

  • 理解java和python类变量以及类的成员变量

    最可怕的不是犯错而是一直都没发现错误,直到现在我才知道自己对类变量的理解有问题. 大概可能也许是因为不常用类变量的原因吧,一直没有发现这个问题.最近在看C++时才知道了类变量到底是什么? 以前我一直觉得类变量和成员变量的唯一区别是类变量可以通过类名直接访问,是静态的.而成员变量需要实例化一个类后通过实例来访问. 万万没想到忽视了类变量在一个类中只有一个,各个实例中的都是同一个的,在一个实例中修改会影响其他实例中的类变量...(虽然平常也没有因为这个而引起什么bug,但是还是要补上认知的漏洞).

  • python实现JAVA源代码从ANSI到UTF-8的批量转换方法

    本文实例讲述了python实现JAVA源代码从ANSI到UTF-8的批量转换方法.分享给大家供大家参考.具体如下: 喜欢用eclipse的大神们,可能一不小心代码就变成ANSI码了,需要转换成utf-8嘛,一个文件一个文件的在Notepad2或者notepad++里面转换么?不,这里有批量转换的程序,python实现,需要的拿去用吧. ansi2utf8.py: #-*- coding: utf-8 -*- import codecs import os import shutil import

  • python调用java的Webservice示例

    一.java端首先我使用的是java自带的对webservice的支持包来编写的服务端和发布程序,代码如下.webservice的接口代码: 复制代码 代码如下: package com.xxx.test.ws; import javax.jws.WebMethod;import javax.jws.WebService; /** * Created with IntelliJ IDEA. * User: Administrator * Date: 14-3-5 * Time: 下午3:11 *

  • java直接调用python脚本的例子

    复制代码 代码如下: import java.io.BufferedReader;import java.io.InputStreamReader; public class Main { public static void main(String[] args) {  try {   System.out.println("start");   Process pr = Runtime.getRuntime().exec("python test.py"); B

随机推荐