幻影参考对象

幻影参考用于验尸操作。 Java规范规定,在清除幻像引用本身之前,不会释放幻像引用的对象

我的问题是:此function(对象未解除分配)的用途是什么?

(我提出的唯一想法是允许本机代码对对象进行事后清理,但这并不是很有说服力)。

我能想到的唯一一个可以防止重新分配的好用例是某种JNI实现的异步数据源写入引用的对象,并且必须被告知要停下来 – 停止写入对象 – 在记忆被回收之前。 如果允许先前的释放,一个简单的忘记处置()错误可能会导致内存损坏。

这是过去使用finalize()的情况之一,可能会引发一些怪癖。

编辑,因为我首先误解了这个问题:

引自http://www.memorymanagement.org/glossary/p.html :

Java规范说,当引用对象入队时,幻像引用不会被清除,但实际上,语言无法判断是否已经完成。 在一些实现中,JNI弱全局引用弱于幻像引用,并提供访问幻像可到达对象的方式。

但是我发现没有其他的参考文献会说同样的。

我认为这个想法是让其他对象在原始对象之外进行额外的清理。 例如,如果无法扩展原始对象以实现某些终结内容,则可以使用幻像引用。

更大的问题是JVM不能保证一个对象永远不会被最终确定,并且我不能保证幻像引用能够在完成后完成它们的事情。

幻像引用可用于执行预垃圾收集操作,例如释放资源。 相反,人们通常使用finalize()方法,这不是一个好主意。 终结器会对垃圾收集器的性能产生可怕的影响,并且如果您不是非常小心,可能会破坏应用程序的数据完整性,因为随机时间会在随机线程中调用“终结器”。

在幻像引用的构造函数中,指定一个ReferenceQueue,一旦引用的对象变为“幻像可达”,幻像引用就会入队。 幻影可达除了通过幻像引用之外无法访问。 最初令人困惑的是,尽管幻像引用继续将引用的对象保存在私有字段中(与软引用或弱引用不同),但其getReference()方法始终返回null。 这样您就无法再次强烈地访问对象。

有时,您可以轮询ReferenceQueue并检查是否有任何新的PhantomReferences,其引用的对象已变为幻像可达。 为了能够使用任何有用的东西,例如可以从java.lang.ref.PhantomReference派生一个类,该类引用在垃圾收集之前应该释放的资源。 一旦幻像引用变得无法访问,引用的对象仅被垃圾收集。

http://www.javalobby.org/java/forums/m91822870.html#91822413

对于没有生命周期管理机制但正在实现需要明确生命周期管理的API的API,这是一个完美的解决方案。

特别是任何类型的API,过去只使用内存中的对象,但是你使用套接字连接或文件连接重新实现到其他更大的后备存储,可以使用PhantomReference“关闭”并清除连接信息。对象是GC’d并且连接从未关闭,因为没有您可以使用的生命周期管理API接口。

考虑将简单的地图映射移动到数据库中。 丢弃映射引用时,没有明确的“关闭”操作。 但是,如果您已经实现了写入缓存,那么您希望能够完成任何写入并关闭与“数据库”的套接字连接。

下面是我用于此类内容的课程。 注意,对PhantomReferences的引用必须是非本地引用才能正常工作。 否则,在退出代码块之前,jit会导致它们过早排队。


     import java.lang.ref.PhantomReference;
     import java.lang.ref.Reference;
     import java.lang.ref.ReferenceQueue;
     import java.util.ArrayList;
     import java.util.List;
     import java.util.concurrent.ConcurrentHashMap;
     import java.util.concurrent.atomic.AtomicInteger;
     import java.util.logging.Level;
     import java.util.logging.Logger;

     / **
      *此类提供了一种跟踪一种类型的引用丢失的方法
      * object允许使用辅助引用来执行一些清理
      *活动。 最常见的用途是使用一个可能的对象
      *包含或引用需要进行一些清理的另一个对象
      *当引用不再被引用时。
      * 

*一个例子可能是Holder类型的对象,它引用或使用a *套接字连接。 当引用丢失时,套接字应该是 *关闭。 因此,可以创建实例,如 *

      * ReferenceTracker trker = ReferenceTracker(){
      * public void release(Socket s){
      *尝试{
      * s.close();
      *} catch(Exception ex){
      * log.log(Level.SEVERE,ex.toString(),ex);
      *}
      *}
      *};
      * 

