Daemons.java revision c0b55b80df57b9f8c659a5af4525ceba271622b6
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package java.lang; 18 19import dalvik.system.VMRuntime; 20import java.lang.ref.FinalizerReference; 21import java.lang.ref.Reference; 22import java.lang.ref.ReferenceQueue; 23import java.util.concurrent.atomic.AtomicBoolean; 24import java.util.concurrent.TimeoutException; 25import libcore.util.EmptyArray; 26 27/** 28 * Calls Object.finalize() on objects in the finalizer reference queue. The VM 29 * will abort if any finalize() call takes more than the maximum finalize time 30 * to complete. 31 * 32 * @hide 33 */ 34public final class Daemons { 35 private static final int NANOS_PER_MILLI = 1000 * 1000; 36 private static final int NANOS_PER_SECOND = NANOS_PER_MILLI * 1000; 37 private static final long MAX_FINALIZE_NANOS = 10L * NANOS_PER_SECOND; 38 39 public static void start() { 40 ReferenceQueueDaemon.INSTANCE.start(); 41 FinalizerDaemon.INSTANCE.start(); 42 FinalizerWatchdogDaemon.INSTANCE.start(); 43 HeapTrimmerDaemon.INSTANCE.start(); 44 GCDaemon.INSTANCE.start(); 45 } 46 47 public static void stop() { 48 ReferenceQueueDaemon.INSTANCE.stop(); 49 FinalizerDaemon.INSTANCE.stop(); 50 FinalizerWatchdogDaemon.INSTANCE.stop(); 51 HeapTrimmerDaemon.INSTANCE.stop(); 52 GCDaemon.INSTANCE.stop(); 53 } 54 55 /** 56 * A background task that provides runtime support to the application. 57 * Daemons can be stopped and started, but only so that the zygote can be a 58 * single-threaded process when it forks. 59 */ 60 private static abstract class Daemon implements Runnable { 61 private Thread thread; 62 63 public synchronized void start() { 64 if (thread != null) { 65 throw new IllegalStateException("already running"); 66 } 67 thread = new Thread(ThreadGroup.systemThreadGroup, this, getClass().getSimpleName()); 68 thread.setDaemon(true); 69 thread.start(); 70 } 71 72 public abstract void run(); 73 74 /** 75 * Returns true while the current thread should continue to run; false 76 * when it should return. 77 */ 78 protected synchronized boolean isRunning() { 79 return thread != null; 80 } 81 82 public synchronized void interrupt() { 83 if (thread == null) { 84 throw new IllegalStateException("not running"); 85 } 86 thread.interrupt(); 87 } 88 89 /** 90 * Waits for the runtime thread to stop. This interrupts the thread 91 * currently running the runnable and then waits for it to exit. 92 */ 93 public void stop() { 94 Thread threadToStop; 95 synchronized (this) { 96 threadToStop = thread; 97 thread = null; 98 } 99 if (threadToStop == null) { 100 throw new IllegalStateException("not running"); 101 } 102 threadToStop.interrupt(); 103 while (true) { 104 try { 105 threadToStop.join(); 106 return; 107 } catch (InterruptedException ignored) { 108 } 109 } 110 } 111 112 /** 113 * Returns the current stack trace of the thread, or an empty stack trace 114 * if the thread is not currently running. 115 */ 116 public synchronized StackTraceElement[] getStackTrace() { 117 return thread != null ? thread.getStackTrace() : EmptyArray.STACK_TRACE_ELEMENT; 118 } 119 } 120 121 /** 122 * This heap management thread moves elements from the garbage collector's 123 * pending list to the managed reference queue. 124 */ 125 private static class ReferenceQueueDaemon extends Daemon { 126 private static final ReferenceQueueDaemon INSTANCE = new ReferenceQueueDaemon(); 127 128 @Override public void run() { 129 while (isRunning()) { 130 Reference<?> list; 131 try { 132 synchronized (ReferenceQueue.class) { 133 while (ReferenceQueue.unenqueued == null) { 134 ReferenceQueue.class.wait(); 135 } 136 list = ReferenceQueue.unenqueued; 137 ReferenceQueue.unenqueued = null; 138 } 139 } catch (InterruptedException e) { 140 continue; 141 } 142 enqueue(list); 143 } 144 } 145 146 private void enqueue(Reference<?> list) { 147 Reference<?> start = list; 148 do { 149 // pendingNext is owned by the GC so no synchronization is required. 150 Reference<?> next = list.pendingNext; 151 list.pendingNext = null; 152 list.enqueueInternal(); 153 list = next; 154 } while (list != start); 155 } 156 } 157 158 private static class FinalizerDaemon extends Daemon { 159 private static final FinalizerDaemon INSTANCE = new FinalizerDaemon(); 160 private final ReferenceQueue<Object> queue = FinalizerReference.queue; 161 private volatile Object finalizingObject; 162 private volatile long finalizingStartedNanos; 163 164 @Override public void run() { 165 while (isRunning()) { 166 // Take a reference, blocking until one is ready or the thread should stop 167 try { 168 doFinalize((FinalizerReference<?>) queue.remove()); 169 } catch (InterruptedException ignored) { 170 } 171 } 172 } 173 174 @FindBugsSuppressWarnings("FI_EXPLICIT_INVOCATION") 175 private void doFinalize(FinalizerReference<?> reference) { 176 FinalizerReference.remove(reference); 177 Object object = reference.get(); 178 reference.clear(); 179 try { 180 finalizingStartedNanos = System.nanoTime(); 181 finalizingObject = object; 182 synchronized (FinalizerWatchdogDaemon.INSTANCE) { 183 FinalizerWatchdogDaemon.INSTANCE.notify(); 184 } 185 object.finalize(); 186 } catch (Throwable ex) { 187 // The RI silently swallows these, but Android has always logged. 188 System.logE("Uncaught exception thrown by finalizer", ex); 189 } finally { 190 // Done finalizing, stop holding the object as live. 191 finalizingObject = null; 192 } 193 } 194 } 195 196 /** 197 * The watchdog exits the VM if the finalizer ever gets stuck. We consider 198 * the finalizer to be stuck if it spends more than MAX_FINALIZATION_MILLIS 199 * on one instance. 200 */ 201 private static class FinalizerWatchdogDaemon extends Daemon { 202 private static final FinalizerWatchdogDaemon INSTANCE = new FinalizerWatchdogDaemon(); 203 204 @Override public void run() { 205 while (isRunning()) { 206 boolean waitSuccessful = waitForObject(); 207 if (waitSuccessful == false) { 208 // We have been interrupted, need to see if this daemon has been stopped. 209 continue; 210 } 211 boolean finalized = waitForFinalization(); 212 if (!finalized && !VMRuntime.getRuntime().isDebuggerActive()) { 213 Object finalizedObject = FinalizerDaemon.INSTANCE.finalizingObject; 214 // At this point we probably timed out, look at the object in case the finalize 215 // just finished. 216 if (finalizedObject != null) { 217 finalizerTimedOut(finalizedObject); 218 break; 219 } 220 } 221 } 222 } 223 224 private boolean waitForObject() { 225 while (true) { 226 Object object = FinalizerDaemon.INSTANCE.finalizingObject; 227 if (object != null) { 228 return true; 229 } 230 synchronized (this) { 231 // wait until something is ready to be finalized 232 // http://code.google.com/p/android/issues/detail?id=22778 233 try { 234 wait(); 235 } catch (InterruptedException e) { 236 // Daemon.stop may have interrupted us. 237 return false; 238 } 239 } 240 } 241 } 242 243 private void sleepFor(long startNanos, long durationNanos) { 244 while (true) { 245 long elapsedNanos = System.nanoTime() - startNanos; 246 long sleepNanos = durationNanos - elapsedNanos; 247 long sleepMills = sleepNanos / NANOS_PER_MILLI; 248 if (sleepMills <= 0) { 249 return; 250 } 251 try { 252 Thread.sleep(sleepMills); 253 } catch (InterruptedException e) { 254 if (!isRunning()) { 255 return; 256 } 257 } 258 } 259 } 260 261 private boolean waitForFinalization() { 262 long startTime = FinalizerDaemon.INSTANCE.finalizingStartedNanos; 263 sleepFor(startTime, MAX_FINALIZE_NANOS); 264 // If we are finalizing an object and the start time is the same, it must be that we 265 // timed out finalizing something. It may not be the same object that we started out 266 // with but this doesn't matter. 267 return FinalizerDaemon.INSTANCE.finalizingObject == null || 268 FinalizerDaemon.INSTANCE.finalizingStartedNanos != startTime; 269 } 270 271 private static void finalizerTimedOut(Object object) { 272 // The current object has exceeded the finalization deadline; abort! 273 String message = object.getClass().getName() + ".finalize() timed out after " 274 + (MAX_FINALIZE_NANOS / NANOS_PER_SECOND) + " seconds"; 275 Exception syntheticException = new TimeoutException(message); 276 // We use the stack from where finalize() was running to show where it was stuck. 277 syntheticException.setStackTrace(FinalizerDaemon.INSTANCE.getStackTrace()); 278 Thread.UncaughtExceptionHandler h = Thread.getDefaultUncaughtExceptionHandler(); 279 if (h == null) { 280 // If we have no handler, log and exit. 281 System.logE(message, syntheticException); 282 System.exit(2); 283 } 284 // Otherwise call the handler to do crash reporting. 285 // We don't just throw because we're not the thread that 286 // timed out; we're the thread that detected it. 287 h.uncaughtException(Thread.currentThread(), syntheticException); 288 } 289 } 290 291 // Invoked by the GC to request that the HeapTrimmerDaemon thread attempt to trim the heap. 292 public static void requestHeapTrim() { 293 synchronized (HeapTrimmerDaemon.INSTANCE) { 294 HeapTrimmerDaemon.INSTANCE.notify(); 295 } 296 } 297 298 private static class HeapTrimmerDaemon extends Daemon { 299 private static final HeapTrimmerDaemon INSTANCE = new HeapTrimmerDaemon(); 300 301 @Override public void run() { 302 while (isRunning()) { 303 try { 304 synchronized (this) { 305 wait(); 306 } 307 VMRuntime.getRuntime().trimHeap(); 308 } catch (InterruptedException ignored) { 309 } 310 } 311 } 312 } 313 314 // Invoked by the GC to request that the HeapTrimmerDaemon thread attempt to trim the heap. 315 public static void requestGC() { 316 GCDaemon.INSTANCE.requestGC(); 317 } 318 319 private static class GCDaemon extends Daemon { 320 private static final GCDaemon INSTANCE = new GCDaemon(); 321 private static final AtomicBoolean atomicBoolean = new AtomicBoolean(); 322 323 public void requestGC() { 324 if (atomicBoolean.getAndSet(true)) { 325 return; 326 } 327 synchronized (this) { 328 notify(); 329 } 330 atomicBoolean.set(false); 331 } 332 333 @Override public void run() { 334 while (isRunning()) { 335 try { 336 synchronized (this) { 337 // Wait until a request comes in. 338 wait(); 339 } 340 VMRuntime.getRuntime().concurrentGC(); 341 } catch (InterruptedException ignored) { 342 } 343 } 344 } 345 } 346} 347