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 = 1000000; 35 private static final long MAX_FINALIZE_MILLIS = 10L * 1000L; // 10 seconds 36 start()37 public static void start() { 38 ReferenceQueueDaemon.INSTANCE.start(); 39 FinalizerDaemon.INSTANCE.start(); 40 FinalizerWatchdogDaemon.INSTANCE.start(); 41 } 42 stop()43 public static void stop() { 44 ReferenceQueueDaemon.INSTANCE.stop(); 45 FinalizerDaemon.INSTANCE.stop(); 46 FinalizerWatchdogDaemon.INSTANCE.stop(); 47 } 48 49 /** 50 * A background task that provides runtime support to the application. 51 * Daemons can be stopped and started, but only so that the zygote can be a 52 * single-threaded process when it forks. 53 */ 54 private static abstract class Daemon implements Runnable { 55 private Thread thread; 56 start()57 public synchronized void start() { 58 if (thread != null) { 59 throw new IllegalStateException("already running"); 60 } 61 thread = new Thread(ThreadGroup.mSystem, this, 62 getClass().getSimpleName()); 63 thread.setDaemon(true); 64 thread.start(); 65 } 66 run()67 public abstract void run(); 68 69 /** 70 * Returns true while the current thread should continue to run; false 71 * when it should return. 72 */ isRunning()73 protected synchronized boolean isRunning() { 74 return thread != null; 75 } 76 interrupt()77 public synchronized void interrupt() { 78 if (thread == null) { 79 throw new IllegalStateException("not running"); 80 } 81 thread.interrupt(); 82 } 83 84 /** 85 * Waits for the runtime thread to stop. This interrupts the thread 86 * currently running the runnable and then waits for it to exit. 87 */ stop()88 public void stop() { 89 Thread threadToStop; 90 synchronized (this) { 91 threadToStop = thread; 92 thread = null; 93 } 94 if (threadToStop == null) { 95 throw new IllegalStateException("not running"); 96 } 97 threadToStop.interrupt(); 98 while (true) { 99 try { 100 threadToStop.join(); 101 return; 102 } catch (InterruptedException ignored) { 103 } 104 } 105 } 106 107 /** 108 * Returns the current stack trace of the thread, or an empty stack trace 109 * if the thread is not currently running. 110 */ getStackTrace()111 public synchronized StackTraceElement[] getStackTrace() { 112 return thread != null ? thread.getStackTrace() : EmptyArray.STACK_TRACE_ELEMENT; 113 } 114 } 115 116 /** 117 * This heap management thread moves elements from the garbage collector's 118 * pending list to the managed reference queue. 119 */ 120 private static class ReferenceQueueDaemon extends Daemon { 121 private static final ReferenceQueueDaemon INSTANCE = new ReferenceQueueDaemon(); 122 run()123 @Override public void run() { 124 while (isRunning()) { 125 Reference<?> list; 126 try { 127 synchronized (ReferenceQueue.class) { 128 while (ReferenceQueue.unenqueued == null) { 129 ReferenceQueue.class.wait(); 130 } 131 list = ReferenceQueue.unenqueued; 132 ReferenceQueue.unenqueued = null; 133 } 134 } catch (InterruptedException e) { 135 continue; 136 } 137 enqueue(list); 138 } 139 } 140 enqueue(Reference<?> list)141 private void enqueue(Reference<?> list) { 142 while (list != null) { 143 Reference<?> reference; 144 // pendingNext is owned by the GC so no synchronization is required 145 if (list == list.pendingNext) { 146 reference = list; 147 reference.pendingNext = null; 148 list = null; 149 } else { 150 reference = list.pendingNext; 151 list.pendingNext = reference.pendingNext; 152 reference.pendingNext = null; 153 } 154 reference.enqueueInternal(); 155 } 156 } 157 } 158 159 private static class FinalizerDaemon extends Daemon { 160 private static final FinalizerDaemon INSTANCE = new FinalizerDaemon(); 161 private final ReferenceQueue<Object> queue = FinalizerReference.queue; 162 private volatile Object finalizingObject; 163 private volatile long finalizingStartedNanos; 164 run()165 @Override public void run() { 166 while (isRunning()) { 167 // Take a reference, blocking until one is ready or the thread should stop 168 try { 169 doFinalize((FinalizerReference<?>) queue.remove()); 170 } catch (InterruptedException ignored) { 171 } 172 } 173 } 174 175 @FindBugsSuppressWarnings("FI_EXPLICIT_INVOCATION") doFinalize(FinalizerReference<?> reference)176 private void doFinalize(FinalizerReference<?> reference) { 177 FinalizerReference.remove(reference); 178 Object object = reference.get(); 179 reference.clear(); 180 try { 181 finalizingStartedNanos = System.nanoTime(); 182 finalizingObject = object; 183 synchronized (FinalizerWatchdogDaemon.INSTANCE) { 184 FinalizerWatchdogDaemon.INSTANCE.notify(); 185 } 186 object.finalize(); 187 } catch (Throwable ex) { 188 // The RI silently swallows these, but Android has always logged. 189 System.logE("Uncaught exception thrown by finalizer", ex); 190 } finally { 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 run()204 @Override public void run() { 205 while (isRunning()) { 206 try { 207 Object object = FinalizerDaemon.INSTANCE.finalizingObject; 208 long startedNanos = FinalizerDaemon.INSTANCE.finalizingStartedNanos; 209 210 if (object == null) { 211 synchronized (this) { 212 // wait until something is being finalized 213 // http://code.google.com/p/android/issues/detail?id=22778 214 wait(); 215 continue; 216 } 217 } 218 219 long elapsedMillis = (System.nanoTime() - startedNanos) / NANOS_PER_MILLI; 220 long sleepMillis = MAX_FINALIZE_MILLIS - elapsedMillis; 221 if (sleepMillis > 0) { 222 Thread.sleep(sleepMillis); 223 elapsedMillis = (System.nanoTime() - startedNanos) / NANOS_PER_MILLI; 224 } 225 226 if (object != FinalizerDaemon.INSTANCE.finalizingObject 227 || VMRuntime.getRuntime().isDebuggerActive()) { 228 continue; 229 } 230 231 // The current object has exceeded the finalization deadline; abort! 232 Exception syntheticException = new TimeoutException(); 233 syntheticException.setStackTrace(FinalizerDaemon.INSTANCE.getStackTrace()); 234 System.logE(object.getClass().getName() + ".finalize() timed out after " 235 + elapsedMillis + " ms; limit is " + MAX_FINALIZE_MILLIS + " ms", 236 syntheticException); 237 System.exit(2); 238 } catch (InterruptedException ignored) { 239 } 240 } 241 } 242 } 243 } 244