#86625: do not leak org-openide-util.jar class loader just because backportto55_86510_root before_release55_dev_merge_trunk1
authorjglick@netbeans.org
Thu, 05 Oct 2006 23:43:28 +0000
changeset 21499164d905ccf
parent 213 eab3ec754211
child 215 8290ee7dfc2d
#86625: do not leak org-openide-util.jar class loader just because
Utilities.activeReferenceQueue() was called at some point.
openide.util/src/org/openide/util/Utilities.java
openide.util/test/unit/src/org/openide/util/UtilitiesActiveQueueTest.java
     1.1 --- a/openide.util/src/org/openide/util/Utilities.java	Thu Oct 05 21:28:27 2006 +0000
     1.2 +++ b/openide.util/src/org/openide/util/Utilities.java	Thu Oct 05 23:43:28 2006 +0000
     1.3 @@ -166,8 +166,7 @@
     1.4      /** A height of the Mac OS X's menu */
     1.5      private static final int TYPICAL_MACOSX_MENU_HEIGHT = 24;
     1.6  
     1.7 -    /** variable holding the activeReferenceQueue */
     1.8 -    private static ReferenceQueue<Object> activeReferenceQueue;
     1.9 +    private static ActiveQueue activeReferenceQueue;
    1.10  
    1.11      /** reference to map that maps allowed key names to their values (String, Integer)
    1.12      and reference to map for mapping of values to their names */
    1.13 @@ -251,7 +250,9 @@
    1.14       * <P>
    1.15       * Do not call any <code>ReferenceQueue</code> methods. They
    1.16       * will throw exceptions. You may only enqueue a reference.
    1.17 -     *
    1.18 +     * <p>
    1.19 +     * Be sure to call this method anew for each reference.
    1.20 +     * Do not attempt to cache the return value.
    1.21       * @since 3.11
    1.22       */
    1.23      public static synchronized ReferenceQueue<Object> activeReferenceQueue() {
    1.24 @@ -259,6 +260,8 @@
    1.25              activeReferenceQueue = new ActiveQueue(false);
    1.26          }
    1.27  
    1.28 +        activeReferenceQueue.ping();
    1.29 +
    1.30          return activeReferenceQueue;
    1.31      }
    1.32  
    1.33 @@ -2988,49 +2991,38 @@
    1.34      /** Implementation of the active queue.
    1.35       */
    1.36      private static final class ActiveQueue extends ReferenceQueue<Object> implements Runnable {
    1.37 -        private boolean running;
    1.38 +
    1.39 +        private static final Logger LOGGER = Logger.getLogger(ActiveQueue.class.getName().replace('$', '.'));
    1.40 +
    1.41 +        /** number of known outstanding references */
    1.42 +        private int count;
    1.43          private boolean deprecated;
    1.44  
    1.45          public ActiveQueue(boolean deprecated) {
    1.46              this.deprecated = deprecated;
    1.47 -
    1.48 -            Thread t = new Thread(this, "Active Reference Queue Daemon"); // NOI18N
    1.49 -            t.setPriority(Thread.MIN_PRIORITY);
    1.50 -            t.setDaemon(true); // to not prevent exit of VM
    1.51 -            t.start();
    1.52          }
    1.53  
    1.54          public Reference<Object> poll() {
    1.55 -            throw new java.lang.UnsupportedOperationException();
    1.56 +            throw new UnsupportedOperationException();
    1.57          }
    1.58  
    1.59          public Reference<Object> remove(long timeout) throws IllegalArgumentException, InterruptedException {
    1.60 -            throw new java.lang.InterruptedException();
    1.61 +            throw new InterruptedException();
    1.62          }
    1.63  
    1.64          public Reference<Object> remove() throws InterruptedException {
    1.65 -            throw new java.lang.InterruptedException();
    1.66 +            throw new InterruptedException();
    1.67          }
    1.68  
    1.69 -        /** Called either from Thread.run or RequestProcessor.post. In first case
    1.70 -         * calls scanTheQueue (only once) in the second and nexts calls cleanTheQueue
    1.71 -         */
    1.72          public void run() {
    1.73 -            synchronized (this) {
    1.74 -                if (running) {
    1.75 -                    return;
    1.76 -                }
    1.77 -
    1.78 -                running = true;
    1.79 -            }
    1.80 -
    1.81 -            for (;;) {
    1.82 +            while (true) {
    1.83                  try {
    1.84 -                    Reference ref = super.remove(0);
    1.85 +                    Reference<?> ref = super.remove(0);
    1.86 +                    LOGGER.finer("dequeued reference");
    1.87  
    1.88                      if (!(ref instanceof Runnable)) {
    1.89 -                        Logger.getAnonymousLogger().warning(
    1.90 -                            "A reference not implementing runnable has been added to the Utilities.activeReferenceQueue (): " +
    1.91 +                        LOGGER.warning(
    1.92 +                            "A reference not implementing runnable has been added to the Utilities.activeReferenceQueue(): " +
    1.93                              ref.getClass() // NOI18N
    1.94                          );
    1.95  
    1.96 @@ -3038,7 +3030,7 @@
    1.97                      }
    1.98  
    1.99                      if (deprecated) {
   1.100 -                        Logger.getAnonymousLogger().warning(
   1.101 +                        LOGGER.warning(
   1.102                              "Utilities.ACTIVE_REFERENCE_QUEUE has been deprecated for " + ref.getClass() +
   1.103                              " use Utilities.activeReferenceQueue" // NOI18N
   1.104                          );
   1.105 @@ -3052,15 +3044,46 @@
   1.106                      } catch (Throwable t) {
   1.107                          // Should not happen.
   1.108                          // If it happens, it is a bug in client code, notify!
   1.109 -                        Logger.getAnonymousLogger().log(Level.WARNING, null, t);
   1.110 +                        LOGGER.log(Level.WARNING, null, t);
   1.111                      } finally {
   1.112                          // to allow GC
   1.113                          ref = null;
   1.114                      }
   1.115                  } catch (InterruptedException ex) {
   1.116 -                    Logger.getAnonymousLogger().log(Level.WARNING, null, ex);
   1.117 +                    LOGGER.log(Level.WARNING, null, ex);
   1.118 +                }
   1.119 +
   1.120 +                synchronized (this) {
   1.121 +                    assert count > 0;
   1.122 +                    count--;
   1.123 +                    if (count == 0) {
   1.124 +                        // We have processed all we have to process (for now at least).
   1.125 +                        // Could be restarted later if ping() called again.
   1.126 +                        // This could also happen in case someone called activeReferenceQueue() once and tried
   1.127 +                        // to use it for several references; in that case run() might never be called on
   1.128 +                        // the later ones to be collected. Can't really protect against that situation.
   1.129 +                        // See issue #86625 for details.
   1.130 +                        LOGGER.fine("stopping thread");
   1.131 +                        break;
   1.132 +                    }
   1.133                  }
   1.134              }
   1.135          }
   1.136 +
   1.137 +        synchronized void ping() {
   1.138 +            if (count == 0) {
   1.139 +                Thread t = new Thread(this, "Active Reference Queue Daemon"); // NOI18N
   1.140 +                t.setPriority(Thread.MIN_PRIORITY);
   1.141 +                t.setDaemon(true); // to not prevent exit of VM
   1.142 +                t.start();
   1.143 +                // Note that this will not be printed during IDE startup because
   1.144 +                // it happens before logging is even initialized.
   1.145 +                LOGGER.fine("starting thread");
   1.146 +            } else {
   1.147 +                LOGGER.finer("enqueuing reference");
   1.148 +            }
   1.149 +            count++;
   1.150 +        }
   1.151 +
   1.152      }
   1.153  }
     2.1 --- a/openide.util/test/unit/src/org/openide/util/UtilitiesActiveQueueTest.java	Thu Oct 05 21:28:27 2006 +0000
     2.2 +++ b/openide.util/test/unit/src/org/openide/util/UtilitiesActiveQueueTest.java	Thu Oct 05 23:43:28 2006 +0000
     2.3 @@ -19,22 +19,19 @@
     2.4  
     2.5  package org.openide.util;
     2.6  
     2.7 -import java.lang.ref.*;
     2.8 -
     2.9 -import junit.framework.*;
    2.10 -
    2.11 -import org.netbeans.junit.*;
    2.12 +import java.lang.ref.Reference;
    2.13 +import java.lang.ref.ReferenceQueue;
    2.14 +import java.lang.ref.WeakReference;
    2.15 +import java.net.URL;
    2.16 +import java.net.URLClassLoader;
    2.17 +import org.netbeans.junit.NbTestCase;
    2.18  
    2.19  public class UtilitiesActiveQueueTest extends NbTestCase {
    2.20  
    2.21 -    public UtilitiesActiveQueueTest(java.lang.String testName) {
    2.22 +    public UtilitiesActiveQueueTest(String testName) {
    2.23          super(testName);
    2.24      }
    2.25  
    2.26 -    public static void main(java.lang.String[] args) {
    2.27 -        junit.textui.TestRunner.run(new NbTestSuite(UtilitiesActiveQueueTest.class));
    2.28 -    }
    2.29 -
    2.30      public void testRunnableReferenceIsExecuted () throws Exception {
    2.31          Object obj = new Object ();
    2.32          RunnableRef ref = new RunnableRef (obj);
    2.33 @@ -95,15 +92,60 @@
    2.34          }
    2.35      }
    2.36      
    2.37 +    public void testMemoryLeak() throws Exception {
    2.38 +        final Class<?> u1 = Utilities.class;
    2.39 +        class L extends URLClassLoader {
    2.40 +            public L() {
    2.41 +                super(new URL[] {u1.getProtectionDomain().getCodeSource().getLocation()}, u1.getClassLoader().getParent());
    2.42 +            }
    2.43 +            protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    2.44 +                if (name.equals(u1.getName()) || name.startsWith(u1.getName() + "$")) {
    2.45 +                    Class c = findLoadedClass(name);
    2.46 +                    if (c == null) {
    2.47 +                        c = findClass(name);
    2.48 +                    }
    2.49 +                    if (resolve) {
    2.50 +                        resolveClass(c);
    2.51 +                    }
    2.52 +                    return c;
    2.53 +                } else {
    2.54 +                    return super.loadClass(name, resolve);
    2.55 +                }
    2.56 +            }
    2.57 +        }
    2.58 +        ClassLoader l = new L();
    2.59 +        Class<?> u2 = l.loadClass(u1.getName());
    2.60 +        assertEquals(l, u2.getClassLoader());
    2.61 +        Object obj = new Object();
    2.62 +        @SuppressWarnings("unchecked")
    2.63 +        ReferenceQueue<Object> q = (ReferenceQueue<Object>) u2.getMethod("activeReferenceQueue").invoke(null);
    2.64 +        RunnableRef ref = new RunnableRef(obj, q);
    2.65 +        synchronized (ref) {
    2.66 +            obj = null;
    2.67 +            assertGC("Ref should be GC'ed as usual", ref);
    2.68 +            ref.wait();
    2.69 +            assertTrue("Run method has been executed", ref.executed);
    2.70 +        }
    2.71 +        Reference<?> r = new WeakReference<Object>(u2);
    2.72 +        q = null;
    2.73 +        u2 = null;
    2.74 +        l = null;
    2.75 +        assertGC("#86625: Utilities.class can also be collected now", r);
    2.76 +    }
    2.77 +
    2.78      
    2.79 -    private static class RunnableRef extends WeakReference 
    2.80 +    private static class RunnableRef extends WeakReference<Object>
    2.81      implements Runnable {
    2.82          public boolean wait;
    2.83          public boolean entered;
    2.84          public boolean executed;
    2.85          
    2.86          public RunnableRef (Object o) {
    2.87 -            super (o, Utilities.activeReferenceQueue ());
    2.88 +            this(o, Utilities.activeReferenceQueue());
    2.89 +        }
    2.90 +        
    2.91 +        public RunnableRef(Object o, ReferenceQueue<Object> q) {
    2.92 +            super(o, q);
    2.93          }
    2.94          
    2.95          public synchronized void run () {