• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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