Как обращаться с: java.util.concurrent.TimeoutException: android.os.BinderProxy.finalize() тайм-аут после 10 секунд ошибок?

Мы видим число TimeoutExceptions в GcWatcher.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)

До сих пор нам не удавалось воспроизвести проблему в доме или выяснить, что могло бы вызвать ее.

Любые идеи, что может вызвать это? Любая идея, как отладить это и выяснить, какая часть приложения вызывает это? Все, что проливает свет на проблему, помогает.

Больше стоп-кадров:

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

Ответ 1

Полное раскрытие. Я являюсь автором упомянутых выше разговоров в TLV DroidCon.

У меня была возможность изучить эту проблему во многих приложениях Android и обсудить ее с другими разработчиками, которые столкнулись с ней, - и мы все дошли до одной точки: эту проблему нельзя избежать, только минимизировать.

Я более подробно рассмотрел стандартную реализацию кода коллекционера Android Garbage, чтобы лучше понять, почему это исключение выбрано и какие могут быть возможные причины. Я даже нашел возможную основную причину во время экспериментов.

Корень проблемы заключается в том, что устройство "Goes to Sleep" какое-то время - это означает, что ОС решила снизить потребление батареи, остановив большинство процессов User Land на некоторое время и отключив Screen, сокращение циклов процессора и т.д. Как это делается - на уровне системы Linux, где процессы приостанавливаются в середине запуска. Это может произойти в любое время во время обычного выполнения приложения, но оно остановится при системном вызове Native, поскольку переключение контекста выполняется на уровне ядра. Итак - вот где Далвик GC присоединяется к истории. Код GC Dalvik (как реализован в проекте Dalvik на сайте AOSP) не является сложной частью кода. Основной способ его работы охватывает мои слайды DroidCon. то, что я не рассматривал, - это основной цикл GC - в точке, где сборщик имеет список объектов для завершения (и уничтожения). логика цикла в базе может быть упрощена следующим образом:

  • взять start_timestamp,
  • удалить объект для списка объектов для выпуска,
  • объект выпуска - finalize() и при необходимости вызовите native destroy(),
  • возьмите end_timestamp,
  • вычислить (end_timestamp - starting_timestamp) и сравнить с жестким временем таймаута 10 секунд,
  • если время ожидания достигло - выбросьте concurrent.TimeoutException и убейте процесс.

Теперь рассмотрим следующий сценарий:

Приложение работает, выполняя свою задачу. это не приложение, ориентированное на пользователя, оно работает в фоновом режиме. Во время этой фоновой операции объекты создаются, используются и должны собираться для освобождения памяти. Приложение не беспокоится о Wakelock - так как это может отрицательно сказаться на батарее и кажется ненужным. это означает, что приложение будет время от времени ссылаться на GC. Обычно работы GC завершаются без заминок. Иногда (очень редко) система решит Сон в середине прогона GC. Это произойдет, если вы запустите приложение достаточно долго и внимательно следите за журналами памяти Dalvik. Теперь рассмотрим логику метки времени базового цикла GC - возможно, чтобы устройство запустило запуск, запустило start_stamp и перешло в режим сна при вызове destroy() на системном объекте. когда он просыпается и возобновляет прогон, завершается destroy(), а следующий end_stamp будет временем, когда потребовался вызов destroy() + время сна. Если время сна было длинным - более 10 секунд, будет выведено исключение concurrent.timeout.

Я видел это на графиках, сгенерированных из анализа python script - для системных приложений Android, а не только для моих собственных контролируемых приложений. собрать достаточное количество журналов, вы в конце концов увидите его.

Нижняя строка:

Проблема не может быть устранена - вы столкнетесь с ней, если ваше приложение работает в фоновом режиме. Вы можете смягчить, взяв wakelock и не позволять устройству спать, но это совсем другая история, и новая головная боль, а может быть, еще один разговор в другом con.

Вы можете свести к минимуму проблему за счет сокращения вызовов GC, что делает сценарий менее вероятным. подсказки находятся в слайдах.

У меня еще не было возможности пройти код GC Dalvik 2 (a.k.a ART), который может похвастаться новой функцией Generational Compacting или выполнять любые эксперименты на Android Lollipop.

Добавлено 7/5/2015:

После анализа агрегации отчетов Crash для этого типа сбоя, похоже, что эти сбои от версии 5.0+ для ОС Android (Lollipop with ART) составляют только 0,5% от этого типа сбоя. Это означает, что изменения ART GC уменьшили частоту этих сбоев.

