Daemons.java revision 152be540e335376f66ed662d8e63e601a09cb4b3
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        HeapTaskDaemon.INSTANCE.start();
44    }
45
46    public static void stop() {
47        HeapTaskDaemon.INSTANCE.stop();
48        ReferenceQueueDaemon.INSTANCE.stop();
49        FinalizerDaemon.INSTANCE.stop();
50        FinalizerWatchdogDaemon.INSTANCE.stop();
51    }
52
53    /**
54     * A background task that provides runtime support to the application.
55     * Daemons can be stopped and started, but only so that the zygote can be a
56     * single-threaded process when it forks.
57     */
58    private static abstract class Daemon implements Runnable {
59        private Thread thread;
60
61        public synchronized void start() {
62            if (thread != null) {
63                throw new IllegalStateException("already running");
64            }
65            thread = new Thread(ThreadGroup.systemThreadGroup, this, getClass().getSimpleName());
66            thread.setDaemon(true);
67            thread.start();
68        }
69
70        public abstract void run();
71
72        /**
73         * Returns true while the current thread should continue to run; false
74         * when it should return.
75         */
76        protected synchronized boolean isRunning() {
77            return thread != null;
78        }
79
80        public synchronized void interrupt() {
81            interrupt(thread);
82        }
83
84        public synchronized void interrupt(Thread thread) {
85            if (thread == null) {
86                throw new IllegalStateException("not running");
87            }
88            thread.interrupt();
89        }
90
91        /**
92         * Waits for the runtime thread to stop. This interrupts the thread
93         * currently running the runnable and then waits for it to exit.
94         */
95        public void stop() {
96            Thread threadToStop;
97            synchronized (this) {
98                threadToStop = thread;
99                thread = null;
100            }
101            if (threadToStop == null) {
102                throw new IllegalStateException("not running");
103            }
104            interrupt(threadToStop);
105            while (true) {
106                try {
107                    threadToStop.join();
108                    return;
109                } catch (InterruptedException ignored) {
110                }
111            }
112        }
113
114        /**
115         * Returns the current stack trace of the thread, or an empty stack trace
116         * if the thread is not currently running.
117         */
118        public synchronized StackTraceElement[] getStackTrace() {
119            return thread != null ? thread.getStackTrace() : EmptyArray.STACK_TRACE_ELEMENT;
120        }
121    }
122
123    /**
124     * This heap management thread moves elements from the garbage collector's
125     * pending list to the managed reference queue.
126     */
127    private static class ReferenceQueueDaemon extends Daemon {
128        private static final ReferenceQueueDaemon INSTANCE = new ReferenceQueueDaemon();
129
130        @Override public void run() {
131            while (isRunning()) {
132                Reference<?> list;
133                try {
134                    synchronized (ReferenceQueue.class) {
135                        while (ReferenceQueue.unenqueued == null) {
136                            ReferenceQueue.class.wait();
137                        }
138                        list = ReferenceQueue.unenqueued;
139                        ReferenceQueue.unenqueued = null;
140                    }
141                } catch (InterruptedException e) {
142                    continue;
143                }
144                enqueue(list);
145            }
146        }
147
148        private void enqueue(Reference<?> list) {
149            Reference<?> start = list;
150            do {
151                // pendingNext is owned by the GC so no synchronization is required.
152                Reference<?> next = list.pendingNext;
153                list.pendingNext = null;
154                list.enqueueInternal();
155                list = next;
156            } while (list != start);
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                // Done finalizing, stop holding the object as live.
193                finalizingObject = null;
194            }
195        }
196    }
197
198    /**
199     * The watchdog exits the VM if the finalizer ever gets stuck. We consider
200     * the finalizer to be stuck if it spends more than MAX_FINALIZATION_MILLIS
201     * on one instance.
202     */
203    private static class FinalizerWatchdogDaemon extends Daemon {
204        private static final FinalizerWatchdogDaemon INSTANCE = new FinalizerWatchdogDaemon();
205
206        @Override public void run() {
207            while (isRunning()) {
208                boolean waitSuccessful = waitForObject();
209                if (waitSuccessful == false) {
210                    // We have been interrupted, need to see if this daemon has been stopped.
211                    continue;
212                }
213                boolean finalized = waitForFinalization();
214                if (!finalized && !VMRuntime.getRuntime().isDebuggerActive()) {
215                    Object finalizedObject = FinalizerDaemon.INSTANCE.finalizingObject;
216                    // At this point we probably timed out, look at the object in case the finalize
217                    // just finished.
218                    if (finalizedObject != null) {
219                        finalizerTimedOut(finalizedObject);
220                        break;
221                    }
222                }
223            }
224        }
225
226        private boolean waitForObject() {
227            while (true) {
228                Object object = FinalizerDaemon.INSTANCE.finalizingObject;
229                if (object != null) {
230                    return true;
231                }
232                synchronized (this) {
233                    // wait until something is ready to be finalized
234                    // http://code.google.com/p/android/issues/detail?id=22778
235                    try {
236                        wait();
237                    } catch (InterruptedException e) {
238                        // Daemon.stop may have interrupted us.
239                        return false;
240                    }
241                }
242            }
243        }
244
245        private void sleepFor(long startNanos, long durationNanos) {
246            while (true) {
247                long elapsedNanos = System.nanoTime() - startNanos;
248                long sleepNanos = durationNanos - elapsedNanos;
249                long sleepMills = sleepNanos / NANOS_PER_MILLI;
250                if (sleepMills <= 0) {
251                    return;
252                }
253                try {
254                    Thread.sleep(sleepMills);
255                } catch (InterruptedException e) {
256                    if (!isRunning()) {
257                        return;
258                    }
259                }
260            }
261        }
262
263        private boolean waitForFinalization() {
264            long startTime = FinalizerDaemon.INSTANCE.finalizingStartedNanos;
265            sleepFor(startTime, MAX_FINALIZE_NANOS);
266            // If we are finalizing an object and the start time is the same, it must be that we
267            // timed out finalizing something. It may not be the same object that we started out
268            // with but this doesn't matter.
269            return FinalizerDaemon.INSTANCE.finalizingObject == null ||
270                   FinalizerDaemon.INSTANCE.finalizingStartedNanos != startTime;
271        }
272
273        private static void finalizerTimedOut(Object object) {
274            // The current object has exceeded the finalization deadline; abort!
275            String message = object.getClass().getName() + ".finalize() timed out after "
276                    + (MAX_FINALIZE_NANOS / NANOS_PER_SECOND) + " seconds";
277            Exception syntheticException = new TimeoutException(message);
278            // We use the stack from where finalize() was running to show where it was stuck.
279            syntheticException.setStackTrace(FinalizerDaemon.INSTANCE.getStackTrace());
280            Thread.UncaughtExceptionHandler h = Thread.getDefaultUncaughtExceptionHandler();
281            if (h == null) {
282                // If we have no handler, log and exit.
283                System.logE(message, syntheticException);
284                System.exit(2);
285            }
286            // Otherwise call the handler to do crash reporting.
287            // We don't just throw because we're not the thread that
288            // timed out; we're the thread that detected it.
289            h.uncaughtException(Thread.currentThread(), syntheticException);
290        }
291    }
292
293    // Adds a heap trim task ot the heap event processor, not called from java. Left for
294    // compatibility purposes due to reflection.
295    public static void requestHeapTrim() {
296        VMRuntime.getRuntime().requestHeapTrim();
297    }
298
299    // Adds a concurrent GC request task ot the heap event processor, not called from java. Left
300    // for compatibility purposes due to reflection.
301    public static void requestGC() {
302        VMRuntime.getRuntime().requestConcurrentGC();
303    }
304
305    private static class HeapTaskDaemon extends Daemon {
306        private static final HeapTaskDaemon INSTANCE = new HeapTaskDaemon();
307
308        // Overrides the Daemon.interupt method which is called from Daemons.stop.
309        public synchronized void interrupt(Thread thread) {
310            VMRuntime.getRuntime().stopHeapTaskProcessor();
311        }
312
313        @Override public void run() {
314            synchronized (this) {
315                if (isRunning()) {
316                  // Needs to be synchronized or else we there is a race condition where we start
317                  // the thread, call stopHeapTaskProcessor before we start the heap task
318                  // processor, resulting in a deadlock since startHeapTaskProcessor restarts it
319                  // while the other thread is waiting in Daemons.stop().
320                  VMRuntime.getRuntime().startHeapTaskProcessor();
321                }
322            }
323            // This runs tasks until we are stopped and there is no more pending task.
324            VMRuntime.getRuntime().runHeapTasks();
325        }
326    }
327}
328