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