Phantom Referenced Objects

前端 未结 6 1027
悲哀的现实
悲哀的现实 2021-02-15 20:04

Phantom References serve for post-mortem operations. The Java specification states that a phantom referenced object will not be deallocated until the phantom-re

相关标签:
6条回答
  • 2021-02-15 20:12

    Edit, since I've misunderstand the question first:

    Quoted from here http://www.memorymanagement.org/glossary/p.html:

    The Java specification says that the phantom reference is not cleared when the reference object is enqueued, but actually, there's no way in the language to tell whether that has been done or not. In some implementations, JNI weak global references are weaker than phantom references, and provide a way to access phantom reachable objects.

    But I found no other references which would say the same.

    0 讨论(0)
  • 2021-02-15 20:17

    The only good use-case I can think of, that would prevent deallocation, is one where some kind of JNI-implemented asynchronous data source is writing into the referenced object, and must be told to stand down - to stop writing into the object - before the memory is recycled. If prior deallocation were allowed, a simple forgot-to-dispose() bug could result in memory corruption.

    This is one of the cases where finalize() would have been used in the past, and probably drove some of its quirks.

    0 讨论(0)
  • 2021-02-15 20:22

    It can allow you two have phantom caches which are very efficient in memory management. Simply put, if you have huge objects that are expensive to create but seldom used, you can use a phantom cache to reference them and be sure they do not take up memory that is more valuable. If you use regular references you have to be manually make sure there are no references left to the object. You can argue the same about any object but you dont have to manually manage the references in your phantom cache. Just have to be carefull to check if they have been collected or not.

    Also you can use a framework (i.e. a factory) where references are given as phantom references. This is useful if the objects are many and short lived (i.e. used and then disposed). Very handy for clearing memory if you have sloppy programmers that think garbage collection is magical.

    0 讨论(0)
  • 2021-02-15 20:24

    This is a perfect solution for APIs which don't have a lifecycle management mechanism, but which you are implementing with something which requires explicit lifecycle management.

    In particular any sort of API which used to just use objects in memory, but which you've reimplemented using a socket connection or file connection to some other, larger backing store, can use PhantomReference to "close" and cleanup connection information prior to the object being GC'd and the connection never closed because there was no lifecycle management API interface that you could otherwise use.

    Think of moving a simple Map map into a database. When the map reference is discarded, there is no explicit "close" operation. Yet, if you had implemented a write through cache, you'd like to be able to finish any writes and close the socket connection to the your "database".

    Below is a class which I use for this kind of stuff. Note, that References to PhantomReferences must be non-local references to work correctly. Otherwise, the jit will cause them to be queued prematurely before you exit blocks of code.

    
        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;
    
        /**
         * This class provides a way for tracking the loss of reference of one type of
         * object to allow a secondary reference to be used to perform some cleanup
         * activity.  The most common use of this is with one object which might
         * contain or refer to another object that needs some cleanup performed
         * when the referer is no longer referenced.
         * 

    * An example might be an object of type Holder, which refers to or uses a * Socket connection. When the reference is lost, the socket should be * closed. Thus, an instance might be created as in *

         *    ReferenceTracker trker = ReferenceTracker() {
         *        public void released( Socket s ) {
         *            try {
         *                s.close();
         *            } catch( Exception ex ) {
         *                log.log( Level.SEVERE, ex.toString(), ex );
         *            }
         *        }
         *  };
         * 
    * Somewhere, there might be calls such as the following. *
         *        interface Holder {
         *            public T get();
         *        }
         *        class SocketHolder implements Holder {
         *            Socket s;
         *            public SocketHolder( Socket sock ) {
         *                s = sock;
         *            }
         *            public Socket get() {
         *                return s;
         *            }
         *        }
         * 
    * This defines an implementation of the Holder interface which holds * a reference to Socket objects. The use of the trker * object, above, might then include the use of a method for creating * the objects and registering the references as shown below. *
         *    public SocketHolder connect( String host, int port ) throws IOException {
         *        Socket s = new Socket( host, port );
         *        SocketHolder h = new SocketHolder( s );
         *        trker.trackReference( h, s );
         *        return h;
         *    }
         * 
    * Software wishing to use a socket connection, and pass it around would * use SocketHolder.get() to reference the Socket instance, in all cases. * then, when all SocketHolder references are dropped, the socket would * be closed by the released(java.net.Socket) method shown * above. *

    * The {@link ReferenceTracker} class uses a {@link PhantomReference} to the first argument as * the key to a map holding a reference to the second argument. Thus, when the * key instance is released, the key reference is queued, can be removed from * the queue, and used to remove the value from the map which is then passed to * released(). */ public abstract class ReferenceTracker { /** * The thread instance that is removing entries from the reference queue, refqueue, as they appear. */ private volatile RefQueuePoll poll; /** * The Logger instance used for this instance. It will include the name as a suffix * if that constructor is used. */ private static final Logger log = Logger.getLogger(ReferenceTracker.class.getName()); /** * The name indicating which instance this is for logging and other separation of * instances needed. */ private final String which; /** * Creates a new instance of ReferenceTracker using the passed name to differentiate * the instance in logging and toString() implementation. * @param which The name of this instance for differentiation of multiple instances in logging etc. */ public ReferenceTracker( String which ) { this.which = which; } /** * Creates a new instance of ReferenceTracker with no qualifying name. */ public ReferenceTracker( ) { this.which = null; } /** * Provides access to the name of this instance. * @return The name of this instance. */ @Override public String toString() { if( which == null ) { return super.toString()+": ReferenceTracker"; } return super.toString()+": ReferenceTracker["+which+"]"; } /** * Subclasses must implement this method. It will be called when all references to the * associated holder object are dropped. * @param val The value passed as the second argument to a corresponding call to {@link #trackReference(Object, Object) trackReference(T,K)} */ public abstract void released( K val ); /** The reference queue for references to the holder objects */ private final ReferenceQueuerefqueue = new ReferenceQueue(); /** * The count of the total number of threads that have been created and then destroyed as entries have * been tracked. When there are zero tracked references, there is no queue running. */ private final AtomicInteger tcnt = new AtomicInteger(); private volatile boolean running; /** * A Thread implementation that polls {@link #refqueue} to subsequently call {@link released(K)} * as references to T objects are dropped. */ private class RefQueuePoll extends Thread { /** * The thread number associated with this instance. There might briefly be two instances of * this class that exists in a volatile system. If that is the case, this value will * be visible in some of the logging to differentiate the active ones. */ private final int mycnt; /** * Creates an instance of this class. */ public RefQueuePoll() { setDaemon( true ); setName( getClass().getName()+": ReferenceTracker ("+which+")" ); mycnt = tcnt.incrementAndGet(); } /** * This method provides all the activity of performing refqueue.remove() * calls and then calling released(K) to let the application release the * resources needed. */ public @Override void run() { try { doRun(); } catch( Throwable ex ) { log.log( done ? Level.INFO : Level.SEVERE, ex.toString()+": phantom ref poll thread stopping", ex ); } finally { running = false; } } private volatile boolean done = false; private void doRun() { while( !done ) { Reference ref = null; try { 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("count of active references and map size are not in sync"); log.log(Level.SEVERE, ex.toString(), ex); } } if( log.isLoggable( Level.FINER ) ) { log.log(Level.FINER, "reference released for: {0}, dep={1}", new Object[]{ref, ctl}); } if( ctl != null ) { try { released( ctl ); if( log.isLoggable( Level.FINE ) ) { log.log(Level.FINE, "dependant object released: {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, "poll thread {0} shutdown for {1}", new Object[]{mycnt, this}); } } } /** * A count of the active references. */ private final AtomicInteger actCnt = new AtomicInteger(); /** * Map from T References to K objects to be used for the released(K) call */ private final ConcurrentHashMap,K>refmap = new ConcurrentHashMap,K>(); /** * Adds a tracked reference. dep should not refer to ref in any way except possibly * a WeakReference. dep is almost always something referred to by ref. * @throws IllegalArgumentException of ref and dep are the same object. * @param dep The dependent object that needs cleanup when ref is no longer referenced. * @param ref the object whose reference is to be tracked */ public void trackReference( T ref, K dep ) { if( ref == dep ) { throw new IllegalArgumentException( "Referenced object and dependent object can not be the same" ); } PhantomReference p = new 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("starting stopped phantom ref polling thread"); } } poll = new RefQueuePoll(); poll.start(); if( log.isLoggable( Level.FINE ) ) { log.log( Level.FINE, "poll thread #{0} created for {1}", new Object[]{tcnt.get(), this}); } } } } /** * This method can be called if the JVM that the tracker is in, is being * shutdown, or someother context is being shutdown and the objects tracked * by the tracker should now be released. This method will result in * {@link #released(Object) released(K) } being called for each outstanding refernce. */ public void shutdown() { Listrem; // Copy the values and clear the map so that released // is only ever called once, incase GC later evicts references synchronized( refmap ) { rem = new ArrayList( refmap.values() ); refmap.clear(); } for( K dep : rem ) { try { released( dep ); } catch( Exception ex ) { log.log( Level.SEVERE, ex.toString(), ex ); } } } }

    0 讨论(0)
  • 2021-02-15 20:28
    Phantom references can be used to perform pre-garbage collection actions such as freeing resources. Instead, people usually use the finalize() method for this which is not a good idea. Finalizers have a horrible impact on the performance of the garbage collector and can break data integrity of your application if you're not very careful since the "finalizer" is invoked in a random thread, at a random time.

    In the constructor of a phantom reference, you specify a ReferenceQueue where the phantom references are enqueued once the referenced objects becomes "phantom reachable". Phantom reachable means unreachable other than through the phantom reference. The initially confusing thing is that although the phantom reference continues to hold the referenced object in a private field (unlike soft or weak references), its getReference() method always returns null. This is so that you cannot make the object strongly reachable again.

    From time to time, you can poll the ReferenceQueue and check if there are any new PhantomReferences whose referenced objects have become phantom reachable. In order to be able to to anything useful, one can for example derive a class from java.lang.ref.PhantomReference that references resources that should be freed before garbage collection. The referenced object is only garbage collected once the phantom reference becomes unreachable itself.

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

    0 讨论(0)
  • 2021-02-15 20:37

    I think the idea is to let other objects do extra cleanup above and beyond what the original object does. For example, if the original object cannot be extended to implement some finalization stuff, you can use phantom references.

    The bigger problem is that the JVM makes no guarantee that an object will ever be finalized, and I assume by extension no guarantee that phantom references get to do their thing post-finalization.

    0 讨论(0)
提交回复
热议问题