diff options
-rw-r--r-- | dalvik/src/main/java/dalvik/system/VMRuntime.java | 5 | ||||
-rw-r--r-- | dalvik/src/main/java/dalvik/system/Zygote.java | 8 | ||||
-rw-r--r-- | luni/src/main/java/java/lang/Daemons.java | 274 | ||||
-rw-r--r-- | luni/src/main/java/java/lang/FinalizerThread.java | 125 | ||||
-rw-r--r-- | luni/src/main/java/java/lang/Runtime.java | 2 | ||||
-rw-r--r-- | luni/src/main/java/java/lang/Thread.java | 5 | ||||
-rw-r--r-- | luni/src/main/java/java/lang/ref/FinalizerReference.java | 5 | ||||
-rw-r--r-- | luni/src/main/java/java/lang/ref/Reference.java | 8 | ||||
-rw-r--r-- | luni/src/main/java/java/lang/ref/ReferenceQueue.java | 3 | ||||
-rw-r--r-- | luni/src/main/java/java/lang/ref/ReferenceQueueThread.java | 92 | ||||
-rw-r--r-- | luni/src/main/java/libcore/util/EmptyArray.java | 1 | ||||
-rw-r--r-- | luni/src/test/java/libcore/java/lang/ref/FinalizeTest.java | 26 |
12 files changed, 321 insertions, 233 deletions
diff --git a/dalvik/src/main/java/dalvik/system/VMRuntime.java b/dalvik/src/main/java/dalvik/system/VMRuntime.java index 3e35ecf..1a19d0e 100644 --- a/dalvik/src/main/java/dalvik/system/VMRuntime.java +++ b/dalvik/src/main/java/dalvik/system/VMRuntime.java @@ -203,4 +203,9 @@ public final class VMRuntime { * up to the maximum heap size. */ public native void clearGrowthLimit(); + + /** + * Returns true if either a Java debugger or native debugger is active. + */ + public native boolean isDebuggerActive(); } diff --git a/dalvik/src/main/java/dalvik/system/Zygote.java b/dalvik/src/main/java/dalvik/system/Zygote.java index 336a6d6..fe432a9 100644 --- a/dalvik/src/main/java/dalvik/system/Zygote.java +++ b/dalvik/src/main/java/dalvik/system/Zygote.java @@ -17,8 +17,6 @@ package dalvik.system; import java.io.File; -import java.lang.FinalizerThread; -import java.lang.ref.ReferenceQueueThread; /** * Provides access to the Dalvik "zygote" feature, which allows a VM instance to @@ -50,8 +48,7 @@ public class Zygote { private Zygote() {} private static void preFork() { - ReferenceQueueThread.stopReferenceQueue(); - FinalizerThread.stopFinalizer(); + Daemons.stop(); waitUntilAllThreadsStopped(); } @@ -72,8 +69,7 @@ public class Zygote { } private static void postFork() { - ReferenceQueueThread.startReferenceQueue(); - FinalizerThread.startFinalizer(); + Daemons.start(); } /** diff --git a/luni/src/main/java/java/lang/Daemons.java b/luni/src/main/java/java/lang/Daemons.java new file mode 100644 index 0000000..345185a --- /dev/null +++ b/luni/src/main/java/java/lang/Daemons.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.lang; + +import dalvik.system.VMRuntime; +import java.lang.ref.FinalizerReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.util.concurrent.TimeoutException; +import libcore.util.EmptyArray; + +/** + * Calls Object.finalize() on objects in the finalizer reference queue. The VM + * will abort if any finalize() call takes more than the maximum finalize time + * to complete. + * + * @hide + */ +public final class Daemons { + private static final int NANOS_PER_MILLI = 1000000; + private static final long MAX_FINALIZE_MILLIS = 10L * 1000L; // 10 seconds + + public static void waitUntilFinalizerIsIdle() throws InterruptedException { + FinalizerDaemon.INSTANCE.waitUntilFinalizerIsIdle(); + } + + public static void start() { + ReferenceQueueDaemon.INSTANCE.start(); + FinalizerDaemon.INSTANCE.start(); + FinalizerWatchdogDaemon.INSTANCE.start(); + } + + public static void stop() { + ReferenceQueueDaemon.INSTANCE.stop(); + FinalizerDaemon.INSTANCE.stop(); + FinalizerWatchdogDaemon.INSTANCE.stop(); + } + + /** + * A background task that provides runtime support to the application. + * Daemons can be stopped and started, but only so that the zygote can be a + * single-threaded process when it forks. + */ + private static abstract class Daemon implements Runnable { + private Thread thread; + + public synchronized void start() { + if (thread != null) { + throw new IllegalStateException("already running"); + } + thread = new Thread(this, getClass().getSimpleName()); + thread.setDaemon(true); + thread.start(); + } + + public abstract void run(); + + /** + * Returns true while the current thread should continue to run; false + * when it should return. + */ + protected synchronized boolean isRunning() { + return thread != null; + } + + public synchronized void interrupt() { + if (thread == null) { + throw new IllegalStateException("not running"); + } + thread.interrupt(); + } + + /** + * Waits for the runtime thread to stop. This interrupts the thread + * currently running the runnable and then waits for it to exit. + */ + public void stop() { + Thread threadToStop; + synchronized (this) { + threadToStop = thread; + thread = null; + } + if (threadToStop == null) { + throw new IllegalStateException("not running"); + } + threadToStop.interrupt(); + while (true) { + try { + threadToStop.join(); + return; + } catch (InterruptedException ignored) { + } + } + } + + /** + * Returns the current stack trace of the thread, or an empty stack trace + * if the thread is not currently running. + */ + public synchronized StackTraceElement[] getStackTrace() { + return thread != null ? thread.getStackTrace() : EmptyArray.STACK_TRACE_ELEMENT; + } + } + + /** + * This heap management thread moves elements from the garbage collector's + * pending list to the managed reference queue. + */ + private static class ReferenceQueueDaemon extends Daemon { + private static final ReferenceQueueDaemon INSTANCE = new ReferenceQueueDaemon(); + + @Override public void run() { + while (isRunning()) { + Reference<?> list; + try { + synchronized (ReferenceQueue.class) { + while (ReferenceQueue.unenqueued == null) { + ReferenceQueue.class.wait(); + } + list = ReferenceQueue.unenqueued; + ReferenceQueue.unenqueued = null; + } + } catch (InterruptedException e) { + continue; + } + enqueue(list); + } + } + + private void enqueue(Reference<?> list) { + while (list != null) { + Reference<?> reference; + // pendingNext is owned by the GC so no synchronization is required + if (list == list.pendingNext) { + reference = list; + reference.pendingNext = null; + list = null; + } else { + reference = list.pendingNext; + list.pendingNext = reference.pendingNext; + reference.pendingNext = null; + } + reference.enqueueInternal(); + } + } + } + + private static class FinalizerDaemon extends Daemon { + private static final FinalizerDaemon INSTANCE = new FinalizerDaemon(); + private final ReferenceQueue<Object> queue = FinalizerReference.queue; + private boolean idle; + private volatile Object finalizingObject; + private volatile long finalizingStartedNanos; + + @Override public void run() { + while (isRunning()) { + // Take a reference, blocking until one is ready or the thread should stop + FinalizerReference<Object> reference; + try { + reference = (FinalizerReference<Object>) queue.remove(); + } catch (InterruptedException e) { + reference = (FinalizerReference<Object>) queue.poll(); + } + synchronized (this) { + idle = false; + } + + // Finalize references until the queue is empty. + while (reference != null) { + doFinalize(reference); + reference = (FinalizerReference<Object>) queue.poll(); + } + + // Mark this thread as idle until queue.remove() returns. + synchronized (this) { + idle = true; + notifyAll(); + } + } + } + + @FindBugsSuppressWarnings("FI_EXPLICIT_INVOCATION") + private void doFinalize(FinalizerReference<Object> reference) { + FinalizerReference.remove(reference); + Object obj = reference.get(); + reference.clear(); + try { + finalizingStartedNanos = System.nanoTime(); + finalizingObject = obj; + obj.finalize(); + } catch (Throwable ex) { + // The RI silently swallows these, but Android has always logged. + System.logE("Uncaught exception thrown by finalizer", ex); + } finally { + finalizingObject = null; + } + } + + /** + * Awakens the finalizer thread if necessary and then wait for it to + * become idle again. When that happens, all finalizable references enqueued + * at the time of this method call will have been finalized. + * + * TODO: return as soon as the currently-enqueued references are finalized; + * this currently waits until the queue is empty. http://b/4193517 + */ + public synchronized void waitUntilFinalizerIsIdle() throws InterruptedException { + idle = false; + interrupt(); + while (!idle) { + wait(); + } + } + } + + /** + * The watchdog exits the VM if the finalizer ever gets stuck. We consider + * the finalizer to be stuck if it spends more than MAX_FINALIZATION_MILLIS + * on one instance. + */ + private static class FinalizerWatchdogDaemon extends Daemon { + private static final FinalizerWatchdogDaemon INSTANCE = new FinalizerWatchdogDaemon(); + + @Override public void run() { + while (isRunning()) { + Object object = FinalizerDaemon.INSTANCE.finalizingObject; + long startedNanos = FinalizerDaemon.INSTANCE.finalizingStartedNanos; + + long sleepMillis = MAX_FINALIZE_MILLIS; + if (object != null) { + long elapsedMillis = (System.nanoTime() - startedNanos) / NANOS_PER_MILLI; + sleepMillis -= elapsedMillis; + } + + if (sleepMillis > 0) { + try { + Thread.sleep(sleepMillis); + } catch (InterruptedException e) { + continue; + } + } + + if (object == null + || object != FinalizerDaemon.INSTANCE.finalizingObject + || VMRuntime.getRuntime().isDebuggerActive()) { + continue; + } + + // The current object has exceeded the finalization deadline; abort! + long elapsedMillis = (System.nanoTime() - startedNanos) / NANOS_PER_MILLI; + Exception syntheticException = new TimeoutException(); + syntheticException.setStackTrace(FinalizerDaemon.INSTANCE.getStackTrace()); + System.logE(object.getClass().getName() + ".finalize() timed out after " + + elapsedMillis + " ms; limit is " + MAX_FINALIZE_MILLIS + " ms", + syntheticException); + System.exit(2); + } + } + } +} diff --git a/luni/src/main/java/java/lang/FinalizerThread.java b/luni/src/main/java/java/lang/FinalizerThread.java deleted file mode 100644 index 978332c..0000000 --- a/luni/src/main/java/java/lang/FinalizerThread.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package java.lang; - -import java.lang.ref.FinalizerReference; -import java.lang.ref.ReferenceQueue; - -/** - * @hide - */ -public final class FinalizerThread extends Thread { - public static ReferenceQueue<Object> queue = new ReferenceQueue<Object>(); - private static FinalizerThread finalizerThread; - private static boolean idle; - - private FinalizerThread() { - super("Finalizer"); - setDaemon(true); - } - - public void run() { - FinalizerReference<Object> reference = null; - while (true) { - /* - * Finalize references until the queue is empty. - */ - if (reference == null) { - reference = (FinalizerReference<Object>) queue.poll(); - } - while (reference != null) { - doFinalize(reference); - reference = (FinalizerReference<Object>) queue.poll(); - } - - /* - * Mark this thread as idle and wait on ReferenceQueue.remove() - * until awaken by either an enqueued reference or an interruption. - */ - synchronized (FinalizerThread.class) { - idle = true; - FinalizerThread.class.notifyAll(); - if (finalizerThread != this) { - return; - } - } - try { - reference = (FinalizerReference<Object>) queue.remove(); - } catch (InterruptedException ignored) { - } - synchronized (FinalizerThread.class) { - idle = false; - } - } - } - - @FindBugsSuppressWarnings("FI_EXPLICIT_INVOCATION") - private void doFinalize(FinalizerReference<Object> reference) { - FinalizerReference.remove(reference); - Object obj = reference.get(); - reference.clear(); - try { - obj.finalize(); - } catch (Throwable ex) { - // The RI silently swallows these, but Android has always logged. - System.logE("Uncaught exception thrown by finalizer", ex); - } - } - - /** - * Awakens the finalizer thread if necessary and then wait for it to - * become idle again. When that happens, all finalizable references enqueued - * at the time of this method call will have been finalized. - * - * TODO: return as soon as the currently-enqueued references are finalized; - * this currently waits until the queue is empty. http://b/4193517 - */ - public static synchronized void waitUntilFinalizerIsIdle() throws InterruptedException { - idle = false; - finalizerThread.interrupt(); - while (!idle) { - FinalizerThread.class.wait(); - } - } - - public static synchronized void startFinalizer() { - if (finalizerThread != null) { - throw new IllegalStateException(); - } - - idle = false; - finalizerThread = new FinalizerThread(); - finalizerThread.start(); - } - - public static synchronized void stopFinalizer() { - if (finalizerThread == null) { - throw new IllegalStateException(); - } - - idle = false; - finalizerThread.interrupt(); - finalizerThread = null; - try { - while (!idle) { - FinalizerThread.class.wait(); - } - } catch (InterruptedException e) { - throw new AssertionError(); - } - } -} diff --git a/luni/src/main/java/java/lang/Runtime.java b/luni/src/main/java/java/lang/Runtime.java index 0097615..72b0711 100644 --- a/luni/src/main/java/java/lang/Runtime.java +++ b/luni/src/main/java/java/lang/Runtime.java @@ -402,7 +402,7 @@ public class Runtime { */ public void runFinalization() { try { - FinalizerThread.waitUntilFinalizerIsIdle(); + Daemons.waitUntilFinalizerIsIdle(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } diff --git a/luni/src/main/java/java/lang/Thread.java b/luni/src/main/java/java/lang/Thread.java index 67c914a..a61f669 100644 --- a/luni/src/main/java/java/lang/Thread.java +++ b/luni/src/main/java/java/lang/Thread.java @@ -37,6 +37,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import libcore.util.EmptyArray; /** * A {@code Thread} is a concurrent unit of execution. It has its own call stack @@ -69,8 +70,6 @@ import java.util.Map; * */ public class Thread implements Runnable { - private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0]; - private static final int NANOS_PER_MILLI = 1000000; /** Park states */ @@ -590,7 +589,7 @@ public class Thread implements Runnable { */ public StackTraceElement[] getStackTrace() { StackTraceElement ste[] = VMStack.getThreadStackTrace(this); - return ste != null ? ste : EMPTY_STACK_TRACE; + return ste != null ? ste : EmptyArray.STACK_TRACE_ELEMENT; } /** diff --git a/luni/src/main/java/java/lang/ref/FinalizerReference.java b/luni/src/main/java/java/lang/ref/FinalizerReference.java index 2d5cef2..4751e33 100644 --- a/luni/src/main/java/java/lang/ref/FinalizerReference.java +++ b/luni/src/main/java/java/lang/ref/FinalizerReference.java @@ -16,12 +16,12 @@ package java.lang.ref; -import java.lang.FinalizerThread; - /** * @hide */ public final class FinalizerReference<T> extends Reference<T> { + public static final ReferenceQueue<Object> queue = new ReferenceQueue<Object>(); + private static FinalizerReference head = null; private T zombie; @@ -45,7 +45,6 @@ public final class FinalizerReference<T> extends Reference<T> { } static void add(Object referent) { - ReferenceQueue<Object> queue = FinalizerThread.queue; FinalizerReference<?> reference = new FinalizerReference<Object>(referent, queue); synchronized (FinalizerReference.class) { reference.prev = null; diff --git a/luni/src/main/java/java/lang/ref/Reference.java b/luni/src/main/java/java/lang/ref/Reference.java index db33813..85fbb04 100644 --- a/luni/src/main/java/java/lang/ref/Reference.java +++ b/luni/src/main/java/java/lang/ref/Reference.java @@ -71,8 +71,10 @@ public abstract class Reference<T> { * singly linked list of reference objects discovered by the * garbage collector and awaiting processing by the reference * queue thread. + * + * @hide */ - volatile Reference<?> pendingNext; + public volatile Reference<?> pendingNext; /** * Constructs a new instance of this class. @@ -98,8 +100,10 @@ public abstract class Reference<T> { * * @return {@code true} if this call has caused the {@code Reference} to * become enqueued, or {@code false} otherwise + * + * @hide */ - final synchronized boolean enqueueInternal() { + public final synchronized boolean enqueueInternal() { if (queue != null && queueNext == null) { queue.enqueue(this); queue = null; diff --git a/luni/src/main/java/java/lang/ref/ReferenceQueue.java b/luni/src/main/java/java/lang/ref/ReferenceQueue.java index 0c04467..494cc97 100644 --- a/luni/src/main/java/java/lang/ref/ReferenceQueue.java +++ b/luni/src/main/java/java/lang/ref/ReferenceQueue.java @@ -131,7 +131,8 @@ public class ReferenceQueue<T> { notify(); } - static Reference unenqueued = null; + /** @hide */ + public static Reference unenqueued = null; static void add(Reference<?> list) { synchronized (ReferenceQueue.class) { diff --git a/luni/src/main/java/java/lang/ref/ReferenceQueueThread.java b/luni/src/main/java/java/lang/ref/ReferenceQueueThread.java deleted file mode 100644 index 201c240..0000000 --- a/luni/src/main/java/java/lang/ref/ReferenceQueueThread.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package java.lang.ref; - -/** - * @hide - */ -public final class ReferenceQueueThread extends Thread { - private static ReferenceQueueThread thread = null; - - public ReferenceQueueThread() { - super("ReferenceQueue"); - setDaemon(true); - } - - /** - * Moves each element from the pending list to the reference queue - * list. The pendingNext field is owned by the garbage collector - * so no synchronization is required to perform the unlinking. - */ - private static void doEnqueue(Reference list) { - while (list != null) { - Reference reference; - if (list == list.pendingNext) { - reference = list; - reference.pendingNext = null; - list = null; - } else { - reference = list.pendingNext; - list.pendingNext = reference.pendingNext; - reference.pendingNext = null; - } - reference.enqueueInternal(); - } - } - - public void run() { - for (;;) { - Reference list; - try { - synchronized (ReferenceQueue.class) { - while (ReferenceQueue.unenqueued == null) { - ReferenceQueue.class.wait(); - } - list = ReferenceQueue.unenqueued; - ReferenceQueue.unenqueued = null; - } - } catch (InterruptedException ex) { - break; - } - doEnqueue(list); - } - } - - public static synchronized void startReferenceQueue() { - if (thread != null) { - throw new IllegalStateException("already started"); - } - thread = new ReferenceQueueThread(); - thread.start(); - } - - public static synchronized void stopReferenceQueue() { - if (thread == null) { - throw new IllegalStateException("not started"); - } - thread.interrupt(); - for (;;) { - try { - thread.join(); - } catch (InterruptedException ex) { - continue; - } - break; - } - thread = null; - } -} diff --git a/luni/src/main/java/libcore/util/EmptyArray.java b/luni/src/main/java/libcore/util/EmptyArray.java index 2040ec1..6c99878 100644 --- a/luni/src/main/java/libcore/util/EmptyArray.java +++ b/luni/src/main/java/libcore/util/EmptyArray.java @@ -29,4 +29,5 @@ public final class EmptyArray { public static final Object[] OBJECT = new Object[0]; public static final String[] STRING = new String[0]; public static final Throwable[] THROWABLE = new Throwable[0]; + public static final StackTraceElement[] STACK_TRACE_ELEMENT = new StackTraceElement[0]; } diff --git a/luni/src/test/java/libcore/java/lang/ref/FinalizeTest.java b/luni/src/test/java/libcore/java/lang/ref/FinalizeTest.java index 3dda61e..33a8d17 100644 --- a/luni/src/test/java/libcore/java/lang/ref/FinalizeTest.java +++ b/luni/src/test/java/libcore/java/lang/ref/FinalizeTest.java @@ -16,6 +16,7 @@ package libcore.java.lang.ref; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import junit.framework.TestCase; @@ -81,4 +82,29 @@ public final class FinalizeTest extends TestCase { .printStackTrace(); } } + + /** + * The finalizer watch dog exits the VM if any object takes more than 10 s + * to finalize. Check that objects near that limit are okay. + */ + public void testWatchdogDoesNotFailForObjectsThatAreNearTheDeadline() throws Exception { + CountDownLatch latch = new CountDownLatch(5); + createSlowFinalizer( 1, latch); + createSlowFinalizer(1000, latch); + createSlowFinalizer(2000, latch); + createSlowFinalizer(4000, latch); + createSlowFinalizer(8000, latch); + induceFinalization(); + latch.await(); + } + + public void createSlowFinalizer(final long millis, final CountDownLatch latch) { + new Object() { + @Override protected void finalize() throws Throwable { + System.out.println("finalize sleeping " + millis + " ms"); + Thread.sleep(millis); + latch.countDown(); + } + }; + } } |