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