Java/Collections Data Structure/SoftReference

Материал из Java эксперт
Перейти к: навигация, поиск

An implementation of Set that manages a map of soft references to the set values.

   
 
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
/**
 * An implementation of Set that manages a map of soft references to the set
 * values. The map is keyed by the value hashCode and so this is only useful for
 * value whose hashCode is a valid identity representation (String, primative
 * wrappers, etc).
 * 
 * @author Scott.Stark@jboss.org
 * @version $Revision: 2787 $
 */
@SuppressWarnings("unchecked")
public class SoftSet implements Set {
  private HashMap map = new HashMap();
  /** The queue of garbage collected soft references */
  private ReferenceQueue gcqueue = new ReferenceQueue();
  static class ComparableSoftReference extends SoftReference {
    private Integer key;
    ComparableSoftReference(Integer key, Object o, ReferenceQueue q) {
      super(o, q);
      this.key = key;
    }
    Integer getKey() {
      return key;
    }
  }
  static class ComparableSoftReferenceIterator implements Iterator {
    Iterator theIter;
    ComparableSoftReferenceIterator(Iterator theIter) {
      this.theIter = theIter;
    }
    public boolean hasNext() {
      return theIter.hasNext();
    }
    public Object next() {
      ComparableSoftReference csr = (ComparableSoftReference) theIter.next();
      return csr.get();
    }
    public void remove() {
      theIter.remove();
    }
  }
  /**
   * 
   */
  public SoftSet() {
  }
  public int size() {
    processQueue();
    return map.size();
  }
  public boolean isEmpty() {
    processQueue();
    return map.isEmpty();
  }
  public boolean contains(Object o) {
    processQueue();
    Integer key = new Integer(o.hashCode());
    boolean contains = map.containsKey(key);
    return contains;
  }
  public Iterator iterator() {
    processQueue();
    Iterator theIter = map.values().iterator();
    return new ComparableSoftReferenceIterator(theIter);
  }
  public Object[] toArray() {
    processQueue();
    return toArray(new Object[0]);
  }
  public Object[] toArray(Object[] a) {
    processQueue();
    int size = map.size();
    Object[] array = {};
    if (a.length >= size)
      array = a;
    Iterator iter = map.values().iterator();
    int index = 0;
    while (iter.hasNext()) {
      ComparableSoftReference csr = (ComparableSoftReference) iter.next();
      Object value = csr.get();
      // Create the correct array type
      if (array.length == 0) {
        if (value == null) {
          index++;
          continue;
        }
        Array.newInstance(value.getClass(), size);
      }
      array[index] = value;
      index++;
    }
    return array;
  }
  public boolean add(Object o) {
    processQueue();
    Integer key = new Integer(o.hashCode());
    ComparableSoftReference sr = new ComparableSoftReference(key, o, gcqueue);
    return map.put(key, sr) == null;
  }
  public boolean remove(Object o) {
    processQueue();
    Integer key = new Integer(o.hashCode());
    return map.remove(key) != null;
  }
  public boolean containsAll(Collection c) {
    processQueue();
    Iterator iter = c.iterator();
    boolean contains = true;
    while (iter.hasNext()) {
      Object value = iter.next();
      Integer key = new Integer(value.hashCode());
      contains &= map.containsKey(key);
    }
    return contains;
  }
  public boolean addAll(Collection c) {
    processQueue();
    Iterator iter = c.iterator();
    boolean added = false;
    while (iter.hasNext()) {
      Object value = iter.next();
      Integer key = new Integer(value.hashCode());
      ComparableSoftReference sr = new ComparableSoftReference(key, value, gcqueue);
      added |= map.put(key, sr) == null;
    }
    return added;
  }
  public boolean retainAll(Collection c) {
    Iterator iter = iterator();
    boolean removed = false;
    while (iter.hasNext()) {
      Object value = iter.next();
      if (c.contains(value) == false) {
        iter.remove();
        removed = true;
      }
    }
    return removed;
  }
  public boolean removeAll(Collection c) {
    processQueue();
    Iterator iter = c.iterator();
    boolean removed = false;
    while (iter.hasNext()) {
      Object value = iter.next();
      removed |= remove(value);
    }
    return removed;
  }
  public void clear() {
    while (gcqueue.poll() != null)
      ;
    map.clear();
  }
  public boolean equals(Object o) {
    return map.equals(o);
  }
  public int hashCode() {
    return map.hashCode();
  }
  /**
   * Iterate through the gcqueue for for any cleared reference, remove the
   * associated value from the underlying set.
   */
  private void processQueue() {
    ComparableSoftReference cr;
    while ((cr = (ComparableSoftReference) gcqueue.poll()) != null) {
      map.remove(cr.getKey());
    }
  }
}





