如何处理: java.util.concurt.TimeoutException: android.os. BinderProxy.finalize ()在错误发生10秒后超时?

我们在 GcWatcher.finalize, BinderProxy.finalizePlainSocketImpl.finalize中看到了一些 TimeoutExceptions。其中90% 以上都发生在 Android 4.3上。我们收到了来自 Crittercism 的报告,来自实地的用户。

enter image description here

错误是“ com.android.internal.BinderInternal$GcWatcher.finalize() timed out after 10 seconds”的变体

java.util.concurrent.TimeoutException: android.os.BinderProxy.finalize() timed out after 10 seconds
at android.os.BinderProxy.destroy(Native Method)
at android.os.BinderProxy.finalize(Binder.java:459)
at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:187)
at java.lang.Daemons$FinalizerDaemon.run(Daemons.java:170)
at java.lang.Thread.run(Thread.java:841)

到目前为止,我们还没有任何运气在内部重现这个问题,也没有找出可能导致这个问题的原因。

知道是什么引起的吗? 有什么办法可以调试这个程序,并找出哪一部分的应用程序导致这个? 任何能揭示这个问题的东西都会有所帮助。

更多堆栈痕迹:

1   android.os.BinderProxy.destroy
2   android.os.BinderProxy.finalize Binder.java, line 482
3   java.lang.Daemons$FinalizerDaemon.doFinalize    Daemons.java, line 187
4   java.lang.Daemons$FinalizerDaemon.run   Daemons.java, line 170
5   java.lang.Thread.run    Thread.java, line 841

2

1   java.lang.Object.wait
2   java.lang.Object.wait   Object.java, line 401
3   java.lang.ref.ReferenceQueue.remove ReferenceQueue.java, line 102
4   java.lang.ref.ReferenceQueue.remove ReferenceQueue.java, line 73
5   java.lang.Daemons$FinalizerDaemon.run   Daemons.java, line 170
6   java.lang.Thread.run

3

1   java.util.HashMap.newKeyIterator    HashMap.java, line 907
2   java.util.HashMap$KeySet.iterator   HashMap.java, line 913
3   java.util.HashSet.iterator  HashSet.java, line 161
4   java.util.concurrent.ThreadPoolExecutor.interruptIdleWorkers    ThreadPoolExecutor.java, line 755
5   java.util.concurrent.ThreadPoolExecutor.interruptIdleWorkers    ThreadPoolExecutor.java, line 778
6   java.util.concurrent.ThreadPoolExecutor.shutdown    ThreadPoolExecutor.java, line 1357
7   java.util.concurrent.ThreadPoolExecutor.finalize    ThreadPoolExecutor.java, line 1443
8   java.lang.Daemons$FinalizerDaemon.doFinalize    Daemons.java, line 187
9   java.lang.Daemons$FinalizerDaemon.run   Daemons.java, line 170
10  java.lang.Thread.run

4

1   com.android.internal.os.BinderInternal$GcWatcher.finalize   BinderInternal.java, line 47
2   java.lang.Daemons$FinalizerDaemon.doFinalize    Daemons.java, line 187
3   java.lang.Daemons$FinalizerDaemon.run   Daemons.java, line 170
4   java.lang.Thread.run
125623 次浏览

我们经常在我们的应用程序中看到这种情况,使用 Crashlytics。崩溃通常发生在平台代码中。一个小样本:

Database. CursorWindow.finalize ()在10秒后超时

Finalize ()在10秒后超时

Bitmap $BitmapFinalizer.finalize ()在10秒后超时

10秒后超时

并发.ThreadPoolExecutor.finalize ()在10秒后超时

Finalize ()在10秒后超时

Path.finalize ()在10秒后超时

发生这种情况的设备绝大多数(但不仅限于)是由三星制造的。这可能仅仅意味着我们的大多数用户使用的是三星设备; 或者,这可能表明三星设备存在问题。我不太确定。

我想这并没有真正回答你的问题,但我只是想强调,这似乎相当普遍,并不是特定于您的应用程序。

广播接收机10秒后超时。可能你做了一个异步调用(错误)从广播接收器和4.3实际上检测到它。

完整披露 -我是 TLV DroidCon 中前面提到的演讲的作者。

我曾有机会在很多 Android 应用程序中研究这个问题,并与其他遇到过这个问题的开发人员进行讨论——我们都得出了同样的结论: 这个问题无法避免,只能最小化。

我仔细研究了 Android 垃圾收集器代码的默认实现,以便更好地理解为什么会抛出这个异常,以及可能的原因是什么。我甚至在实验中找到了一个可能的根源。

问题的根源在于一个设备“入睡”了一段时间——这意味着操作系统已经决定通过暂停大多数 User Land 进程、关闭 Screen、减少 CPU 周期等来降低电池消耗。这样做的方式-是在 Linux 系统级别上,其中的进程是暂停中期运行。这可能在正常的 Application 执行过程中的任何时候发生,但是它将在一个 Nativesystem 调用时停止,因为上下文切换是在内核级别上完成的。这就是 Dalvik GC 加入故事的地方。

