When an object changes its reachability state, a reference to the object may be placed in a reference queue. These queues are used by the garbage collector to communicate with our code about changes in object accessibility. These queues are the best way to detect accessibility changes, although we can also detect accessibility changes of the object by checking whether the return value of the get method is null.
Reference objects can be associated with specific queues when constructed. Each subclass of Reference provides constructors of the following form:
.public Strength Reference (T reference, ReferenceQueueq): This method creates a new reference object with the given reference object and registers the reference object to the given queue. Weak references and soft references are inserted into the queue after the garbage collector determines that their reference objects enter the specific reachability state they represent, and both references are cleared before the insertion queue. Virtual references will also be inserted into the queue after the garbage collector determines that its reference objects have entered the virtual reachable state, but they will not be cleared. Once the reference object is inserted into the queue by the garbage collector, the return value of its get method will definitely be null, so the object can never be resurrected.
Registering a reference object into a reference queue does not create a reference between the queue and the reference object. If our reference object itself becomes unreachable, it cannot be inserted into the queue. Therefore, our application needs to maintain a strong reference to all referenced objects.
The ReferenceQueue class provides three methods to remove references in a queue:
The poll method allows a thread to query whether a reference is in a queue and performs a specific action when the reference exists in the queue. The remove method can handle more complex (less common) situations, where a dedicated thread is responsible for removing references from the queue and performing appropriate actions. The blocking behavior of these methods is the same as the blocking behavior defined in object.wait. For a specific reference, we can query whether it is in the queue by its isEnqueued method, or force it into the queue by calling its enqueue method, but usually this kind of plugging is done by the garbage collector.
Virtual references in the reference queue can be used to determine when an object can be recycled. We cannot access any object through virtual references, even if the object is reachable in other ways, because the get method of virtual reference always returns null. In fact, using virtual references to find the object to be recycled is the safest way, because weak references and soft references will be inserted into the queue after the object can be terminated, while virtual references are inserted into the queue after the terminating object is terminated, that is, the queue is inserted after the last moment when the object can perform certain operations, so it is absolutely safe. If you can, virtual references should always be used, because other references will have the possibility that finalize methods use endable objects.
Consider an example of a resource manager that can control access to external resource collections. Objects can request access to an external resource and do not end access until the operation is completed, after which they should return the resource used to the resource manager. If this resource is shared, its usage rights will be passed between multiple objects, and may even be passed between multiple threads, so it is difficult for us to determine which object is the last user of this resource, and thus it is difficult to determine which piece of code will be responsible for returning this resource. To handle this situation, the resource manager can achieve automatic recycling of this resource by associating a resource to a special object called a key. As long as the key object is reachable, we think that this resource is still in use; as long as the key object can be collected as garbage, the resource will be automatically released. The following code is an abstract representation of the above resources:
interface Resource{ void use(Object key, Object…args); void release(); }When a resource is obtained, its key object must be provided to the resource manager. For a returned Resource instance, this resource can only be used if it gets its corresponding key. This ensures that after the key is recycled, its corresponding resource can no longer be used, even if the Resource object representing this resource may still be accessible. Note that the Resource object does not store a strong reference to the key object, which is important because this prevents the key object from becoming unreachable, resulting in the resource being unretrievable. The implementation of Resource can be nested in the resource manager:
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 } } }The hash code of the key object is stored when the resource is created, and whenever the use method is called, it checks whether the same key is provided. The actual use of resources may also require synchronization, but for simplicity, we omit them here. The release method is responsible for releasing the resource. It can be called directly by the resource user after use, or by the resource manager when the key object is no longer referenced. Since we will use independent threads to monitor the reference queue, the release method must be synchronized and multiple calls must be allowed.
The actual resource manager has the following form:
public final class ResourceManager{ final ReferenceQueue
The key object can be any object, which gives resource users great flexibility compared to having the resource manager assign key objects. When the getResource method is called, a new Resource work mpl object will be created, and the key provided to the method will be passed to the new ResourceImpl object. Then a virtual reference is created, and its referential object is the key passed to the method, and then this virtual reference will be inserted into the resource manager's reference queue. The virtual reference and reference objects created in the end will be stored in the mapping table. This mapping table has two purposes: one is to keep all virtual reference objects reachable, and the other is to provide a convenient way to query the actual Resource object associated with each virtual reference. (An alternative is to subclass PhantomReference and store the Resource object in a field.)
If the key object becomes unreachable, the resource manager uses a separate "reaper" thread to process the resource. The shutdown method "close" the explorer by terminating the harvester thread (in response to an interrupt), causing the getResource method to throw an Ille-llleStateException exception. In this simple design, any references that plug in a queue after the Explorer is closed will not be processed. The actual harvester thread is as follows:
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 is an internal class, and the given harvester thread will run until the resource manager associated with it is closed. The thread blocks on the remove method until the virtual reference associated with a specific key is inserted into the reference queue. This virtual reference can obtain a reference to the Resource object from the mapping table, and then this "key-to-reference" pair will be removed from the mapping table. Immediately afterwards, its release method is called on the Resource object to release the resource. at last,
The virtual reference is cleared so that the key can be recycled.
As an alternative to using independent threads, any operation that calls the poll method on the reference queue and releases all resources whose keys have become unreachable can be replaced by the getResource "method, and the shutdow" method can also be used to perform the final poll operation. The semantics of the resource manager will depend on the actual resource type and resource usage pattern.
Designs using reference queues are much more reliable than designs that use terminations directly (especially virtual references). But we need to remember that the exact time and location of the reference object inserted into the reference queue is not certain, nor is we sure whether all pluggable references have been inserted into the reference queue when the application terminates. If we need to ensure that all resources can be released before the application terminates, we must install the necessary shutdown hook or use other protocols defined by the application to ensure that this is achieved.