背景
最近Android项目,越来越多手机上报 java.util.concurrent.TimeoutException
异常。具体出错信息如下:
出错信息
com.android.internal.os.BinderInternal$GcWatcher.finalize() timed out after 10 seconds
出错函数堆栈
php
1 java.lang.Object.wait(Native Method)
2 java.lang.Object.wait(Object.java:422)
3 java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:188)
4 java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:209)
5 java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:232)
6 java.lang.Daemons$Daemon.run(Daemons.java:103)
7 java.lang.Thread.run(Thread.java:764)
小结
在执行某些对象的finalize方法进行特定资源回收时,需要在10s 内执行完毕,不然就会出现超时错误。
垃圾回收机制中,finalize方法的作用
java.lang.Daemons类存储JVM能正常运行的后台线程。
HeapTaskDaemon
是 Android 系统中的一个守护线程,它负责监控和管理 Dalvik 虚拟机的堆内存ReferenceQueueDaemon
是 Android 系统中的一个守护线程,它负责监控和管理 Java 对象的引用队列。FinalizerDaemon
是 Android 系统中的一个守护线程,它负责执行 Java 对象的 finalize() 方法。FinalizerWatchdogDaemon
是 Android 系统中的一个守护线程,它负责监控和管理 Java 对象的 finalize() 方法。
Object的finalize方法作用
在Java中,垃圾回收器负责回收不再使用的对象。在回收对象之前,垃圾回收器会调用对象的finalize()
方法。如果对象没有重写finalize()
方法,则该方法不会执行任何操作。如果对象重写了finalize()
方法,则该方法会在垃圾回收器回收对象之前被调用。
需要注意的是,
finalize()
方法的使用应该谨慎。因为finalize()
方法的执行时间是不确定的,可能会导致系统性能下降。另外,finalize()
方法只会被调用一次,因此不能保证对象的资源一定会被释放。
Object.finalize方法的核心执行过程
FinalizerDaemon
类的运行流程如下:
- 在 Java 虚拟机启动时,
FinalizerDaemon
线程会被启动。 FinalizerDaemon
线程会创建一个ReferenceQueue
对象,用于存放 finalizer 引用。FinalizerDaemon
线程会进入一个无限循环,等待 finalizer 引用被加入队列。- 当一个 finalizer 引用被加入队列时,
FinalizerDaemon
会通知FinalizerWatchdogDaemon
去监控finalize方法的执行时间,并执行finalize
方法。如果监控到方法执行超时,则发出警告。 finalize
方法执行完成后,FinalizerDaemon
会被阻塞等待finalizer引用加入队列。重复步骤 4-5
FinalizerDaemon代码逻辑
java
private static class FinalizerDaemon extends Daemon {
private Object finalizingObject = null;
FinalizerDaemon() {
super("FinalizerDaemon");
}
@Override public void runInternal() {
int localProgressCounter = progressCounter.get();
while (isRunning()) {
try {
FinalizerReference<?> finalizingReference = (FinalizerReference<?>)queue.poll();
if (finalizingReference != null) {
finalizingObject = finalizingReference.get();
progressCounter.lazySet(++localProgressCounter);
} else {
finalizingObject = null;
progressCounter.lazySet(++localProgressCounter);
FinalizerWatchdogDaemon.INSTANCE.goToSleep();
// 1.
// 阻塞线程,直到队列中获取到需要回收的引用
finalizingReference = (FinalizerReference<?>)queue.remove();
finalizingObject = finalizingReference.get();
progressCounter.set(++localProgressCounter);
// 2.
// 唤醒FinalizerWatchdogDaemon监控程序,监控finalize方法执行
FinalizerWatchdogDaemon.INSTANCE.wakeUp();
}
// 3.
// 核心逻辑调用引用的finalize方法
doFinalize(finalizingReference);
} catch (InterruptedException ignored) {
} catch (OutOfMemoryError ignored) {
}
}
}
@FindBugsSuppressWarnings("FI_EXPLICIT_INVOCATION")
private void doFinalize(FinalizerReference<?> reference) {
FinalizerReference.remove(reference);
Object object = reference.get();
reference.clear();
try {
// 4. 核心方法
// 调用finalize()方法,回收对象
object.finalize();
} catch (Throwable ex) {
System.logE("Uncaught exception thrown by finalizer", ex);
} finally {
finalizingObject = null;
}
}
}
Object.finalize方法的警告机制核心执行过程
FinalizerWatchdogDaemon
核心执行流程如下
- 在Java虚拟机启动时,
FinalizerWatchdogDaemon
后台线程会被启动,并且进入睡眠状态,直到FinalizerWatchdogDaemon
被唤醒 - 记录当前监控的初始时间,然后执行sleep函数,睡眠10s。
- 睡醒之后,判断
finalize
方法是否超时。 - 超时则发出超时错误警告,否则重复2-4的步骤。
java
private static class FinalizerWatchdogDaemon extends Daemon {
@UnsupportedAppUsage
private static final FinalizerWatchdogDaemon INSTANCE = new FinalizerWatchdogDaemon();
private boolean needToWork = true; // Only accessed in synchronized methods.
private long finalizerTimeoutMs = 0; // Lazily initialized.
FinalizerWatchdogDaemon() {
super("FinalizerWatchdogDaemon");
}
@Override public void runInternal() {
while (isRunning()) {
if (!sleepUntilNeeded()) {
continue;
}
// 1
// 执行Sleep函数,去睡眠10s
final Object finalizing = waitForFinalization();
// 2
// 判断finalizing结果是否为空。空则表示超时,并将警告发出
if (finalizing != null && !VMRuntime.getRuntime().isDebuggerActive()) {
finalizerTimedOut(finalizing);
break;
}
}
}
private synchronized boolean sleepUntilNeeded() {
while (!needToWork) {
try {
wait();
} catch (InterruptedException e) {
// Daemon.stop may have interrupted us.
return false;
} catch (OutOfMemoryError e) {
return false;
}
}
return true;
}
private synchronized void goToSleep() {
needToWork = false;
}
private synchronized void wakeUp() {
needToWork = true;
notify();
}
private synchronized boolean getNeedToWork() {
return needToWork;
}
private boolean sleepForMillis(long durationMillis) {
long startMillis = System.currentTimeMillis();
while (true) {
long elapsedMillis = System.currentTimeMillis() - startMillis;
long sleepMillis = durationMillis - elapsedMillis;
if (sleepMillis <= 0) {
return true;
}
try {
Thread.sleep(sleepMillis);
} catch (InterruptedException e) {
if (!isRunning()) {
return false;
}
} catch (OutOfMemoryError ignored) {
if (!isRunning()) {
return false;
}
}
}
}
private Object waitForFinalization() {
if (finalizerTimeoutMs == 0) {
finalizerTimeoutMs = VMRuntime.getRuntime().getFinalizerTimeoutMs();
MAX_FINALIZE_NANOS = NANOS_PER_MILLI * finalizerTimeoutMs;
}
long startCount = FinalizerDaemon.INSTANCE.progressCounter.get();
if (!sleepForMillis(finalizerTimeoutMs)) {
return null;
}
if (getNeedToWork() && FinalizerDaemon.INSTANCE.progressCounter.get() == startCount) {
if (getNeedToWork()
&& FinalizerDaemon.INSTANCE.progressCounter.get() == startCount) {
return finalizing;
}
}
return null;
}
private static void finalizerTimedOut(Object object) {
Exception syntheticException = new TimeoutException(message);
try {
Os.kill(Os.getpid(), OsConstants.SIGQUIT);
// Sleep a few seconds to let the stack traces print.
Thread.sleep(5000);
} catch (Exception e) {
System.logE("failed to send SIGQUIT", e);
} catch (OutOfMemoryError ignored) {
// May occur while trying to allocate the exception.
}
if (Thread.getUncaughtExceptionPreHandler() == null &&
Thread.getDefaultUncaughtExceptionHandler() == null) {
// If we have no handler, log and exit.
System.logE(message, syntheticException);
System.exit(2);
}
Thread.currentThread().dispatchUncaughtException(syntheticException);
}
}
警告机制的好处&缺点
好处
FinalizerDaemon 线程负责执行回收对象的finalize方法。 FinalizerWatchdogDaemon线程负责监控finalize方法执行是否超时。 两者异步执行,相互尽量不干扰,提高了程序的效率和程序的安全性。
缺点
如果因为系统休眠或者其它原因,导致整个JVM临时停止工作。当JVM恢复运行的时候,FinalizerWatchdogDaemon 线程有可能会报出警告错误。这种情况不多见,但目前项目中确实存在。可以参考这篇文章链接Android 经验:TimeoutException 问题分析与解决