Dalvik GC 代码(在 AOSP 站点的 Dalvik 项目中实现)不是一段复杂的代码。它的基本工作方式在我的 DroidCon 幻灯片中有介绍。我没有涉及的是基本的 GC 循环——在收集器有一个需要完成(和销毁)的对象列表的时候。底层的循环逻辑可以简化如下:

  1. 采用 starting_timestamp,
  2. 为要释放的对象列表删除对象,
  3. 释放对象 -finalize(),并在需要时调用本机 destroy(),
  4. 采用 end_timestamp,
  5. 计算(end_timestamp - starting_timestamp)并与10秒的硬编码超时值进行比较,
  6. 如果已经超时-抛出 java.util.concurrent.TimeoutException并终止进程。

现在考虑以下情况:

应用程序继续执行它的任务。

这不是一个面向用户的应用程序,它在后台运行。

在这个后台操作期间,创建、使用和需要收集对象以释放内存。

应用程序不麻烦与 WakeLock-因为这将影响电池的负面影响,似乎没有必要。

这意味着 Application 将不时地调用 GC。

通常 GC 的运行是顺利完成的。

有时(非常罕见)系统会决定在 GC 运行的过程中睡眠。

如果您运行应用程序的时间足够长,并且密切监视 Dalvik 内存日志,就会出现这种情况。

现在——考虑一下基本 GC 循环的时间戳逻辑——设备可以启动运行,获取一个 start_stamp,然后在系统对象的 destroy()本机调用时进入休眠状态。

当它醒来并继续运行时,destroy()将结束,下一个 end_stamp将是它执行 destroy()调用 + 睡眠时间的时间。

如果睡眠时间很长(超过10秒) ,将抛出 java.util.concurrent.TimeoutException

我在分析 python 脚本生成的图表中看到了这一点——用于 Android 系统应用程序,而不仅仅是我自己监控的应用程序。

收集足够的日志,你最终会看到它。

总之:

这个问题是无法避免的——如果你的应用程序在后台运行,你就会遇到这个问题。

你可以通过使用 WakeLock 来减轻症状,防止设备陷入睡眠状态,但这完全是另外一回事,也是一个新的麻烦,也许是另一个骗局中的另一个对话。

您可以通过减少 GC 调用来最小化这个问题——降低场景的可能性(技巧在幻灯片中)。

我还没有机会去研究 Dalvik 2(又名 ART)的 GC 代码——它拥有一个新的分代压缩功能,或者在 Android Lollipop 上进行任何实验。

增补于2015年7月5日:

在回顾了这种崩溃类型的崩溃报告聚合之后,看起来 Android OS 5.0 + 版本(带 ART 的棒棒糖)的崩溃只占这种崩溃类型的0.5% 。这意味着 ART GC 的改变减少了这些崩溃的频率。

增补6/1/2016:

看起来 Android 项目已经在 Dalvik 2.0(又名 ART)中添加了很多关于 GC 如何工作的信息。

你可以在这里阅读-调试 ART 垃圾收集

本文还讨论了一些获取应用程序 GC 行为信息的工具。

向应用程序进程发送 SIGQUIT 实际上会导致 ANR,并将应用程序状态转储到日志文件以进行分析。

有一件事情是不可避免的,那就是在这个时候,设备会因为一些内存而窒息(这通常是 GC 最有可能被触发的原因)。

正如前面几乎所有作者提到的,当 Android 尝试在后台运行应用程序时,这个问题就会浮出水面。在我们观察到的大多数情况下,用户通过锁定屏幕暂停应用程序。 这也可能表明应用程序中的某个地方存在内存泄漏,或者设备已经被加载过多。 所以唯一合法的方法就是:

  • 确保没有内存泄漏,以及
  • 减少应用程序的内存占用。

FinalizeQueue 可能太长

我认为 Java 可能需要 SuppressFinalize ()ReregisterForFinalize ()来让用户显式地减少 finalizedQueue 长度

如果 JVM 的源代码可用,可以自己实现这些方法,比如 android ROM maker

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 (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}

看起来像是 Android 运行时的 bug。似乎有一个终结器在其单独的线程中运行,如果对象不在堆栈跟踪的当前框架中,它就会调用对象的 finalize ()方法。 例如,以下代码(为验证此问题而创建)以崩溃结束。

让一些游标在 finalize 方法中执行某些操作(例如,SqlCipher 方法,执行 close ()锁定当前使用的数据库)

