home *** CD-ROM | disk | FTP | other *** search
Java Source | 1998-03-20 | 15.1 KB | 552 lines |
- /*
- * @(#)SoftReference.java 1.19 98/03/18
- *
- * Copyright 1997, 1998 by Sun Microsystems, Inc.,
- * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
- * All rights reserved.
- *
- * This software is the confidential and proprietary information
- * of Sun Microsystems, Inc. ("Confidential Information"). You
- * shall not disclose such Confidential Information and shall use
- * it only in accordance with the terms of the license agreement
- * you entered into with Sun.
- */
-
- package java.lang.ref;
- import java.util.Comparator;
- import java.util.TreeSet;
- import java.util.SortedMap;
- import java.util.TreeMap;
-
-
- /**
- * The <code>SoftReference</code> class implements soft reference objects.
- * When memory gets tight, an instance of this class may be cleared
- * automatically if its referent is reachable only via
- * <code>SoftReference</code> instances and, perhaps, via some guarded, weak,
- * or phantom references. The <code>SoftReference</code> class makes a best
- * effort to clear all <code>SoftReference</code> instances, in approximate
- * least-recently-used order, before the virtual machine throws an
- * <code>OutOfMemoryError</code>.
- *
- * @version 1.19, 98/03/18
- * @author Mark Reinhold
- * @since JDK1.2
- * @see java.lang.ref.Reference
- * @see java.lang.Runtime.MemoryAdvice
- */
-
- public class SoftReference extends Reference {
-
-
- /* Each SoftReference instance contains two internal objects:
-
- An internal Shadow instance, which is a weak reference pointing to the
- SoftReference. The Shadow is used to track the SoftReference itself.
- Each Shadow contains a timestamp, which is updated each time its
- associated SoftReference is dereferenced; the timestamp is used to
- implement clearing in an order approximating least-recently-used.
-
- An internal Guard instance, which is a guarded reference to the actual
- referent of the SoftReference. The Guard is used to track the
- reachability of the SoftReference's referent.
-
- At any point in time a SoftReference is considered to be either active,
- inactive, or cleared. The referent of an active SoftReference is likely
- to be in use; the referent of an inactive SoftReference is likely not to
- be in use; the referent of a cleared SoftReference is unknown. We
- maintain a set of active Shadows and a map of inactive Shadows, the
- latter ordered by timestamp values. (It would be more efficient to use
- a linked list for the former and a heap for the latter, but it is more
- expedient right now to use a TreeSet and a TreeMap, respectively.)
-
- A SoftReference is initially active. If the GC detects that its
- referent has become guardedly-reachable and enqueues the associated
- Guard, then the SoftReference is made inactive. When a inactive
- SoftReference is dereferenced, it is made active again and a new Guard
- is created for it. A new Guard is also created when an active
- SoftReference is dereferenced but its Guard has already been enqueued.
-
- A high-priority "sweeper" thread continually monitors the memory advice
- dispensed by the GC. As the advice becomes more dire, it gets more
- aggressive about clearing inactive SoftReferences in least-recently-used
- order. If the advice state ever reaches RED, it will also clear
- active SoftReferences.
-
- Shadow instances also serve as the locus of synchronization. Nearly all
- internal operations on a SoftReference, its Guard, and its Shadow
- synchronize on the Shadow. (The only exception is that the synchronized
- Guard operations may be invoked if no Shadow is locked.) This design
- prevents any synchronization that might be done by the user from
- interfering with the internal synchronization required in this class.
- */
-
-
- /* Debugging support */
- static private boolean debug; /* Set by static initializer, below */
-
- static private void dbg(String s) {
- java.io.PrintStream o = System.err;
- if (o != null) {
- o.println("<GC: SoftReference: " + s + ">");
- o.flush();
- }
- }
-
-
- /* Reference queue for Shadow and Guard objects */
- static private ReferenceQueue softQueue = new ReferenceQueue();
-
-
- static private class Shadow extends WeakReference implements Comparable {
-
- /* Instance state */
-
- private long timestamp;
- private boolean active;
-
- public boolean isActive() {
- return active;
- }
-
- public int compareTo(Object o) {
- Shadow s = (Shadow)o;
- if (this.timestamp < s.timestamp) return -1;
- if (this.timestamp > s.timestamp) return +1;
- return 0;
- }
-
-
- /* Timestamp clock */
-
- static private class Clock {
- private long clock = 0;
- private synchronized long tick() {
- return this.clock++;
- }
- }
-
- static private Clock clock = new Clock();
-
- public void touch() {
- this.timestamp = clock.tick();
- }
-
-
- /* Collections of active and inactive shadows */
-
- static private TreeSet activeSet
- = new TreeSet(new Comparator() {
- public int compare(Object o1, Object o2) {
- int h1 = o1.hashCode();
- int h2 = o2.hashCode();
- if (h1 < h2) return -1;
- if (h1 > h2) return +1;
- return 0; /* ## Assumes identity-hash uniqueness */
- }});
-
- static private SortedMap inactive = new TreeMap();
- static private class Lock { };
- static private Lock lock = new Lock(); /* Collection lock */
-
-
- /* Collection operations, which assume that the invoker has already
- locked this instance */
-
- private void activate() {
- synchronized (lock) {
- if (!activeSet.add(this)) {
- throw new InternalError("Shadow already in active set");
- }
- this.active = true;
- }
- }
-
- public void deActivate() {
- synchronized (lock) {
- if (!this.active) {
- throw new InternalError("Shadow not active");
- }
- if (!activeSet.remove(this)) {
- throw new InternalError("Shadow not in active set " +
- this + " " +
- ((this.get() != null) ?
- this.get().toString() : "null"));
- }
- inactive.put(this, this);
- this.active = false;
- }
- }
-
- public void reActivate() {
- synchronized (lock) {
- if (this.active) {
- throw new InternalError("Shadow not inactive");
- }
- if (inactive.remove(this) == null) {
- throw new InternalError("Shadow not in inactive map");
- }
- if (!activeSet.add(this)) {
- throw new InternalError("Shadow already in active set");
- }
- this.active = true;
- }
- }
-
- public void drop() {
- synchronized (lock) {
- if (this.active) {
- if (!activeSet.remove(this)) {
- throw new InternalError("Shadow not in active set");
- }
- } else {
- if (inactive.remove(this) == null) {
- throw new InternalError("Shadow not in inactive map");
- }
- }
- }
- }
-
- static public int getInactiveCount() {
- synchronized (lock) {
- return inactive.size();
- }
- }
-
- static public Shadow getMostInactiveShadow() {
- synchronized (lock) {
- if (inactive.size() == 0) return null;
- return (Shadow)(inactive.firstKey());
- }
- }
-
- static public Shadow getSomeActiveShadow() {
- synchronized (lock) {
- if (activeSet.size() == 0) return null;
- return (Shadow)(activeSet.first());
- }
- }
-
-
- /* Constructor */
-
- Shadow(SoftReference sr) {
- super(sr, SoftReference.softQueue);
- this.activate();
- this.touch();
- }
-
- }
-
-
- static private class Guard extends GuardedReference {
-
- private Shadow shadow;
-
- Guard(Shadow s, Object referent) {
- super(referent, SoftReference.softQueue);
- this.shadow = s;
- }
-
- /* The only exception to the rule that all operations on a Guard must
- be synchronized on the associated Shadow is that the following two
- methods may be called as long as no Shadow locks are held */
-
- synchronized public Shadow getShadow() {
- return shadow;
- }
-
- synchronized public void clear() {
- super.clear();
- this.shadow = null;
- }
-
- }
-
-
- /* Instance invariants
- The guard and shadow fields of a cleared instance are null.
- A non-cleared instance is either in the active set or the inactive map.
- The guard of an inactive instance has been enqueued and then dequeued.
- Timestamps of inactive instances are never updated.
- The Shadow of an instance is temporally unique; once set, the shadow
- field is only ever cleared.
- */
-
- /* The Guard for the referent of this SoftReference instance, or null if
- this instance has been cleared */
- private Guard guard;
-
- /* The Shadow for this SoftReference instance, or null if this instance
- has been cleared */
- private Shadow shadow;
-
-
- /* Process any Guards or Shadows that have been added to the reference
- queue by the garbage collector. When a Guard is enqueued then the
- corresponding SoftReference is made inactive. When a Shadow is
- enqueued then its SoftReference is dropped. This method is called by
- each of the public methods in order to clean things up incrementally.
- It is also called by the sweeper thread when the sweeper must clear
- every SoftReference.
- */
- static private void processQueue(boolean clearAll) {
- Reference r;
- while ((r = softQueue.poll()) != null) {
- if (r instanceof Guard) {
- Guard g = (Guard)r;
- Shadow s = g.getShadow();
- if (s != null) {
- synchronized (s) {
- SoftReference sr = (SoftReference)(s.get());
- if ((sr != null) && (sr.guard == g)) {
- if (clearAll) {
- sr.reallyClear();
- } else if (s.isActive()) {
- s.deActivate();
- }
- } else {
- /* This guard's SoftReference was cleared
- before we locked the shadow */
- g.clear();
- }
- }
- } else {
- /* This guard has already been dissociated from its
- shadow */
- g.clear();
- }
- } else if (r instanceof Shadow) {
- /* The SoftReference held by this shadow has become
- only weakly reachable, so remove all traces of it */
- Shadow s = (Shadow)r;
- synchronized (s) {
- SoftReference sr = (SoftReference)(s.get());
- if (sr != null) {
- sr.reallyClear();
- }
- }
- } else {
- throw new InternalError("Unknown queue-entry type");
- }
- }
- }
-
-
- /* -- Constructors -- */
-
- /**
- * Construct a new soft reference that refers to the given object.
- */
- public SoftReference(Object referent) {
- super(null); /* ## Leaves Reference.referent field unused */
-
- /* We needn't worry about race conditions here -- this new
- SoftReference is strongly reachable for the duration of the
- constructor invocation, so there's no chance of the Shadow instance
- being enqueued until sometime after the constructor returns */
- Shadow s = new Shadow(this);
- this.shadow = s;
-
- /* It is extremely unlikely that the Guard will be enqueued before we
- set the guard field, but we synchronize anyway just in case */
- synchronized (s) {
- this.guard = new Guard(s, referent);
- }
-
- processQueue(false);
- }
-
- /**
- * Construct a new soft reference that refers to the given object and is
- * registered with the given queue.
- */
- public SoftReference(Object referent, ReferenceQueue queue) {
- this(referent);
- this.queue = queue;
- }
-
-
- /* -- Accessors -- */
-
- /**
- * Return the object to which this soft reference refers, or
- * <code>null</code> if the reference has been cleared by the garbage
- * collector due to lack of space.
- *
- * <p><em>Implementation note:</em> This method may allocate a small object
- * and adjust an internal data structure, so it is not quite as fast as the
- * <code>get</code> methods of the other <code>Reference</code> types.
- */
- public Object get() {
- processQueue(false);
- Shadow s = this.shadow;
- if (s == null) {
- /* This SoftReference has been cleared */
- return null;
- }
- synchronized (s) {
- if (this.shadow == s) {
-
- Guard g = this.guard;
- if (g == null) {
- throw new InternalError("SoftReference has shadow but no guard");
- }
- Object o = g.get();
-
- /* At this point the referent is strongly reachable; if the
- Guard hasn't been enqueued yet, then that can't happen until
- we return */
- boolean active = s.isActive();
- if (!active || g.isEnqueued()) {
- this.guard = new Guard(s, o);
- g.clear();
- }
- if (!active) {
- s.reActivate();
- }
- s.touch();
- return o;
-
- } else {
- /* SoftReference was cleared before we locked it */
- return null;
- }
- }
- }
-
-
- /**
- * Clear this soft reference.
- */
- public void clear() {
- processQueue(false);
- Shadow s = this.shadow;
- if (s == null) return;
- synchronized (s) {
- if (this.shadow == s) {
- reallyClear();
- }
- }
- }
-
-
- /**
- * Really clear this soft reference, assuming that it has not yet been
- * cleared and that its shadow is already locked.
- */
- private void reallyClear() {
- this.shadow.drop();
- this.shadow.clear();
- Guard g = this.guard;
- if (g == null) {
- throw new InternalError("SoftReference has shadow but no guard");
- }
- g.clear();
- this.guard = null;
- this.shadow = null;
- if (this.queue != null) {
- this.enqueue();
- }
- }
-
-
- private static class Sweeper implements Runnable {
- private final Class sr /* Don't let SoftReference be unloaded */
- = SoftReference.class;
- private Runtime rt = Runtime.getRuntime();
-
- static private void sweep(Shadow s) {
- if (s == null) return;
- synchronized (s) {
- SoftReference sr = (SoftReference)s.get();
- if ((sr == null) || (sr.shadow != s)) {
- s.drop();
- s.clear();
- if (debug) {
- dbg("sweeper: Dropped " +
- ((sr == null) ? s.toString() : sr.toString()));
- }
- } else {
- sr.reallyClear();
- if (debug) {
- dbg("sweeper: Cleared " + sr);
- }
- }
- }
- }
-
- static private void sweepSome(int divisor) {
- processQueue(false);
- int c = Shadow.getInactiveCount();
- int n = c / divisor;
- if (debug) {
- dbg("sweeper: Trying to clear " + n + " of " + c);
- }
- int i;
- for (i = 0; i < n; i++) {
- Shadow s = Shadow.getMostInactiveShadow();
- if (s == null) break;
- sweep(s);
- }
- if (debug) {
- dbg("sweeper: Cleared " + i);
- }
- }
-
- static private void sweepAll() {
- if (debug) {
- dbg("sweeper: Clearing everything");
- }
- processQueue(true);
- sweepSome(1);
- Shadow s;
- while ((s = Shadow.getSomeActiveShadow()) != null) {
- sweep(s);
- }
- if (debug) {
- dbg("sweeper: Done clearing everything");
- }
- }
-
- public void run() {
- int ma = rt.getMemoryAdvice();
- for (;;) {
- switch (ma) { /* ## Tune this */
- case Runtime.MemoryAdvice.GREEN: break;
- case Runtime.MemoryAdvice.YELLOW: sweepSome(3); break;
- case Runtime.MemoryAdvice.ORANGE: sweepSome(1); break;
- case Runtime.MemoryAdvice.RED: sweepAll(); break;
- }
- try {
- ma = rt.waitForMemoryAdvice(ma);
- } catch (InterruptedException x) {
- continue;
- }
- }
- }
-
- }
-
- private static Thread sweeper;
-
-
- static {
- try {
- java.security.AccessController.beginPrivileged();
- debug = Boolean.getBoolean("java.lang.ref.SoftReference.debug");
- ThreadGroup tg = Thread.currentThread().getThreadGroup();
- for (ThreadGroup tgn = tg;
- tgn != null;
- tg = tgn, tgn = tg.getParent());
- sweeper = new Thread(tg, new Sweeper(), "SoftReference sweeper");
- sweeper.setDaemon(true);
- sweeper.setPriority(Thread.MAX_PRIORITY - 1);
- sweeper.start();
- } finally {
- java.security.AccessController.endPrivileged();
- }
- }
-
- }
-