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

时间:2014-06-03 17:39:48

标签: android garbage-collection

我们在TimeoutExceptionsGcWatcher.finalize, BinderProxy.finalize中看到了一些PlainSocketImpl.finalize。其中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)

到目前为止,我们还没有幸运地在内部复制问题或弄清楚可能导致问题的原因。

任何想法会导致什么? 知道如何调试这个并找出应用程序的哪个部分导致这个? 任何能够揭示这个问题的东西都有帮助。

更多Stacktraces:

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

11 个答案:

答案 0 :(得分:186)

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

我有机会在许多Android应用程序中检查这个问题,并与遇到它的其他开发人员讨论这个问题 - 我们都达到了同样的观点:这个问题无法避免,只能最小化。

我仔细研究了Android垃圾收集器代码的默认实现,以更好地理解为什么抛出此异常以及可能的原因。我甚至在实验过程中发现了可能的根本原因。

问题的根源在于设备"进入睡眠状态"暂时 - 这意味着操作系统已经决定通过暂停大部分User Land进程一段时间来降低电池消耗,并关闭屏幕,减少CPU周期等。这样做的方法是 - 在Linux系统级别上过程暂停中期。这可以在正常的应用程序执行期间的任何时间发生,但它将在Native系统调用时停止,因为上下文切换是在内核级别完成的。所以 - 这就是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. 如果达到超时 - 抛出concurrent.TimeoutException并终止进程。
  7. 现在考虑以下情况:

    应用程序一直在运行。这不是面向用户的应用程序,它在后台运行。在此后台操作期间,将创建,使用对象并需要收集对象以释放内存。应用程序不会对Wakelock感到烦恼 - 因为这会对电池造成不利影响,而且似乎没有必要。这意味着应用程序将不时调用GC。通常情况下,GC运行完成没有任何障碍。有时(非常罕见)系统将决定在GC运行过程中休眠。如果您运行的应用程序足够长,并且密切监视Dalvik内存日志,则会发生这种情况。现在 - 考虑基本GC循环的时间戳逻辑 - 设备可以启动运行,获取start_stamp,然后在系统对象的destroy()本机调用上进入休眠状态。当它醒来并恢复运行时,destroy()将完成,下一个end_stamp将是destroy()呼叫+休眠时间所花费的时间。如果睡眠时间很长 - 超过10秒,将抛出concurrent.timeout异常。

    我在分析python脚本生成的图表中已经看到了这一点 - 对于Android系统应用程序,而不仅仅是我自己的受监控应用程序。收集足够的日志,你最终会看到它。

    底线:

    这个问题无法避免 - 如果你的应用在后台运行,你会遇到它。你可以通过唤醒唤醒来缓解,并防止设备睡觉,但这完全是一个不同的故事,一个新的头痛,也许是另一个骗局的另一个话题。

    您可以通过减少GC调用来最小化问题 - 使方案不太可能发生。提示在幻灯片中。

    我还没有机会浏览Dalvik 2(a.k.a ART)GC代码 - 它拥有新的Generational Compacting功能,或者在Android Lollipop上进行任何实验。

    已添加2015年7月5日:

    在查看此崩溃类型的崩溃报告聚合后,看起来这些来自Android OS 5.0版本(带有ART的Lollipop)的崩溃仅占此崩溃类型的0.5%。这意味着ART GC的改变降低了这些崩溃的频率。

    已添加2016年6月1日:

    看起来Android项目已经添加了很多关于GC如何在Dalvik 2.0(a.k.a ART)中工作的信息。你可以在这里阅读 - Debugging ART Garbage Collection 。它还讨论了一些获取应用程序GC行为信息的工具。将SIGQUIT发送到您的应用程序进程实际上会导致ANR,并将应用程序状态转储到日志文件进行分析。

答案 1 :(得分:65)

我们使用Crashlytics在我们的应用程序中不断地看到这一点。崩溃通常发生在平台代码中。小样本:

  

android.database.CursorWindow.finalize()在10秒后超时

     

java.util.regex.Matcher.finalize()在10秒后超时

     

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

     

org.apache.http.impl.conn.SingleClientConnManager.finalize()在10秒后超时

     

java.util.concurrent.ThreadPoolExecutor.finalize()在10秒后超时

     

android.os.BinderProxy.finalize()在10秒后超时

     

android.graphics.Path.finalize()在10秒后超时

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

我认为这并没有真正回答你的问题,但我只想强调这似乎很常见,并不是特定于你的应用程序。

答案 2 :(得分:14)

我找到了一些有关此问题的幻灯片。

http://de.slideshare.net/DroidConTLV/android-crash-analysis-and-the-dalvik-garbage-collector-tools-and-tips

在这张幻灯片中,作者告诉我,如果堆中有很多对象或大对象,那么它似乎是GC的问题。幻灯片还包括对示例应用程序的引用和用于分析此问题的python脚本。

https://github.com/oba2cat3/GCTest

https://github.com/oba2cat3/logcat2memorygraph

此外,我在这方面的评论#3中找到了一个提示:https://code.google.com/p/android/issues/detail?id=53418#c3

答案 3 :(得分:4)

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

答案 4 :(得分:4)

我们通过停止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()。 出于同样的原因,您也可以通过手机的制造商来解决问题,这取决于您。

答案 5 :(得分:2)

总有一件事是,在这个时候,设备会对某些内存感到窒息(这通常是GC很可能被触发的原因)。

正如之前几乎所有作者所提到的,当Android尝试在应用程序处于后台时运行GC时,会出现此问题。在我们观察它的大多数情况下,用户通过锁定屏幕来暂停应用程序。 这也可能表示应用程序中某处存在内存泄漏,或者设备已经过载。 因此,最小化它的唯一合法方法是:

  • 确保没有内存泄漏,
  • 通常可以减少应用内存占用。

答案 6 :(得分:1)

--exit-on-error

答案 7 :(得分:0)

finalizeQueue可能太长了

我认为java可能需要GC.SuppressFinalize()& GC.ReRegisterForFinalize()让用户明确减少finalizedQueue长度

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

答案 8 :(得分:0)

这似乎是一个Android Runtime错误。似乎有终结器在其单独的线程中运行,并且如果它们不在stacktrace的当前帧中,则调用对象的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的生产变体非常相似:

&#13;
&#13;
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)
&#13;
&#13;
&#13;

简历:尽快关闭游标。至少在使用Android 7的三星S8上已经看到问题。

答案 9 :(得分:0)

对于您创建的类(即不属于Android),可以完全避免崩溃。

任何实现finalize()的类都有不可避免的崩溃可能性,如@oba所述。因此,不要使用终结器来执行清理,而应使用PhantomReferenceQueue

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

答案 10 :(得分:0)

这是Didi解决此问题的有效解决方案,由于此bug非常普遍且难以找到原因,它看起来更像是系统问题,我们为什么不能直接忽略它?当然我们可以忽略它,这是示例代码:

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);
        }
    }
});

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

通过练习,未发现其他不良影响。 GC系统仍在工作,随着CPU使用率的降低,超时得以缓解。

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