Home | History | Annotate | Download | only in lang
      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 = 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 }
    282