View Javadoc
1   /*
2    * Copyright 2017 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.util.internal;
17  
18  import io.netty.util.concurrent.FastThreadLocalThread;
19  
20  import java.lang.ref.ReferenceQueue;
21  import java.lang.ref.WeakReference;
22  import java.security.AccessController;
23  import java.security.PrivilegedAction;
24  import java.util.Set;
25  import java.util.concurrent.ConcurrentHashMap;
26  import java.util.concurrent.atomic.AtomicBoolean;
27  
28  import static io.netty.util.internal.SystemPropertyUtil.getInt;
29  import static java.lang.Math.max;
30  
31  /**
32   * Allows a way to register some {@link Runnable} that will executed once there are no references to an {@link Object}
33   * anymore.
34   *
35   * @deprecated The object cleaner is deprecated for removal.
36   */
37  @Deprecated
38  public final class ObjectCleaner {
39      private static final int REFERENCE_QUEUE_POLL_TIMEOUT_MS =
40              max(500, getInt("io.netty.util.internal.ObjectCleaner.refQueuePollTimeout", 10000));
41  
42      // Package-private for testing
43      static final String CLEANER_THREAD_NAME = ObjectCleaner.class.getSimpleName() + "Thread";
44      // This will hold a reference to the AutomaticCleanerReference which will be removed once we called cleanup()
45      private static final Set<AutomaticCleanerReference> LIVE_SET = ConcurrentHashMap.newKeySet();
46      private static final ReferenceQueue<Object> REFERENCE_QUEUE = new ReferenceQueue<>();
47      private static final AtomicBoolean CLEANER_RUNNING = new AtomicBoolean(false);
48      private static final Runnable CLEANER_TASK = new Runnable() {
49          @Override
50          public void run() {
51              boolean interrupted = false;
52              for (;;) {
53                  // Keep on processing as long as the LIVE_SET is not empty and once it becomes empty
54                  // See if we can let this thread complete.
55                  while (!LIVE_SET.isEmpty()) {
56                      final AutomaticCleanerReference reference;
57                      try {
58                          reference = (AutomaticCleanerReference) REFERENCE_QUEUE.remove(REFERENCE_QUEUE_POLL_TIMEOUT_MS);
59                      } catch (InterruptedException ex) {
60                          // Just consume and move on
61                          interrupted = true;
62                          continue;
63                      }
64                      if (reference != null) {
65                          try {
66                              reference.cleanup();
67                          } catch (Throwable ignored) {
68                              // ignore exceptions, and don't log in case the logger throws an exception, blocks, or has
69                              // other unexpected side effects.
70                          }
71                          LIVE_SET.remove(reference);
72                      }
73                  }
74                  CLEANER_RUNNING.set(false);
75  
76                  // Its important to first access the LIVE_SET and then CLEANER_RUNNING to ensure correct
77                  // behavior in multi-threaded environments.
78                  if (LIVE_SET.isEmpty() || !CLEANER_RUNNING.compareAndSet(false, true)) {
79                      // There was nothing added after we set STARTED to false or some other cleanup Thread
80                      // was started already so its safe to let this Thread complete now.
81                      break;
82                  }
83              }
84              if (interrupted) {
85                  // As we caught the InterruptedException above we should mark the Thread as interrupted.
86                  Thread.currentThread().interrupt();
87              }
88          }
89      };
90  
91      /**
92       * Register the given {@link Object} for which the {@link Runnable} will be executed once there are no references
93       * to the object anymore.
94       *
95       * This should only be used if there are no other ways to execute some cleanup once the Object is not reachable
96       * anymore because it is not a cheap way to handle the cleanup.
97       */
98      public static void register(Object object, Runnable cleanupTask) {
99          AutomaticCleanerReference reference = new AutomaticCleanerReference(object,
100                 ObjectUtil.checkNotNull(cleanupTask, "cleanupTask"));
101         // Its important to add the reference to the LIVE_SET before we access CLEANER_RUNNING to ensure correct
102         // behavior in multi-threaded environments.
103         LIVE_SET.add(reference);
104 
105         // Check if there is already a cleaner running.
106         if (CLEANER_RUNNING.compareAndSet(false, true)) {
107             final Thread cleanupThread = new FastThreadLocalThread(CLEANER_TASK);
108             cleanupThread.setPriority(Thread.MIN_PRIORITY);
109             // Set to null to ensure we not create classloader leaks by holding a strong reference to the inherited
110             // classloader.
111             // See:
112             // - https://github.com/netty/netty/issues/7290
113             // - https://bugs.openjdk.java.net/browse/JDK-7008595
114             AccessController.doPrivileged(new PrivilegedAction<Void>() {
115                 @Override
116                 public Void run() {
117                     cleanupThread.setContextClassLoader(null);
118                     return null;
119                 }
120             });
121             cleanupThread.setName(CLEANER_THREAD_NAME);
122 
123             // Mark this as a daemon thread to ensure that we the JVM can exit if this is the only thread that is
124             // running.
125             cleanupThread.setDaemon(true);
126             cleanupThread.start();
127         }
128     }
129 
130     public static int getLiveSetCount() {
131         return LIVE_SET.size();
132     }
133 
134     private ObjectCleaner() {
135         // Only contains a static method.
136     }
137 
138     private static final class AutomaticCleanerReference extends WeakReference<Object> {
139         private final Runnable cleanupTask;
140 
141         AutomaticCleanerReference(Object referent, Runnable cleanupTask) {
142             super(referent, REFERENCE_QUEUE);
143             this.cleanupTask = cleanupTask;
144         }
145 
146         void cleanup() {
147             cleanupTask.run();
148         }
149 
150         @Override
151         public Thread get() {
152             return null;
153         }
154 
155         @Override
156         public void clear() {
157             LIVE_SET.remove(this);
158             super.clear();
159         }
160     }
161 }