summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--dalvik/src/main/java/dalvik/system/VMRuntime.java5
-rw-r--r--dalvik/src/main/java/dalvik/system/Zygote.java8
-rw-r--r--luni/src/main/java/java/lang/Daemons.java274
-rw-r--r--luni/src/main/java/java/lang/FinalizerThread.java125
-rw-r--r--luni/src/main/java/java/lang/Runtime.java2
-rw-r--r--luni/src/main/java/java/lang/Thread.java5
-rw-r--r--luni/src/main/java/java/lang/ref/FinalizerReference.java5
-rw-r--r--luni/src/main/java/java/lang/ref/Reference.java8
-rw-r--r--luni/src/main/java/java/lang/ref/ReferenceQueue.java3
-rw-r--r--luni/src/main/java/java/lang/ref/ReferenceQueueThread.java92
-rw-r--r--luni/src/main/java/libcore/util/EmptyArray.java1
-rw-r--r--luni/src/test/java/libcore/java/lang/ref/FinalizeTest.java26
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();
+ }
+ };
+ }
}