A phantom reference is used to determine when an object is just about to be reclaimed.

  

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
public class Main {
  public static void main(String[] argv) throws Exception {
    ReferenceQueue rq = new ReferenceQueue();
    PhantomReference<String> pr = new PhantomReference<String>("object", rq);
    while (true) {
      Reference r = rq.remove();
      if (r == pr) {
        // about to be reclaimed.
        r.clear();
      }
    }
  }
}





A soft reference holds onto its referent until memory becomes low.

  
import java.lang.ref.SoftReference;
public class Main {
  public static void main(String[] argv) throws Exception {
    SoftReference<String> sr = new SoftReference<String>("object");
    Object o = sr.get();
    if (o != null) {
      System.out.println(o);
    } else {
      System.out.println("collected or has been reclaimed");
    }
  }
}





A weak reference is used to determine when an object is no longer being referenced.

  
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class Main {
  public static void main(String[] argv) throws Exception {
    ReferenceQueue rq = new ReferenceQueue();
    WeakReference<String> wr = new WeakReference<String>("string", rq);
    while (true) {
      Reference r = rq.remove();
      if (r == wr) {
        System.out.println("no longer referenced");
      }
    }
  }
}





Cache based on SoftReference

   
/*   Copyright 2004 The Apache Software Foundation
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *  limitations under the License.
 */
// Revised from xmlbeans
import java.util.HashMap;
import java.lang.ref.SoftReference;
/**
 * @author Cezar Andrei (cezar.andrei at bea.ru)
 *         Date: Apr 26, 2005
 */
public class SoftCache
{
    private HashMap map = new HashMap();
    public Object get(Object key)
    {
        SoftReference softRef = (SoftReference)map.get(key);
        if (softRef==null)
            return null;
        return softRef.get();
    }
    public Object put(Object key, Object value)
    {
        SoftReference softRef = (SoftReference)map.put(key, new SoftReference(value));
        if (softRef==null)
            return null;
        Object oldValue = softRef.get();
        softRef.clear();
        return oldValue;
    }
    public Object remove(Object key)
    {
        SoftReference softRef = (SoftReference)map.remove(key);
        if (softRef==null)
            return null;
        Object oldValue = softRef.get();
        softRef.clear();
        return oldValue;
    }
}





Soft ValueMap

  
/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
 * 
 * This program and the accompanying materials are made available under
 * the terms of the Common Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/cpl-v10.html
 * 
 * $Id: SoftValueMap.java,v 1.1.1.1 2004/05/09 16:57:55 vlad_r Exp $
 */
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
// ----------------------------------------------------------------------------
/**
 * MT-safety: an instance of this class is <I>not</I> safe for access from
 * multiple concurrent threads [even if access is done by a single thread at a
 * time]. The caller is expected to synchronize externally on an instance [the
 * implementation does not do internal synchronization for the sake of efficiency].
 * java.util.ConcurrentModificationException is not supported either.
 *
 * @author (C) 2002, Vlad Roubtsov
 */
public
final class SoftValueMap implements Map
{
    // public: ................................................................
    // TODO: for caching, does clearing of entries make sense? only to save
    // entry memory -- which does not make sense if the set of key values is not
    // growing over time... on the other hand, if the key set is unbounded,
    // entry clearing is needed so that the hash table does not get polluted with
    // empty-valued entries 
    // TODO: provide mode that disables entry clearing 
    // TODO: add shrinking rehashes (is it worth it?)
    /**
     * Equivalent to <CODE>SoftValueMap(1, 1)</CODE>.
     */
    public SoftValueMap ()
    {
        this (1, 1);
    }
    
    /**
     * Equivalent to <CODE>SoftValueMap(11, 0.75F, getClearCheckFrequency, putClearCheckFrequency)</CODE>.
     */
    public SoftValueMap (final int readClearCheckFrequency, final int writeClearCheckFrequency)
    {
        this (11, 0.75F, readClearCheckFrequency, writeClearCheckFrequency);
    }
    
