home *** CD-ROM | disk | FTP | other *** search
/ Java 1.2 How-To / JavaHowTo.iso / 3rdParty / jbuilder / unsupported / JDK1.2beta3 / SOURCE / SRC.ZIP / java / lang / ref / SoftReference.java < prev    next >
Encoding:
Java Source  |  1998-03-20  |  15.1 KB  |  552 lines

  1. /*
  2.  * @(#)SoftReference.java    1.19 98/03/18
  3.  *
  4.  * Copyright 1997, 1998 by Sun Microsystems, Inc.,
  5.  * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
  6.  * All rights reserved.
  7.  *
  8.  * This software is the confidential and proprietary information
  9.  * of Sun Microsystems, Inc. ("Confidential Information").  You
  10.  * shall not disclose such Confidential Information and shall use
  11.  * it only in accordance with the terms of the license agreement
  12.  * you entered into with Sun.
  13.  */
  14.  
  15. package java.lang.ref;
  16. import java.util.Comparator;
  17. import java.util.TreeSet;
  18. import java.util.SortedMap;
  19. import java.util.TreeMap;
  20.  
  21.  
  22. /**
  23.  * The <code>SoftReference</code> class implements soft reference objects.
  24.  * When memory gets tight, an instance of this class may be cleared
  25.  * automatically if its referent is reachable only via
  26.  * <code>SoftReference</code> instances and, perhaps, via some guarded, weak,
  27.  * or phantom references.  The <code>SoftReference</code> class makes a best
  28.  * effort to clear all <code>SoftReference</code> instances, in approximate
  29.  * least-recently-used order, before the virtual machine throws an
  30.  * <code>OutOfMemoryError</code>.
  31.  *
  32.  * @version     1.19, 98/03/18
  33.  * @author    Mark Reinhold
  34.  * @since    JDK1.2
  35.  * @see         java.lang.ref.Reference
  36.  * @see         java.lang.Runtime.MemoryAdvice
  37.  */
  38.  
  39. public class SoftReference extends Reference {
  40.  
  41.  
  42.     /* Each SoftReference instance contains two internal objects:
  43.  
  44.          An internal Shadow instance, which is a weak reference pointing to the
  45.      SoftReference.  The Shadow is used to track the SoftReference itself.
  46.      Each Shadow contains a timestamp, which is updated each time its
  47.      associated SoftReference is dereferenced; the timestamp is used to
  48.      implement clearing in an order approximating least-recently-used.
  49.  
  50.      An internal Guard instance, which is a guarded reference to the actual
  51.      referent of the SoftReference.  The Guard is used to track the
  52.      reachability of the SoftReference's referent.
  53.  
  54.        At any point in time a SoftReference is considered to be either active,
  55.        inactive, or cleared.  The referent of an active SoftReference is likely
  56.        to be in use; the referent of an inactive SoftReference is likely not to
  57.        be in use; the referent of a cleared SoftReference is unknown.  We
  58.        maintain a set of active Shadows and a map of inactive Shadows, the
  59.        latter ordered by timestamp values.  (It would be more efficient to use
  60.        a linked list for the former and a heap for the latter, but it is more
  61.        expedient right now to use a TreeSet and a TreeMap, respectively.)
  62.  
  63.        A SoftReference is initially active.  If the GC detects that its
  64.        referent has become guardedly-reachable and enqueues the associated
  65.        Guard, then the SoftReference is made inactive.  When a inactive
  66.        SoftReference is dereferenced, it is made active again and a new Guard
  67.        is created for it.  A new Guard is also created when an active
  68.        SoftReference is dereferenced but its Guard has already been enqueued.
  69.  
  70.        A high-priority "sweeper" thread continually monitors the memory advice
  71.        dispensed by the GC.  As the advice becomes more dire, it gets more
  72.        aggressive about clearing inactive SoftReferences in least-recently-used
  73.        order.  If the advice state ever reaches RED, it will also clear
  74.        active SoftReferences.
  75.  
  76.        Shadow instances also serve as the locus of synchronization.  Nearly all
  77.        internal operations on a SoftReference, its Guard, and its Shadow
  78.        synchronize on the Shadow.  (The only exception is that the synchronized
  79.        Guard operations may be invoked if no Shadow is locked.)  This design
  80.        prevents any synchronization that might be done by the user from
  81.        interfering with the internal synchronization required in this class.
  82.     */
  83.  
  84.  
  85.     /* Debugging support */
  86.     static private boolean debug;    /* Set by static initializer, below */
  87.  
  88.     static private void dbg(String s) {
  89.     java.io.PrintStream o = System.err;
  90.     if (o != null) {
  91.         o.println("<GC: SoftReference: " + s + ">");
  92.         o.flush();
  93.     }
  94.     }
  95.  
  96.  
  97.     /* Reference queue for Shadow and Guard objects */
  98.     static private ReferenceQueue softQueue = new ReferenceQueue();
  99.  
  100.  
  101.     static private class Shadow extends WeakReference implements Comparable {
  102.  
  103.     /* Instance state */
  104.  
  105.     private long timestamp;
  106.     private boolean active;
  107.  
  108.     public boolean isActive() {
  109.         return active;
  110.     }
  111.  
  112.     public int compareTo(Object o) {
  113.         Shadow s = (Shadow)o;
  114.         if (this.timestamp < s.timestamp) return -1;
  115.         if (this.timestamp > s.timestamp) return +1;
  116.         return 0;
  117.     }
  118.  
  119.  
  120.     /* Timestamp clock */
  121.  
  122.     static private class Clock {
  123.         private long clock = 0;
  124.         private synchronized long tick() {
  125.         return this.clock++;
  126.         }
  127.     }
  128.  
  129.     static private Clock clock = new Clock();
  130.  
  131.     public void touch() {
  132.         this.timestamp = clock.tick();
  133.     }
  134.  
  135.  
  136.     /* Collections of active and inactive shadows */
  137.  
  138.     static private TreeSet activeSet
  139.         = new TreeSet(new Comparator() {
  140.         public int compare(Object o1, Object o2) {
  141.             int h1 = o1.hashCode();
  142.             int h2 = o2.hashCode();
  143.             if (h1 < h2) return -1;
  144.             if (h1 > h2) return +1;
  145.             return 0;    /* ## Assumes identity-hash uniqueness */
  146.         }});
  147.  
  148.     static private SortedMap inactive = new TreeMap();
  149.     static private class Lock { };
  150.     static private Lock lock = new Lock();    /* Collection lock */
  151.  
  152.  
  153.     /* Collection operations, which assume that the invoker has already
  154.        locked this instance */ 
  155.  
  156.     private void activate() {
  157.         synchronized (lock) {
  158.         if (!activeSet.add(this)) {
  159.             throw new InternalError("Shadow already in active set");
  160.         }
  161.         this.active = true;
  162.         }
  163.     }
  164.  
  165.     public void deActivate() {
  166.         synchronized (lock) {
  167.         if (!this.active) {
  168.             throw new InternalError("Shadow not active");
  169.         }
  170.         if (!activeSet.remove(this)) {
  171.             throw new InternalError("Shadow not in active set " +
  172.                         this + " " +
  173.                         ((this.get() != null) ?
  174.                          this.get().toString() : "null"));
  175.         }
  176.         inactive.put(this, this);
  177.         this.active = false;
  178.         }
  179.     }
  180.  
  181.     public void reActivate() {
  182.         synchronized (lock) {
  183.         if (this.active) {
  184.             throw new InternalError("Shadow not inactive");
  185.         }
  186.         if (inactive.remove(this) == null) {
  187.             throw new InternalError("Shadow not in inactive map");
  188.         }
  189.         if (!activeSet.add(this)) {
  190.             throw new InternalError("Shadow already in active set");
  191.         }
  192.         this.active = true;
  193.         }
  194.     }
  195.  
  196.     public void drop() {
  197.         synchronized (lock) {
  198.         if (this.active) {
  199.             if (!activeSet.remove(this)) {
  200.             throw new InternalError("Shadow not in active set");
  201.             }
  202.         } else {
  203.             if (inactive.remove(this) == null) {
  204.             throw new InternalError("Shadow not in inactive map");
  205.             }
  206.         }
  207.         }
  208.     }
  209.  
  210.     static public int getInactiveCount() {
  211.         synchronized (lock) {
  212.         return inactive.size();
  213.         }
  214.     }
  215.  
  216.     static public Shadow getMostInactiveShadow() {
  217.         synchronized (lock) {
  218.         if (inactive.size() == 0) return null;
  219.         return (Shadow)(inactive.firstKey());
  220.         }
  221.     }
  222.  
  223.     static public Shadow getSomeActiveShadow() {
  224.         synchronized (lock) {
  225.         if (activeSet.size() == 0) return null;
  226.         return (Shadow)(activeSet.first());
  227.         }
  228.     }
  229.  
  230.  
  231.     /* Constructor */
  232.  
  233.     Shadow(SoftReference sr) {
  234.         super(sr, SoftReference.softQueue);
  235.         this.activate();
  236.         this.touch();
  237.     }
  238.  
  239.     }
  240.  
  241.  
  242.     static private class Guard extends GuardedReference {
  243.  
  244.     private Shadow shadow;
  245.  
  246.     Guard(Shadow s, Object referent) {
  247.         super(referent, SoftReference.softQueue);
  248.         this.shadow = s;
  249.     }
  250.  
  251.     /* The only exception to the rule that all operations on a Guard must
  252.        be synchronized on the associated Shadow is that the following two
  253.        methods may be called as long as no Shadow locks are held */
  254.  
  255.     synchronized public Shadow getShadow() {
  256.         return shadow;
  257.     }
  258.  
  259.     synchronized public void clear() {
  260.         super.clear();
  261.         this.shadow = null;
  262.     }
  263.  
  264.     }
  265.  
  266.  
  267.     /* Instance invariants
  268.          The guard and shadow fields of a cleared instance are null.
  269.          A non-cleared instance is either in the active set or the inactive map.
  270.      The guard of an inactive instance has been enqueued and then dequeued.
  271.      Timestamps of inactive instances are never updated.
  272.      The Shadow of an instance is temporally unique; once set, the shadow
  273.         field is only ever cleared.
  274.      */
  275.  
  276.     /* The Guard for the referent of this SoftReference instance, or null if
  277.        this instance has been cleared */
  278.     private Guard guard;
  279.  
  280.     /* The Shadow for this SoftReference instance, or null if this instance
  281.        has been cleared */
  282.     private Shadow shadow;
  283.  
  284.  
  285.     /* Process any Guards or Shadows that have been added to the reference
  286.        queue by the garbage collector.  When a Guard is enqueued then the
  287.        corresponding SoftReference is made inactive.  When a Shadow is
  288.        enqueued then its SoftReference is dropped.  This method is called by
  289.        each of the public methods in order to clean things up incrementally.
  290.        It is also called by the sweeper thread when the sweeper must clear
  291.        every SoftReference.
  292.      */
  293.     static private void processQueue(boolean clearAll) {
  294.     Reference r;
  295.     while ((r = softQueue.poll()) != null) {
  296.         if (r instanceof Guard) {
  297.         Guard g = (Guard)r;
  298.         Shadow s = g.getShadow();
  299.         if (s != null) {
  300.             synchronized (s) {
  301.             SoftReference sr = (SoftReference)(s.get());
  302.             if ((sr != null) && (sr.guard == g)) {
  303.                 if (clearAll) {
  304.                 sr.reallyClear();
  305.                 } else if (s.isActive()) {
  306.                 s.deActivate();
  307.                 }
  308.             } else {
  309.                 /* This guard's SoftReference was cleared
  310.                    before we locked the shadow */
  311.                 g.clear();
  312.             }
  313.             }
  314.         } else {
  315.             /* This guard has already been dissociated from its
  316.                shadow */
  317.             g.clear();
  318.         }
  319.         } else if (r instanceof Shadow) {
  320.         /* The SoftReference held by this shadow has become
  321.            only weakly reachable, so remove all traces of it */
  322.         Shadow s = (Shadow)r;
  323.         synchronized (s) {
  324.             SoftReference sr = (SoftReference)(s.get());
  325.             if (sr != null) {
  326.             sr.reallyClear();
  327.             }
  328.         }
  329.         } else {
  330.         throw new InternalError("Unknown queue-entry type");
  331.         }
  332.     }
  333.     }
  334.  
  335.  
  336.     /* -- Constructors -- */
  337.  
  338.     /**
  339.      * Construct a new soft reference that refers to the given object.
  340.      */
  341.     public SoftReference(Object referent) {
  342.     super(null);        /* ## Leaves Reference.referent field unused */
  343.  
  344.     /* We needn't worry about race conditions here -- this new
  345.        SoftReference is strongly reachable for the duration of the
  346.        constructor invocation, so there's no chance of the Shadow instance
  347.        being enqueued until sometime after the constructor returns */
  348.     Shadow s = new Shadow(this);
  349.     this.shadow = s;
  350.  
  351.     /* It is extremely unlikely that the Guard will be enqueued before we
  352.        set the guard field, but we synchronize anyway just in case */
  353.     synchronized (s) {
  354.         this.guard = new Guard(s, referent);
  355.     }
  356.  
  357.     processQueue(false);
  358.     }
  359.  
  360.     /**
  361.      * Construct a new soft reference that refers to the given object and is
  362.      * registered with the given queue.
  363.      */
  364.     public SoftReference(Object referent, ReferenceQueue queue) {
  365.     this(referent);
  366.     this.queue = queue;
  367.     }
  368.  
  369.  
  370.     /* -- Accessors -- */
  371.  
  372.     /**
  373.      * Return the object to which this soft reference refers, or
  374.      * <code>null</code> if the reference has been cleared by the garbage
  375.      * collector due to lack of space.
  376.      *
  377.      * <p><em>Implementation note:</em> This method may allocate a small object
  378.      * and adjust an internal data structure, so it is not quite as fast as the
  379.      * <code>get</code> methods of the other <code>Reference</code> types.
  380.      */
  381.     public Object get() {
  382.     processQueue(false);
  383.     Shadow s = this.shadow;
  384.     if (s == null) {
  385.         /* This SoftReference has been cleared */
  386.         return null;
  387.     }
  388.     synchronized (s) {
  389.         if (this.shadow == s) {
  390.  
  391.         Guard g = this.guard;
  392.         if (g == null) {
  393.             throw new InternalError("SoftReference has shadow but no guard");
  394.         }
  395.         Object o = g.get();
  396.  
  397.         /* At this point the referent is strongly reachable; if the
  398.            Guard hasn't been enqueued yet, then that can't happen until
  399.            we return */
  400.         boolean active = s.isActive();
  401.         if (!active || g.isEnqueued()) {
  402.             this.guard = new Guard(s, o);
  403.             g.clear();
  404.         }
  405.         if (!active) {
  406.             s.reActivate();
  407.         }
  408.         s.touch();
  409.         return o;
  410.  
  411.         } else {
  412.         /* SoftReference was cleared before we locked it */
  413.         return null;
  414.         }
  415.     }
  416.     }
  417.  
  418.  
  419.     /**
  420.      * Clear this soft reference.
  421.      */
  422.     public void clear() {
  423.     processQueue(false);
  424.     Shadow s = this.shadow;
  425.     if (s == null) return;
  426.     synchronized (s) {
  427.         if (this.shadow == s) {
  428.         reallyClear();
  429.         }
  430.     }
  431.     }
  432.  
  433.  
  434.     /**
  435.      * Really clear this soft reference, assuming that it has not yet been
  436.      * cleared and that its shadow is already locked.
  437.      */
  438.     private void reallyClear() {
  439.     this.shadow.drop();
  440.     this.shadow.clear();
  441.     Guard g = this.guard;
  442.     if (g == null) {
  443.         throw new InternalError("SoftReference has shadow but no guard");
  444.     }
  445.     g.clear();
  446.     this.guard = null;
  447.     this.shadow = null;
  448.     if (this.queue != null) {
  449.         this.enqueue();
  450.     }
  451.     }
  452.  
  453.  
  454.     private static class Sweeper implements Runnable {
  455.     private final Class sr    /* Don't let SoftReference be unloaded */
  456.         = SoftReference.class;
  457.     private Runtime rt = Runtime.getRuntime();
  458.  
  459.     static private void sweep(Shadow s) {
  460.         if (s == null) return;
  461.         synchronized (s) {
  462.         SoftReference sr = (SoftReference)s.get();
  463.         if ((sr == null) || (sr.shadow != s)) {
  464.             s.drop();
  465.             s.clear();
  466.             if (debug) {
  467.             dbg("sweeper: Dropped " +
  468.                 ((sr == null) ? s.toString() : sr.toString()));
  469.             }
  470.         } else {
  471.             sr.reallyClear();
  472.             if (debug) {
  473.             dbg("sweeper: Cleared " + sr);
  474.             }
  475.         }
  476.         }
  477.     }
  478.  
  479.     static private void sweepSome(int divisor) {
  480.         processQueue(false);
  481.         int c = Shadow.getInactiveCount();
  482.         int n = c / divisor;
  483.         if (debug) {
  484.         dbg("sweeper: Trying to clear " + n + " of " + c);
  485.         }
  486.         int i;
  487.         for (i = 0; i < n; i++) {
  488.         Shadow s = Shadow.getMostInactiveShadow();
  489.         if (s == null) break;
  490.         sweep(s);
  491.         }
  492.         if (debug) {
  493.         dbg("sweeper: Cleared " + i);
  494.         }
  495.     }
  496.  
  497.     static private void sweepAll() {
  498.         if (debug) {
  499.         dbg("sweeper: Clearing everything");
  500.         }
  501.         processQueue(true);
  502.         sweepSome(1);
  503.         Shadow s;
  504.         while ((s = Shadow.getSomeActiveShadow()) != null) {
  505.         sweep(s);
  506.         }
  507.         if (debug) {
  508.         dbg("sweeper: Done clearing everything");
  509.         }
  510.     }
  511.  
  512.     public void run() {
  513.         int ma = rt.getMemoryAdvice();
  514.         for (;;) {
  515.         switch (ma) {    /* ## Tune this */
  516.         case Runtime.MemoryAdvice.GREEN:   break;
  517.         case Runtime.MemoryAdvice.YELLOW:  sweepSome(3);  break;
  518.         case Runtime.MemoryAdvice.ORANGE:  sweepSome(1);  break;
  519.         case Runtime.MemoryAdvice.RED:     sweepAll();  break;
  520.         }
  521.         try {
  522.             ma = rt.waitForMemoryAdvice(ma);
  523.         } catch (InterruptedException x) {
  524.             continue;
  525.         }
  526.         }
  527.     }
  528.  
  529.     }
  530.  
  531.     private static Thread sweeper;
  532.  
  533.  
  534.     static {
  535.     try {
  536.         java.security.AccessController.beginPrivileged();
  537.         debug = Boolean.getBoolean("java.lang.ref.SoftReference.debug");
  538.         ThreadGroup tg = Thread.currentThread().getThreadGroup();
  539.         for (ThreadGroup tgn = tg;
  540.          tgn != null;
  541.          tg = tgn, tgn = tg.getParent());
  542.         sweeper = new Thread(tg, new Sweeper(), "SoftReference sweeper");
  543.         sweeper.setDaemon(true);
  544.         sweeper.setPriority(Thread.MAX_PRIORITY - 1);
  545.         sweeper.start();
  546.     } finally {
  547.         java.security.AccessController.endPrivileged();
  548.     }
  549.     }
  550.  
  551. }
  552.