blob: 78a4152a823955c41425811c2d5a0ac4f7f95dea [file] [log] [blame]
The Android Open Source Projectb5de22c2012-04-01 00:00:00 -07001/*
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 }
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}