    /**
     * Constructs a SoftValueMap with specified initial capacity, load factor,
     * and cleared value removal frequencies.
     *
     * @param initialCapacity initial number of hash buckets in the table
     * [may not be negative, 0 is equivalent to 1].
     * @param loadFactor the load factor to use to determine rehashing points
     * [must be in (0.0, 1.0] range].
     * @param readClearCheckFrequency specifies that every readClearCheckFrequency
     * {@link #get} should check for and remove all mappings whose soft values
     * have been cleared by the garbage collector [may not be less than 1].
     * @param writeClearCheckFrequency specifies that every writeClearCheckFrequency
     * {@link #put} should check for and remove all mappings whose soft values
     * have been cleared by the garbage collector [may not be less than 1].
     */
    public SoftValueMap (int initialCapacity, final float loadFactor, final int readClearCheckFrequency, final int writeClearCheckFrequency)
    {
        if (initialCapacity < 0)
            throw new IllegalArgumentException ("negative input: initialCapacity [" + initialCapacity + "]");
        if ((loadFactor <= 0.0) || (loadFactor >= 1.0 + 1.0E-6))
            throw new IllegalArgumentException ("loadFactor not in (0.0, 1.0] range: " + loadFactor);
        if (readClearCheckFrequency < 1)
            throw new IllegalArgumentException ("readClearCheckFrequency not in [1, +inf) range: " + readClearCheckFrequency);
        if (writeClearCheckFrequency < 1)
            throw new IllegalArgumentException ("writeClearCheckFrequency not in [1, +inf) range: " + writeClearCheckFrequency);
        
        if (initialCapacity == 0) initialCapacity = 1;
        
        m_valueReferenceQueue = new ReferenceQueue ();
        
        m_loadFactor = loadFactor;
        m_sizeThreshold = (int) (initialCapacity * loadFactor);
        
        m_readClearCheckFrequency = readClearCheckFrequency;
        m_writeClearCheckFrequency = writeClearCheckFrequency;
        
        m_buckets = new SoftEntry [initialCapacity];
    }
    
    
    // unsupported operations:
        
    public boolean equals (final Object rhs)
    {
        throw new UnsupportedOperationException ("not implemented: equals");
    }
    
    public int hashCode ()
    {
        throw new UnsupportedOperationException ("not implemented: hashCode");
    }
    
    
    /**
     * Overrides Object.toString() for debug purposes.
     */
    public String toString ()
    {
        final StringBuffer s = new StringBuffer ();
        debugDump (s);
        
        return s.toString ();
    }
    
    
    /**
     * Returns the number of key-value mappings in this map. Some of the values
     * may have been cleared already but not removed from the table.<P>
     *
     * <B>NOTE:</B> in contrast with the java.util.WeakHashMap implementation,
     * this is a constant time operation.
     */
    public int size ()
    {
        return m_size;
    }
    
    /**
     * Returns "false" is this map contains key-value mappings (even if some of
     * the values may have been cleared already but not removed from the table).<P>
     *
     * <B>NOTE:</B> in contrast with the java.util.WeakHashMap implementation,
     * this is a constant time operation.
     */
    public boolean isEmpty ()
    {
        return m_size == 0;
    }
    