private static class MyCur extends MatrixCursor {




public MyCur(String[] columnNames) {
super(columnNames);
}


@Override
protected void finalize() {
super.finalize();


try {
for (int i = 0; i < 1000; i++)
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

我们做一些长时间运行的东西,打开光标:

for (int i = 0; i < 7; i++) {
new Thread(new Runnable() {
@Override
public void run() {
MyCur cur = null;
try {
cur = new MyCur(new String[]{});
longRun();
} finally {
cur.close();
}
}


private void longRun() {
try {
for (int i = 0; i < 1000; i++)
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}

这导致以下错误:

FATAL EXCEPTION: FinalizerWatchdogDaemon
Process: la.la.land, PID: 29206
java.util.concurrent.TimeoutException: MyCur.finalize() timed out after 10 seconds
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:371)
at java.lang.Thread.sleep(Thread.java:313)
at MyCur.finalize(MessageList.java:1791)
at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:222)
at java.lang.Daemons$FinalizerDaemon.run(Daemons.java:209)
at java.lang.Thread.run(Thread.java:762)

SqlCipher 的产品变体非常类似:

12-21 15:40:31.668: E/EH(32131): android.content.ContentResolver$CursorWrapperInner.finalize() timed out after 10 seconds
12-21 15:40:31.668: E/EH(32131): java.util.concurrent.TimeoutException: android.content.ContentResolver$CursorWrapperInner.finalize() timed out after 10 seconds
12-21 15:40:31.668: E/EH(32131): 	at java.lang.Object.wait(Native Method)
12-21 15:40:31.668: E/EH(32131): 	at java.lang.Thread.parkFor$(Thread.java:2128)
12-21 15:40:31.668: E/EH(32131): 	at sun.misc.Unsafe.park(Unsafe.java:325)
12-21 15:40:31.668: E/EH(32131): 	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:161)
12-21 15:40:31.668: E/EH(32131): 	at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:840)
12-21 15:40:31.668: E/EH(32131): 	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:873)
12-21 15:40:31.668: E/EH(32131): 	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1197)
12-21 15:40:31.668: E/EH(32131): 	at java.util.concurrent.locks.ReentrantLock$FairSync.lock(ReentrantLock.java:200)
12-21 15:40:31.668: E/EH(32131): 	at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:262)
12-21 15:40:31.668: E/EH(32131): 	at net.sqlcipher.database.SQLiteDatabase.lock(SourceFile:518)
12-21 15:40:31.668: E/EH(32131): 	at net.sqlcipher.database.SQLiteProgram.close(SourceFile:294)
12-21 15:40:31.668: E/EH(32131): 	at net.sqlcipher.database.SQLiteQuery.close(SourceFile:136)
12-21 15:40:31.668: E/EH(32131): 	at net.sqlcipher.database.SQLiteCursor.close(SourceFile:510)
12-21 15:40:31.668: E/EH(32131): 	at android.database.CursorWrapper.close(CursorWrapper.java:50)
12-21 15:40:31.668: E/EH(32131): 	at android.database.CursorWrapper.close(CursorWrapper.java:50)
12-21 15:40:31.668: E/EH(32131): 	at android.content.ContentResolver$CursorWrapperInner.close(ContentResolver.java:2746)
12-21 15:40:31.668: E/EH(32131): 	at android.content.ContentResolver$CursorWrapperInner.finalize(ContentResolver.java:2757)
12-21 15:40:31.668: E/EH(32131): 	at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:222)
12-21 15:40:31.668: E/EH(32131): 	at java.lang.Daemons$FinalizerDaemon.run(Daemons.java:209)
12-21 15:40:31.668: E/EH(32131): 	at java.lang.Thread.run(Thread.java:762)

简历: 尽快关闭游标。至少在三星 S8和 Android 7上,这个问题已经被发现了。

我们通过停止 FinalizerWatchdogDaemon解决了这个问题。

public static void fix() {
try {
Class clazz = Class.forName("java.lang.Daemons$FinalizerWatchdogDaemon");


Method method = clazz.getSuperclass().getDeclaredMethod("stop");
method.setAccessible(true);


Field field = clazz.getDeclaredField("INSTANCE");
field.setAccessible(true);


method.invoke(field.get(null));


}
catch (Throwable e) {
e.printStackTrace();
}
}

您可以在应用程序的生命周期中调用该方法,如 attachBaseContext()。 出于同样的原因,你也可以指定手机的制造商来解决这个问题,这取决于你。

对于您创建的类(即不是 Android 的一部分) ,完全避免崩溃是可能的。

任何实现 finalize()的类都有一些不可避免的崩溃的可能性,正如@oba 所解释的那样。因此,不要使用终结器执行清理,而是使用 PhantomReferenceQueue

有关示例,请查看 React Native.https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/jni/DestructorThread.java中的实现

滴滴提供了一个有效的解决方案,因为这个错误非常普遍,而且很难找到原因,它看起来更像是一个系统问题,为什么我们不能直接忽略它,当然我们可以忽略它,这是示例代码:

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) {
} else {
defaultUncaughtExceptionHandler.uncaughtException(t, e);
}
}
});

通过设置一个特殊的默认未捕获异常处理程序,应用程序可以改变那些已经接受系统提供的任何默认行为的线程处理未捕获异常的方式。当从名为 FinalizerWatchdogDaemon的线程抛出未捕获的 TimeoutException时,这个特殊的处理程序将阻塞处理程序链,不会调用系统处理程序,因此可以避免崩溃。

通过实践,没有发现其他不良影响。GC 系统仍在工作,随着 CPU 使用的减少,超时问题得到缓解。

有关详细信息,请参阅: https://mp.weixin.qq.com/s/uFcFYO2GtWWiblotem2bGg