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