*某处,可能会有以下呼叫。
*

      *界面持有人{
      * public T get();
      *}
      *类SocketHolder实现Holder {
      *sockets;
      * public SocketHolder(Socket sock){
      * s =袜子;
      *}
      * public Socket get(){
      *返回s;
      *}
      *}
      * 

*这定义了Holder接口的实现
*对Socket对象的引用。 使用trker
*上面的对象可能包括使用创建方法
*对象和注册参考,如下所示。
*

      * public SocketHolder connect(String host,int port)抛出IOException {
      * Socket s = new Socket(主机,端口);
      * SocketHolder h = new SocketHolder(s);
      * trker.trackReference(h,s);
      *返回h;
      *}
      * 

*希望使用套接字连接的软件,并传递它
*在所有情况下,使用SocketHolder.get()来引用Socket实例。
*然后,当所有SocketHolder引用都被删除时,套接字会
*由显示的已released(java.net.Socket)方法关闭
* 以上。
*

* {@link ReferenceTracker}类使用{@link PhantomReference}作为第一个参数
*持有第二个参数引用的地图的关键字。 因此,当
*密钥实例被释放,密钥引用排队,可以从中删除
*队列,用于从地图中删除随后传递给的值
*已发布()。
* /
公共抽象类ReferenceTracker {
/ **
*正在从引用队列中删除条目的线程实例,在它们出现时重新排队。
* /
private volatile RefQueuePoll民意调查;
/ **
*用于此实例的Logger实例。 它将包含名称作为后缀
*如果使用该构造函数。
* /
private static final Logger log = Logger.getLogger(ReferenceTracker.class.getName());
/ **
*该名称指示这是用于记录和其他分离的实例
*需要的实例。
* /
private final String;

/ **
*使用传递的名称创建ReferenceTracker的新实例以进行区分
*日志记录和toString()实现中的实例。
* @param这个实例的名称用于区分日志记录中的多个实例等。
* /
public ReferenceTracker(String which){
this.which = which;
}

/ **
*创建一个没有限定名称的ReferenceTracker的新实例。
* /
public ReferenceTracker(){
this.which = null;
}

/ **
*提供对此实例名称的访问。
* @return此实例的名称。
* /
@覆盖
public String toString(){
if(which == null){
return super.toString()+“:ReferenceTracker”;
}
return super.toString()+“:ReferenceTracker [”+ which +“]”;
}

/ **
*子类必须实现此方法。 所有引用时都会调用它
*相关的持有者对象被删除。
* @param val作为第二个参数传递给{@link #trackReference(Object,Object)trackReference(T,K)}的相应调用的值
* /
公共抽象空洞释放(K val);

/ **引用持有者对象的引用队列* /
private final ReferenceQueuerefqueue = new ReferenceQueue();
/ **
*已创建然后作为条目销毁的线程总数的计数
*已被跟踪。 当零跟踪引用时,没有队列正在运行。
* /
private final AtomicInteger tcnt = new AtomicInteger();
private volatile boolean running;
/ **
*一个Thread实现,轮询{@link #refqueue}以便随后调用{@link release(K)}
*删除对T对象的引用。
* /
私有类RefQueuePoll扩展Thread {
/ **
*与此实例关联的线程编号。 可能只有两个例子
*此类存在于易失性系统中。 如果是这种情况,则此值将为
*在某些日志记录中可见,以区分活动日志。
* /
私人最终的int mycnt;
/ **
*创建此类的实例。
* /
public RefQueuePoll(){
setDaemon(true);
setName(getClass()。getName()+“:ReferenceTracker(”+ which +“)”);
mycnt = tcnt.incrementAndGet();
}
/ **
*此方法提供执行refqueue.remove()所有活动。
*调用然后调用release released(K)让应用程序释放
*所需资源。
* /
public @Override void run(){
尝试{
doRun();
} catch(Throwable ex){
log.log(完成?Level.INFO:Level.SEVERE,
ex.toString()+“:幻像ref poll thread stopped”,ex);
} finally {
running = false;
}
}

private volatile boolean done = false;
private void doRun(){
while(!done){
参考ref = null;
尝试{
running = true;
ref = refqueue.remove();
K ctl;
synchronized(refmap){
ctl = refmap.remove(ref);
done = actCnt.decrementAndGet()== 0;
if(log.isLoggable(Level.FINE)){
log.log(Level.FINE,“current act refs = {0},mapsize = {1}”,new Object [] {actCnt.get(),refmap.size()});
}
if(actCnt.get()!= refmap.size()){
Throwable ex = new IllegalStateException(“活动引用计数和映射大小不同步”);
log.log(Level.SEVERE,ex.toString(),ex);
}
}

if(log.isLoggable(Level.FINER)){
log.log(Level.FINER,“发布的参考:{0},dep = {1}”,new Object [] {ref,ctl});
}
if(ctl!= null){
尝试{
释放(ctl);
if(log.isLoggable(Level.FINE)){
log.log(Level.FINE,“依赖对象已发布:{0}”,ctl);
}
} catch(RuntimeException ex){
log.log(Level.SEVERE,ex.toString(),ex);
}
}

} catch(Exception ex){
log.log(Level.SEVERE,ex.toString(),ex);
} finally {
if(ref!= null){
ref.clear();
}
}
}

if(log.isLoggable(Level.FINE)){
log.log(Level.FINE,“{1}的轮询线程{0}关闭”,新的Object [] {m​​ycnt,this});
}
}
}

/ **
*活动参考的计数。
* /
private final AtomicInteger actCnt = new AtomicInteger();
/ **
*从T引用到K对象的映射,用于释放(K)调用
* /
private final ConcurrentHashMap,K> refmap = new ConcurrentHashMap,K>();
/ **
*添加跟踪参考。 除了可能之外,dep不应该以任何方式引用ref
*一个WeakReference。 dep几乎总是由ref引用的东西。
* @throws IllegalArgumentException与ref和dep是同一个对象。
* @param dep不再引用ref时需要清理的依赖对象。
* @param ref要跟踪其引用的对象
* /
public void trackReference(T ref,K dep){
if(ref == dep){
抛出新的IllegalArgumentException(“引用对象和依赖对象不能相同”);
}
PhantomReference p =新的PhantomReference(ref,refqueue);
synchronized(refmap){
refmap.put(p,dep);
if(actCnt.getAndIncrement()== 0 || running == false){
if(actCnt.get()> 0 && running == false){
if(log.isLoggable(Level.FINE)){
log.fine(“启动已停止的幻像ref轮询线程”);
}
}
poll = new RefQueuePoll();
poll.start();
if(log.isLoggable(Level.FINE)){
log.log(Level.FINE,“为{1}创建的轮询线程#{0}”,新的Object [] {tcnt.get(),this});
}
}
}
}

/ **
*如果跟踪器所在的JVM正在运行,则可以调用此方法
*关闭,或正在关闭其他上下文并跟踪对象
*现在应该发布跟踪器。 这种方法将导致
* {@link #released(Object)发布(K)}被调用每个未完成的引用。
* /
public void shutdown(){
Listrem;
//复制值并清除地图以便释放
//只会被调用一次,因此GC后来会驱逐引用
synchronized(refmap){
rem = new ArrayList(refmap.values());
refmap.clear();
}
for(K dep:rem){
尝试{
释放(dep);
} catch(Exception ex){
log.log(Level.SEVERE,ex.toString(),ex);
}
}
}
}

它可以让你有两个幻像缓存,它们在内存管理方面非常有效。 简单地说,如果你有巨大的对象,但是很难创建但很少使用,你可以使用幻像缓存来引用它们,并确保它们不会占用更有价值的内存。 如果使用常规引用,则必须手动确保没有对象的引用。 您可以对任何对象进行相同的争论,但您不必手动管理幻像缓存中的引用。 只需要小心检查它们是否已被收集。

您也可以使用框架(即工厂),其中引用作为幻像引用。 如果对象很多且寿命很短(即使用然后处理),这将非常有用。 如果你有麻烦的程序员认为垃圾收集是神奇的,那么清理内存非常方便。