Добавлено 6/1/2016:

Похоже, проект Android добавил много информации о том, как работает GC в Dalvik 2.0 (a.k.a ART). Вы можете прочитать об этом здесь - Отладка коллекции мусора ART , В нем также рассматриваются некоторые инструменты для получения информации о поведении GC для вашего приложения. Отправка SIGQUIT в процесс вашего приложения будет по существу вызывать ANR и сбросить состояние приложения в файл журнала для анализа.

Ответ 2

Мы постоянно это видим во всем нашем приложении, используя 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 секунд

Устройства, на которых это происходит, являются подавляющими (но не исключительно) устройствами, производимыми компанией Samsung. Это может означать, что большинство наших пользователей используют устройства Samsung; он может указывать на проблему с устройствами Samsung. Я не уверен.

Я полагаю, это на самом деле не отвечает на ваши вопросы, но я просто хотел подчеркнуть, что это кажется довольно распространенным явлением и не является специфическим для вашего приложения.

Ответ 3

Я нашел несколько слайдов по этой проблеме.

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

В этом слайде автор говорит, что это проблема с GC, если в куче много объектов или огромных объектов. Слайд также включает ссылку на пример приложения и python script для анализа этой проблемы.

https://github.com/oba2cat3/GCTest

https://github.com/oba2cat3/logcat2memorygraph

Кроме того, я нашел подсказку в комментарии №3 с этой стороны: https://code.google.com/p/android/issues/detail?id=53418#c3

Ответ 4

Время ожидания радиовещательных приемников через 10 секунд. Возможно, ваш асинхронный вызов (неправильный) из широковещательного приемника и 4.3 действительно обнаруживает его.

Ответ 5

Мы решили проблему, остановив 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(). По той же причине вы также можете указать производителя телефона, чтобы решить проблему, решать вам.

Ответ 6

Одна вещь, которая неизменно верна, заключается в том, что в это время устройство будет задыхаться для некоторой памяти (что обычно является причиной того, что GC, скорее всего, срабатывает).

Как упоминалось почти всеми авторами ранее, эта проблема возникает, когда Android пытается запустить GC, когда приложение находится в фоновом режиме. В большинстве случаев, когда мы это наблюдали, пользователь приостановил приложение, заблокировав его экран. Это может также указывать на утечку памяти где-то в приложении, или уже слишком загруженное устройство. Таким образом, единственный законный способ минимизировать это:

  • для обеспечения отсутствия утечек памяти и
  • чтобы уменьшить объем памяти приложения в целом.

Ответ 7

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

Ответ 8

FinalizeQueue может быть слишком длинным

Я думаю, что java может потребовать GC.SuppressFinalize() и GC.ReRegisterForFinalize(), чтобы пользователь явно сократил длину finalizedQueue

если исходный код JVM доступен, может реализовать этот метод самостоятельно, например, создатель Android-ROM

Ответ 9

Это похоже на ошибку Android Runtime. Кажется, что есть финализатор, который работает в своем отдельном потоке и вызывает метод finalize() для объектов, если они не находятся в текущем кадре трассировки стека. Например, следующий код (созданный для проверки этой проблемы) завершился сбоем.

Пусть есть какой-то курсор, который делает что-то в методе finalize (например, SqlCipher, do 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)

Ответ 10

Для классов, которые вы создаете (то есть не являетесь частью Android), можно полностью избежать сбоя.

Любой класс, который реализует finalize() имеет некоторую неизбежную вероятность сбоя, как объяснено @oba. Поэтому вместо использования финализаторов для очистки используйте PhantomReferenceQueue.

Для примера посмотрите реализацию в React Native: https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/jni/DestructorThread.java

Ответ 11

Вот эффективное решение от 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);
        }
    }
});

Устанавливая специальный обработчик неперехваченных исключений по умолчанию, приложение может изменить способ обработки неперехваченных исключений для тех потоков, которые уже приняли бы любое поведение по умолчанию, предоставленное системой. Когда из потока с именем FinalizerWatchdogDaemon создается TimeoutException, этот специальный обработчик блокирует цепочку обработчиков, системный обработчик вызываться не будет, поэтому сбоя избежать не будет.

На практике никаких других негативных последствий обнаружено не было. Система GC все еще работает, таймауты уменьшаются по мере уменьшения загрузки процессора.

Для получения дополнительной информации см.: https://mp.weixin.qq.com/s/uFcFYO2GtWWiblotem2bGg