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


167

我们看到了许多TimeoutExceptionsin GcWatcher.finalize, BinderProxy.finalizePlainSocketImpl.finalize。其中90%以上是在Android 4.3上进行的。我们从现场的用户那里收到了来自Crittercism的报告。

在此处输入图片说明

错误是以下内容的变体:“ 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

2
没关系,发现它bugzilla.mozilla.org/show_bug.cgi?id=864102我还可以确认它正在影响我们的应用程序,闻起来像是Google Play服务问题
eveliotc

引发错误的代码行引入了版本4.3_r1,该版本于2013年6月5日发布。此后可能一直在发生问题。
edubriguenti

Android版本4.2.2也开始引发此异常,因此它可能是源于Google Play的更新。
JWqvist

@EvelioTarazona我在某些不使用播放服务的应用程序中拥有它
ligi

@ligi对您来说是相同的堆栈跟踪吗?
eveliotc

Answers:


220

完全公开 -我是TLV DroidCon中先前提到的演讲的作者。

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

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

问题的根源在于设备“进入睡眠”状态一段时间-这意味着操作系统已决定通过停止大部分User Land进程一段时间并关闭屏幕以减少CPU周期来降低电池消耗。等等。此操作的方法-在Linux系统级别上,该进程在运行中被暂停。在正常的应用程序执行期间,任何时候都可能发生这种情况,但是由于上下文切换是在内核级别完成的,因此它将在本机系统调用时停止。所以-这就是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的困扰-因为这会对电池产生不利影响,并且似乎是不必要的。

这意味着应用程序将不时调用GC。

通常,GC运行会顺利进行。

有时(非常罕见),系统将决定在GC运行期间进入休眠状态。

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

现在-考虑基本GC循环的时间戳逻辑-设备可以在系统对象start_stampdestroy()本机调用上开始运行,进行并进入睡眠状态。

当它醒来并恢复运行时,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的Lollipop)仅占该崩溃类型的0.5%。这意味着ART GC的更改减少了这些崩溃的频率。

添加6/1/2016:

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

您可以在这里阅读有关调试ART垃圾收集的信息

它还讨论了一些工具来获取有关应用程序GC行为的信息。

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


就我而言,我还计划通过寻找减少我在后台运行的代码/时间的方式来尝试减轻这种情况。感谢您对此主题的研究。
parkerfath 2015年

删除您的应用程序中完成的任何后台处理将大大有助于减少问题。
oba

就其价值而言,这仍然发生在棉花糖(6.0.1)中。也就是说,我只收到过一次此错误。因此,这似乎不是一个巨大的问题。感谢您的详尽解释。
克诺索斯

一段时间后,我得到明显的印象,即在操作系统中解决此问题非常麻烦,需要Google与OEM之间的合作。我不希望这一问题很快得到解决。
oba

我正在使用唤醒锁,但在Android 4.4.2上仍然遇到此问题。我的应用程序具有一些后台操作,但主要设计为可以在安装充电电缆的情况下全天工作。有什么其他方法可以缓解此问题?
Orcun Sevsay

74

我们使用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秒后超时

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

我想这并不能真正回答您的问题,但是我只是想强调一点,这似乎很普遍,并且并非特定于您的应用程序。


16
它也适用于Android 5.0.1版本,而且似乎不限于三星设备。这件事发生在Nexus 6
Shobhit普里

4
我在XIAOMI生产的设备的android 4.4.4上遇到了这个问题
Paresh Dudhat

只是想说明一下,我们在三星平板电脑上看到了大多数此类崩溃。不确定三星在平板电脑处理后台应用程序方面做了哪些不同的工作。
FriendlyMikhail

1
我在android 4.4.4上有这个问题。设备制造商。
Rameshbabu

1
如果我在android 5.0.2 Samsung设备上使用泄漏金丝雀库,我的应用程序崩溃。如果我禁用库初始化,则该应用程序可以正常运行。
vanomart

15

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

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


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

您可以在Application的生命周期中调用方法,例如attachBaseContext()。出于相同的原因,您还可以指定手机的制造商来解决问题,这取决于您。


对我们不起作用,我不明白为什么。代码无一例外地完成,但是我们仍然会在Crashlytics报告和Google Play控制台中收到这些问题。
安东·布鲁乌索夫

5

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


3
似乎无法检测到并且没有告诉您足够的信息。让我们知道哪个广播会很好。
亚伦·T·哈里斯

如果我错了,请原谅,但我不认为广播接收器超时会导致此特定崩溃。避免10s限制是个好习惯,但这与请求者的问题不同。
parkerfath 2014年

我的大脑只有十秒钟。developer.android.com/training/articles/perf-anr.html IDK(如果它也导致崩溃)。
danny117

您的观点很扎实,也是一种很好的做法。但是,原始张贴者对一组特定的设备存在特定的问题。我建议此帖子的其他查看者如果看到相同的症状(三星设备(尤其是Galaxy S 4)等),请检查克里斯托弗的回答和oba的回答
parkerfath 2015年

我不是在这里抨击设备制造商,这是违反条款的。
danny117

5

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

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


4

总是正确的一件事是,此时设备将充满一些内存(这通常是GC最有可能被触发的原因)。

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

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

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

如果睡眠持续时间> 100秒,则无法解决问题。为什么不将其设置为MAX_INT?
oba

是的,我只是做例子〜
kot32

1
由于不断的内联,所以这不起作用。更改字段值不会影响内联到调用方的值。
hqzxzwb


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的生产变体非常相似:

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)

继续:尽快关闭游标。至少在看到Android 7问题的三星S8上。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.