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