    /**
     * Returns the value that is mapped to a given "key". Returns
     * null if (a) this key has never been mapped or (b) a previously mapped
     * value has been cleared by the garbage collector and removed from the table.
     *
     * @param key mapping key [may not be null].
     *
     * @return Object value mapping for "key" [can be null].
     */
    public Object get (final Object key)
    {
        if (key == null) throw new IllegalArgumentException ("null input: key");
        
        if ((++ m_readAccessCount % m_readClearCheckFrequency) == 0) removeClearedValues ();
        
        // index into the corresponding hash bucket:
        final int keyHashCode = key.hashCode ();
        final SoftEntry [] buckets = m_buckets;
        final int bucketIndex = (keyHashCode & 0x7FFFFFFF) % buckets.length;
        
        Object result = null; 
        
        // traverse the singly-linked list of entries in the bucket:
        for (SoftEntry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
        {
            final Object entryKey = entry.m_key;
            if (IDENTITY_OPTIMIZATION)
            {
                // note: this uses an early identity comparison opimization, making this a bit
                // faster for table keys that do not override equals() [Thread, etc]
                if ((key == entryKey) || ((keyHashCode == entryKey.hashCode ()) && key.equals (entryKey)))
                {
                    final Reference ref = entry.m_softValue;
                    result = ref.get (); // may return null to the caller
                    
                    // [see comment for ENQUEUE_FOUND_CLEARED_ENTRIES]
                    if (ENQUEUE_FOUND_CLEARED_ENTRIES && (result == null))
                    {
                        ref.enqueue ();
                    }
                    
                    return result;
                }
            }
            else
            {
                if ((keyHashCode == entryKey.hashCode ()) && key.equals (entryKey))
                {
                    final Reference ref = entry.m_softValue;
                    result = ref.get (); // may return null to the caller
                    
                    // [see comment for ENQUEUE_FOUND_CLEARED_ENTRIES]
                    if (ENQUEUE_FOUND_CLEARED_ENTRIES && (result == null))
                    {
                        ref.enqueue ();
                    }
                    
                    return result;
                }
            }
        }
        
        return null;
    }
    
    /**
     * Updates the table to map "key" to "value". Any existing mapping is overwritten.
     *
     * @param key mapping key [may not be null].
     * @param value mapping value [may not be null].
     *
     * @return Object previous value mapping for "key" [null if no previous mapping
     * existed or its value has been cleared by the garbage collector and removed from the table].
     */
    public Object put (final Object key, final Object value)
    {
        if (key == null) throw new IllegalArgumentException ("null input: key");
        if (value == null) throw new IllegalArgumentException ("null input: value");
        
        if ((++ m_writeAccessCount % m_writeClearCheckFrequency) == 0) removeClearedValues ();
            
        SoftEntry currentKeyEntry = null;
        
        // detect if "key" is already in the table [in which case, set "currentKeyEntry" to point to its entry]:
        
        // index into the corresponding hash bucket:
        final int keyHashCode = key.hashCode ();
        SoftEntry [] buckets = m_buckets;
        int bucketIndex = (keyHashCode & 0x7FFFFFFF) % buckets.length;
        
        // traverse the singly-linked list of entries in the bucket:
        for (SoftEntry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
        {
            final Object entryKey = entry.m_key;
            
            if (IDENTITY_OPTIMIZATION)
            {
                // note: this uses an early identity comparison opimization, making this a bit
                // faster for table keys that do not override equals() [Thread, etc]
                if ((key == entryKey) || ((keyHashCode == entryKey.hashCode ()) && key.equals (entryKey)))
                {
                    currentKeyEntry = entry;
                    break;
                }
            }
            else
            {
                if ((keyHashCode == entryKey.hashCode ()) && key.equals (entryKey))
                {
                    currentKeyEntry = entry;
                    break;
                }
            }
        }
        
        if (currentKeyEntry != null)
        {
            // replace the current value:
            
            final IndexedSoftReference ref = currentKeyEntry.m_softValue;
            final Object currentKeyValue = ref.get (); // can be null already [no need to work around the get() bug, though]
            
            if (currentKeyValue == null) ref.m_bucketIndex = -1; // disable removal by removeClearedValues() [need to do this because of the identity comparison there]
            currentKeyEntry.m_softValue = new IndexedSoftReference (value, m_valueReferenceQueue, bucketIndex);
            
            return currentKeyValue; // may return null to the caller
        }
        else
        {
            // add a new entry:
            
            if (m_size >= m_sizeThreshold) rehash ();
            
            // recompute the hash bucket index:
            buckets = m_buckets;
            bucketIndex = (keyHashCode & 0x7FFFFFFF) % buckets.length;
            final SoftEntry bucketListHead = buckets [bucketIndex];
            final SoftEntry newEntry = new SoftEntry (m_valueReferenceQueue, key, value, bucketListHead, bucketIndex);
            buckets [bucketIndex] = newEntry;
            
            ++ m_size;
            
            return null;
        }
    }
    
    public Object remove (final Object key)
    {
        if (key == null) throw new IllegalArgumentException ("null input: key");
        
        if ((++ m_writeAccessCount % m_writeClearCheckFrequency) == 0) removeClearedValues ();
        // index into the corresponding hash bucket:
        final int keyHashCode = key.hashCode ();
        final SoftEntry [] buckets = m_buckets;
        final int bucketIndex = (keyHashCode & 0x7FFFFFFF) % buckets.length;
        
        Object result = null;
        // traverse the singly-linked list of entries in the bucket:
        for (SoftEntry entry = buckets [bucketIndex], prev = null; entry != null; prev = entry, entry = entry.m_next)
        {
            final Object entryKey = entry.m_key;
            
            if ((IDENTITY_OPTIMIZATION && (entryKey == key)) || ((keyHashCode == entryKey.hashCode ()) && key.equals (entryKey)))
            {
                if (prev == null) // head of the list
                {
                    buckets [bucketIndex] = entry.m_next;
                }
                else
                {
                    prev.m_next = entry.m_next;
                }
                
                final IndexedSoftReference ref = entry.m_softValue; 
                result = ref.get (); // can be null already [however, no need to work around 4485942]
                
                // [regardless of whether the value has been enqueued or not, disable its processing by removeClearedValues() since the entire entry is removed here]
                ref.m_bucketIndex = -1;
                
                // help GC:
                entry.m_softValue = null;
                entry.m_key = null;
                entry.m_next = null;
                entry = null;
            
                -- m_size;
                break;
            }
        }
        
        return result;
    }
    
    public void clear ()
    {
        final SoftEntry [] buckets = m_buckets;
        
        for (int b = 0, bLimit = buckets.length; b < bLimit; ++ b)
        {
            for (SoftEntry entry = buckets [b]; entry != null; )
            {
                final SoftEntry next = entry.m_next; // remember next pointer because we are going to reuse this entry
                // [regardless of whether the value has been enqueued or not, disable its processing by removeClearedValues()]
                entry.m_softValue.m_bucketIndex = -1;
                
                // help GC:
                entry.m_softValue = null;
                entry.m_next = null;
                entry.m_key = null;
                
                entry = next;
            }
            
            buckets [b] = null;
        }
        
        m_size = 0;
        m_readAccessCount = 0;
        m_writeAccessCount = 0;
    }

    // unsupported operations:
    
    public boolean containsKey (final Object key)
    {
        throw new UnsupportedOperationException ("not implemented: containsKey");
    }
    
    public boolean containsValue (final Object value)
    {
        throw new UnsupportedOperationException ("not implemented: containsValue");
    }
        
    public void putAll (final Map map)
    {
        throw new UnsupportedOperationException ("not implemented: putAll");
    }
    
    public Set keySet ()
    {
        throw new UnsupportedOperationException ("not implemented: keySet");
    }
    
    public Set entrySet ()
    {
        throw new UnsupportedOperationException ("not implemented: entrySet");
    }
    public Collection values ()
    {
        throw new UnsupportedOperationException ("not implemented: values");
    }
    
    // protected: .............................................................
    // package: ...............................................................
    
    
    void debugDump (final StringBuffer out)
    {
        if (out != null)
        {
            out.append (getClass ().getName ().concat ("@").concat (Integer.toHexString (System.identityHashCode (this)))); out.append (EOL);
            out.append ("size = " + m_size + ", bucket table size = " + m_buckets.length + ", load factor = " + m_loadFactor + EOL);
            out.append ("size threshold = " + m_sizeThreshold + ", get clear frequency = " + m_readClearCheckFrequency + ", put clear frequency = " + m_writeClearCheckFrequency + EOL);
            out.append ("get count: " + m_readAccessCount + ", put count: " + m_writeAccessCount + EOL);
        }
    }
    // private: ...............................................................

    /**
     * An extension of WeakReference that can store an index of the bucket it
     * is associated with.
     */
    static class IndexedSoftReference extends SoftReference
    {
        IndexedSoftReference (final Object referent, ReferenceQueue queue, final int bucketIndex)
        {
            super (referent, queue);
            
            m_bucketIndex = bucketIndex;
        }
        
        int m_bucketIndex;
        
    } // end of nested class
    
    
    /**
     * The structure used for chaining colliding keys.
     */
    static class SoftEntry
    {
        SoftEntry (final ReferenceQueue valueReferenceQueue, final Object key, Object value, final SoftEntry next, final int bucketIndex)
        {
            m_key = key;
            
            m_softValue = new IndexedSoftReference (value, valueReferenceQueue, bucketIndex); // ... do not retain a strong reference to the value
            value = null;
            
            m_next = next;
        }
        
        IndexedSoftReference m_softValue; // soft reference to the value [never null]
        Object m_key;  // strong reference to the key [never null]
        
        SoftEntry m_next; // singly-linked list link
        
    } // end of nested class
    
    /**
     * Re-hashes the table into a new array of buckets. During the process
     * cleared value entries are discarded, making for another efficient cleared
     * value removal method.
     */
    private void rehash ()
    {
        // TODO: it is possible to run this method twice, first time using the 2*k+1 prime sequencer for newBucketCount
        // and then with that value reduced to actually shrink capacity. As it is right now, the bucket table can
        // only grow in size
        
        final SoftEntry [] buckets = m_buckets;
        
        final int newBucketCount = (m_buckets.length << 1) + 1;
        final SoftEntry [] newBuckets = new SoftEntry [newBucketCount];
        
        int newSize = 0;
        
        // rehash all entry chains in every bucket:
        for (int b = 0, bLimit = buckets.length; b < bLimit; ++ b)
        {
            for (SoftEntry entry = buckets [b]; entry != null; )
            {
                final SoftEntry next = entry.m_next; // remember next pointer because we are going to reuse this entry
                
                IndexedSoftReference ref = entry.m_softValue; // get the soft value reference
                                
                Object entryValue = ref.get (); // convert the soft reference to a local strong one
            
                // skip entries whose keys have been cleared: this also saves on future removeClearedValues() work
                if (entryValue != null)
                {
                    // [assertion: "softValue" couldn"t have been enqueued already and can"t be enqueued until strong reference in "entryKey" is nulled out]
                    
                    // index into the corresponding new hash bucket:
                    final int entryKeyHashCode = entry.m_key.hashCode ();
                    final int newBucketIndex = (entryKeyHashCode & 0x7FFFFFFF) % newBucketCount;
                    
                    final SoftEntry bucketListHead = newBuckets [newBucketIndex];
                    entry.m_next = bucketListHead;
                    newBuckets [newBucketIndex] = entry;
                    
                    // adjust bucket index:
                    ref.m_bucketIndex = newBucketIndex;
            
                    ++ newSize;
                    
                    entryValue = null;
                }
                else
                {
                    // ["softValue" may or may not have been enqueued already]
                    
                    // adjust bucket index:
                    // [regardless of whether "softValue" has been enqueued or not, disable its removal by removeClearedValues() since the buckets get restructured]
                    ref.m_bucketIndex = -1;
                }
                
                entry = next;
            }
        }
        
        if (DEBUG)
        {
            if (m_size > newSize) System.out.println ("DEBUG: rehash() cleared " + (m_size - newSize) + " values, new size = " + newSize);
        }
        
        m_size = newSize;
        m_sizeThreshold = (int) (newBucketCount * m_loadFactor);
        m_buckets = newBuckets;
    }
    
    /**
     * Removes all entries whose soft values have been cleared _and_ enqueued.
     * See comments below for why this is safe wrt to rehash().
     */
    private void removeClearedValues ()
    {
        int count = 0;
        
next:   for (Reference _ref; (_ref = m_valueReferenceQueue.poll ()) != null; )
        {
            // remove entry containing "_ref" using its bucket index and identity comparison:
            
            // index into the corresponding hash bucket:
            final int bucketIndex = ((IndexedSoftReference) _ref).m_bucketIndex;
            
            if (bucketIndex >= 0) // skip keys that were already removed by rehash()
            {
                // [assertion: this reference was not cleared when the last rehash() ran and so its m_bucketIndex is correct]
                
                // traverse the singly-linked list of entries in the bucket:
                for (SoftEntry entry = m_buckets [bucketIndex], prev = null; entry != null; prev = entry, entry = entry.m_next)
                {
                    if (entry.m_softValue == _ref)
                    {
                        if (prev == null) // head of the list
                        {
                            m_buckets [bucketIndex] = entry.m_next;
                        }
                        else
                        {
                            prev.m_next = entry.m_next;
                        }
                    
                        entry.m_softValue = null;
                        entry.m_key = null;
                        entry.m_next = null;
                        entry = null;
                    
                        -- m_size;
                        
                        if (DEBUG) ++ count;
                        continue next;
                    }
                }
                
                // no match found this can happen if a soft value got replaced by a put
                
                final StringBuffer msg = new StringBuffer ("removeClearedValues(): soft reference [" + _ref + "] did not match within bucket #" + bucketIndex + EOL);
                debugDump (msg);
            
                throw new Error (msg.toString ());
            }
            // else: it has already been removed by rehash() or other methods
        }
        
        if (DEBUG)
        {
            if (count > 0) System.out.println ("DEBUG: removeClearedValues() cleared " + count + " keys, new size = " + m_size);
        }
    }
    
    
    private final ReferenceQueue m_valueReferenceQueue; // reference queue for all references used by SoftEntry objects used by this table
    private final float m_loadFactor; // determines the setting of m_sizeThreshold
    private final int m_readClearCheckFrequency, m_writeClearCheckFrequency; // parameters determining frequency of running removeClearedKeys() by get() and put()/remove(), respectively
    
    private SoftEntry [] m_buckets; // table of buckets
    private int m_size; // number of values in the table, not cleared as of last check
    private int m_sizeThreshold; // size threshold for rehashing
    private int m_readAccessCount, m_writeAccessCount;
    
    private static final String EOL = System.getProperty ("line.separator", "\n");
    
    private static final boolean IDENTITY_OPTIMIZATION          = true;
    
    // setting this to "true" is an optimization and a workaround for bug 4485942:
    private static final boolean ENQUEUE_FOUND_CLEARED_ENTRIES  = true; 
    
    private static final boolean DEBUG = false;
} // end of class
// ----------------------------------------------------------------------------





Testing PhantomReference

  
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.HashMap;
public class Main {
  public static void main(String[] args) {
    ReferenceQueue referenceQueue = new ReferenceQueue();
    Object object = new Object() {
      public String toString() {
        return "Referenced Object";
      }
    };
    Object data = new Object() {
      public String toString() {
        return "Data";
      }
    };
    HashMap map = new HashMap();
    Reference reference = null;
    System.out.println("Testing PhantomReference.");
    reference = new PhantomReference(object, referenceQueue);
    map.put(reference, data);
    System.out.println(reference.get());
    System.out.println(map.get(reference));
    System.out.println(reference.isEnqueued());
    System.gc();
    System.out.println(reference.get());
    System.out.println(map.get(reference));
    System.out.println(reference.isEnqueued());
    object = null;
    data = null;
    System.gc();
    System.out.println(reference.get());
    System.out.println(map.get(reference));
    System.out.println(reference.isEnqueued());
  }
}





Testing SoftReference

  
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.HashMap;
public class Main {
  public static void main(String[] args) {
    ReferenceQueue referenceQueue = new ReferenceQueue();
    Object object = new Object() {
      public String toString() {
        return "Referenced Object";
      }
    };
    Object data = new Object() {
      public String toString() {
        return "Data";
      }
    };
    HashMap map = new HashMap();
    Reference reference = new SoftReference(object, referenceQueue);
    map.put(reference, data);
    System.out.println(reference.get());
    System.out.println(map.get(reference));
    System.out.println(reference.isEnqueued());
    System.gc();
    System.out.println(reference.get());
    System.out.println(map.get(reference));
    System.out.println(reference.isEnqueued());
    object = null;
    data = null;
    System.gc();
    System.out.println(reference.get());
    System.out.println(map.get(reference));
    System.out.println(reference.isEnqueued());
  }
}





Testing WeakReference

  
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.HashMap;
public class Main {
  public static void main(String[] args) {
    ReferenceQueue referenceQueue = new ReferenceQueue();
    Object object = new Object() {
      public String toString() {
        return "Referenced Object";
      }
    };
    Object data = new Object() {
      public String toString() {
        return "Data";
      }
    };
    HashMap map = new HashMap();
    Reference reference = null;
    System.out.println("Testing WeakReference.");
    reference = new WeakReference(object, referenceQueue);
    map.put(reference, data);
    System.out.println(reference.get());
    System.out.println(map.get(reference));
    System.out.println(reference.isEnqueued());
    System.gc();
    System.out.println(reference.get());
    System.out.println(map.get(reference));
    System.out.println(reference.isEnqueued());
    object = null;
    data = null;
    System.gc();
    System.out.println(reference.get());
    System.out.println(map.get(reference));
    System.out.println(reference.isEnqueued());
  }
}