最近项目的bugly报了一个错finalize() timed out after 10 seconds。最初遇到这个问题,本人一脸懵逼。没写过这个方法怎么会在这里面报错的?查阅了网上的资料才发现,通常这个错误发生在 java.lang.Daemons$FinalizerDaemon.doFinalize的方法中,直接原因是对象的 finalize() 方法执行超时。接下来就有必要看一下Daemons的方法。
1.主要流程
Daemons 开始于 Zygote 进程:Zygote 创建新进程后,通过 ZygoteHooks 类调用了 Daemons 类的 start() 方法,在 start() 方法中启动了 FinalizerDaemon,FinalizerWatchdogDaemon 等关联的守护线程。
FinalizerDaemon 析构守护线程
对于重写了成员函数finalize()的类,在对象创建时会新建一个 FinalizerReference 对象,这个对象封装了原对象。当原对象没有被其他对象引用时,这个对象不会被 GC 马上清除掉,而是被放入 FinalizerReference 的链表中。FinalizerDaemon 线程循环取出链表里面的对象,执行它们的 finalize() 方法,并且清除和对应 FinalizerReference对象引用关系,对应的 FinalizerReference 对象在下次执行 GC 时就会被清理掉。
FinalizerWatchdogDaemon 析构监护守护线程
析构监护守护线程用来监控 FinalizerDaemon 线程的执行,采用 Watchdog 计时器机制。当 FinalizerDaemon 线程开始执行对象的 finalize() 方法时,FinalizerWatchdogDaemon 线程会启动一个计时器,当计时器时间到了之后,检测 FinalizerDaemon 中是否还有正在执行 finalize() 的对象。检测到有对象存在后就视为 finalize() 方法执行超时,就会产生 TimeoutException 异常。
private Object waitForFinalization() {
long startCount = FinalizerDaemon.INSTANCE.progressCounter.get();
// Avoid remembering object being finalized, so as not to keep it alive.
if (!sleepFor(MAX_FINALIZE_NANOS)) {
// Don't report possibly spurious timeout if we are interrupted.
return null;
}
if (getNeedToWork() && FinalizerDaemon.INSTANCE.progressCounter.get() == startCount) {
Object finalizing = FinalizerDaemon.INSTANCE.finalizingObject;
sleepFor(NANOS_PER_SECOND / 2);
if (getNeedToWork()
&& FinalizerDaemon.INSTANCE.progressCounter.get() == startCount) {
return finalizing;
}
}
return null;
}
@Override
public void runInternal() {
while (isRunning()) {
if (!sleepUntilNeeded()) {
// We have been interrupted, need to see if this daemon has been stopped.
continue;
}
final Object finalizing = waitForFinalization();
if (finalizing != null && !VMRuntime.getRuntime().isDebuggerActive()) {
finalizerTimedOut(finalizing);
break;
}
}
}
从源码可以看出,waitForFinalization返回不为空就会报这个错。再看一下finalizerTimedOut的代码
private static void finalizerTimedOut(Object object) {
// The current object has exceeded the finalization deadline; abort!
String message = object.getClass().getName() + ".finalize() timed out after "
+ (MAX_FINALIZE_NANOS / NANOS_PER_SECOND) + " seconds";
......
if (Thread.getUncaughtExceptionPreHandler() == null &&
Thread.getDefaultUncaughtExceptionHandler() == null) {
// If we have no handler, log and exit.
System.logE(message, syntheticException);
System.exit(2);
}
}
2.原因
原因其实有很多,核心还是对象 finalize() 方法耗时较长。
比如方法内部确实有比较耗时的操作,比如 IO 操作,线程休眠等,再比如有的对象在执行 finalize() 方法时需要线程同步操作,如果长时间拿不到锁,可能会导致超时,也有可能是5.0 版本以下机型 GC 过程中 CPU 休眠导致
3.解决方法
(1)手动修改 finalize() 方法超时时间
try {
Class> c = Class.forName(“java.lang.Daemons”);
Field maxField = c.getDeclaredField(“MAX_FINALIZE_NANOS”);
maxField.setAccessible(true);
maxField.set(null, Long.MAX_VALUE);
} catch (Exception e) {
}
这种方案思路是有效的,但是这种方法却是无效的。Daemons 类中 的 MAX_FINALIZE_NANOS 是个 long 型的静态常量,代码中出现的 MAX_FINALIZE_NANOS 字段在编译期就会被编译器替换成常量,因此运行期修改是不起作用的。
(2)手动停掉 FinalizerWatchdogDaemon 线程(现在使用最多的)
这种方案利用反射 FinalizerWatchdogDaemon 的 stop() 方法,以使 FinalizerWatchdogDaemon 计时器功能永远停止。当 finalize() 方法出现超时, FinalizerWatchdogDaemon 因为已经停止而不会抛出异常。这种方案也存在明显的缺点:
- 在 Android 5.1 版本以下系统中,当 FinalizerDaemon 正在执行对象的 finalize() 方法时,调用 FinalizerWatchdogDaemon 的 stop() 方法,将导致 run() 方法正常逻辑被打断,错误判断为 finalize() 超时,直接抛出 TimeoutException。(这个我后面会解释)
- Android 9.0 版本开始限制 Private API 调用,不能再使用反射调用 Daemons 以及 FinalizerWatchdogDaemon 类方法。(本人测试过,至少在Mate20Pro上面还是可以的)
4.解决方案
先仔细分析finalizerTimedOut方法(这是android28的代码)
private static void finalizerTimedOut(Object object) {
// The current object has exceeded the finalization deadline; abort!
String message = object.getClass().getName() + ".finalize() timed out after "
+ (MAX_FINALIZE_NANOS / NANOS_PER_SECOND) + " seconds";
......
if (Thread.getUncaughtExceptionPreHandler() == null &&
Thread.getDefaultUncaughtExceptionHandler() == null) {
// If we have no handler, log and exit.
System.logE(message, syntheticException);
System.exit(2);
}
}
看一下getUncaughtExceptionPreHandler和getDefaultUncaughtExceptionHandler的默认实现(在在com.android.internal.os.RuntimeInit里面)
LoggingHandler loggingHandler = new LoggingHandler();
Thread.setUncaughtExceptionPreHandler(loggingHandler);
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
loggingHandler 的具体实现就是FATAL EXCEPTION IN SYSTEM PROCESS这个Log。KillApplicationHandler明显就是退出程序的。所以可以在这里做文章,直接ignore
final Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
if (t.getName().equals("FinalizerWatchdogDaemon") && e instanceof TimeoutException) {
Log.e("ignore", "ignore");
} else {
defaultUncaughtExceptionHandler.uncaughtException(t, e);
}
}
});
(另外补充说一句,android19里面只有getDefaultUncaughtExceptionHandler==null的判断,所以这段代码在19也能运行。不过区别在于这么设置以后19里面不会有FATAL EXCEPTION的log,而28会有,因为LoggingHandler没有覆盖掉)
5.释疑
前面提到的利用反射 FinalizerWatchdogDaemon 的 stop() 方法的两个问题本人也是测试过。
先说第二种,也就是Android 9.0 版本开始限制 Private API 调用。我的华为Mate20Pro也是9.0的,但是依然可以使用,可能这个还和厂商有关吧
第一种:stop() 方法,将导致 run() 方法正常逻辑被打断,错误判断为 finalize() 超时。
这个就有必要看一下run的方法
先看19的
@Override
public void run() {
while (isRunning()) {
Object object = waitForObject();
if (object == null) {
// We have been interrupted, need to see if this daemon has been stopped.
continue;
}
boolean finalized = waitForFinalization(object);
if (!finalized && !VMRuntime.getRuntime().isDebuggerActive()) {
finalizerTimedOut(object);
break;
}
}
}
private boolean waitForFinalization(Object object) {
sleepFor(FinalizerDaemon.INSTANCE.finalizingStartedNanos, MAX_FINALIZE_NANOS);
return object != FinalizerDaemon.INSTANCE.finalizingObject;
}
如果stop发生在waitForObject里面会直接返回null,直接continue了,不会打断正常逻辑。如果发生在waitForFinalization里面呢?也就是sleepFor里面呢?这样就直接返回。假设如下的情况,(超时默认是10s)A的finalize需要2s,一进去1s就stop了,此时A并没有finalize结束。那么finalizingObject=A,object肯定是A,那么返回false,直接就报超时错误
再看一下28
private Object waitForFinalization() {
long startCount = FinalizerDaemon.INSTANCE.progressCounter.get();
// Avoid remembering object being finalized, so as not to keep it alive.
if (!sleepFor(MAX_FINALIZE_NANOS)) {
// Don't report possibly spurious timeout if we are interrupted.
return null;
}
if (getNeedToWork() && FinalizerDaemon.INSTANCE.progressCounter.get() == startCount) {
Object finalizing = FinalizerDaemon.INSTANCE.finalizingObject;
sleepFor(NANOS_PER_SECOND / 2);
if (getNeedToWork()
&& FinalizerDaemon.INSTANCE.progressCounter.get() == startCount) {
return finalizing;
}
}
return null;
}
@Override
public void runInternal() {
while (isRunning()) {
if (!sleepUntilNeeded()) {
// We have been interrupted, need to see if this daemon has been stopped.
continue;
}
final Object finalizing = waitForFinalization();
if (finalizing != null && !VMRuntime.getRuntime().isDebuggerActive()) {
finalizerTimedOut(finalizing);
break;
}
}
}
(getNeedToWork() && FinalizerDaemon.INSTANCE.progressCounter.get() == startCount
这个判断逻辑看代码就可以知道和finalize有关,如果finalize结束,会通过lazySet来改变的)
如果stop在sleepFor(MAX_FINALIZE_NANOS)里面打断的话,会返回null,此时就走不到finalizerTimedOut
所以可以得出这样的结论,在android19手机上如果强制停掉FinalizerWatchdogDaemon,而这个stop是在waitForFinalization里面停掉的话同样也有可能出现这个错。而通常stop是在MainApplication里面的。那么报这个错只可能是一种情况:一开始启动app,但内存不够,某些对象执行了finalize方法,而此时正好碰上stop,就会有很高的几率发生(android28就不会再报这个错)
实验证明,mate20pro(9.0)确实不会再报这个错。周一的时候用公司的5.0的手机试一下强制stop会不会出问题
参考:https://segmentfault.com/a/1190000019373275