Comment gérer:java.util.simultané.TimeoutException: Android.OS.BinderProxy.finalize () a expiré après des erreurs de 10 secondes?


Nous voyons un certain nombre de TimeoutExceptions dans GcWatcher.finalize, BinderProxy.finalize, et PlainSocketImpl.finalize. 90 + % d'entre eux se produisent sur Android 4.3. Nous recevons des rapports à ce sujet de Crittercism des utilisateurs sur le terrain.

entrez la description de l'image ici

L'erreur est une variation de: "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)

Jusqu'à présent, nous n'avons pas eu de chance de reproduire le problème en interne ou de déterminer ce qui aurait pu le causer.

Des idées ce qui peut causer cela? Une idée de comment déboguer cela et savoir quelle partie de l'application provoque cette? Tout ce qui met en lumière le problème aide.

Plus de 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
Author: noamtm, 2014-06-03

11 answers

Full disclosure - Je suis l'auteur du discours mentionné précédemment dans TLV DroidCon.

J'ai eu l'occasion d'examiner ce problème dans de nombreuses applications Android et d'en discuter avec d'autres développeurs qui l'ont rencontré - et nous sommes tous arrivés au même point: ce problème ne peut pas être évité, seulement minimisé.

J'ai examiné de plus près l'implémentation par défaut du code Garbage collector Android, pour mieux comprendre pourquoi cette exception est levée et sur ce qui pourrait l'être les causes possibles. J'ai même trouvé une cause racine possible lors de l'expérimentation.

La racine du problème est au point où un appareil "se met en veille" pendant un certain temps - cela signifie que le système d'exploitation a décidé de réduire la consommation de la batterie en arrêtant la plupart des processus terrestres de l'utilisateur pendant un certain temps et en éteignant l'écran, en réduisant les cycles du PROCESSEUR, etc. La façon dont cela est fait - est au niveau du système Linux où les processus sont mis en pause à mi-course. Cela peut se produire à tout moment pendant l'exécution normale de l'application, mais il s'arrêtera à un appel système natif, car le changement de contexte est effectué au niveau du noyau. Donc, c'est là que le Dalvik GC rejoint l'histoire.

Le code du GC Dalvik (tel qu'implémenté dans le projet Dalvik sur le site du PSBA) n'est pas un morceau de code compliqué. La façon dont cela fonctionne de base est couverte dans mes diapositives DroidCon. Ce que je n'ai pas couvert, c'est la boucle GC de base - au point où le collecteur a une liste d'objets à finaliser (et à détruire). La logique de boucle à la base peut être simplifiée comme ceci:

  1. prendre starting_timestamp,
  2. supprimer l'objet pour la liste des objets à libérer,
  3. libération de l'objet - finalize() et appel natif destroy(), si nécessaire,
  4. prendre end_timestamp,
  5. calculez (end_timestamp - starting_timestamp) et comparez avec une valeur de délai d'attente codée en dur de 10 secondes,
  6. si le délai d'attente a atteint - lancez le java.util.concurrent.TimeoutException et tuez le processus.

Considérons maintenant le scénario suivant:

L'application fonctionne le long de faire sa chose.

, Ce n'est pas un utilisateur face à la demande, il s'exécute en arrière-plan.

Au cours de cette opération en arrière-plan, des objets sont créés, utilisés et doivent être collectés pour libérer de la mémoire.

L'application ne dérange pas avec un WakeLock - car cela affectera négativement la batterie et semble inutile.

Cela signifie que l'Application invoquera le GC de temps à autre.

Normalement, l'exécution du GC est terminée sans accroc.

Parfois (très rarement) le système décidera de dormez au milieu de la course GC.

Cela se produira si vous exécutez votre application assez longtemps et surveillez de près les journaux de mémoire Dalvik.

Maintenant - considérez la logique d'horodatage de la boucle GC de base - il est possible pour le périphérique de démarrer l'exécution, de prendre un start_stamp et de se mettre en veille à l'appel natif destroy() sur un objet système.

Quand il se réveille et reprend la course, la destroy() va se terminer, et la prochaine end_stamp sera le temps qu'il a fallu le destroy() appel + sommeil temps.

Si le temps de sommeil était long (plus de 10 secondes), le java.util.concurrent.TimeoutException sera lancé.

J'ai vu cela dans les graphiques générés à partir du script Python d'analyse - pour les applications système Android, pas seulement mes propres applications surveillées.

Collectez suffisamment de journaux et vous finirez par le voir.

Ligne de fond:

Le problème ne peut pas être évitée - vous le rencontrerez si votre application s'exécute en arrière-plan.

Vous pouvez atténuer en prenant un WakeLock, et empêcher l'appareil de dormir, mais c'est une autre histoire, et un nouveau mal de tête, et peut-être un autre discours dans un autre con.

Vous pouvez minimiser le problème en réduisant les appels GC - ce qui rend le scénario moins probable (les conseils sont dans les diapositives).

Je n'ai pas encore eu la chance de passer en revue le code GC Dalvik 2 (alias ART) - qui dispose d'une nouvelle fonctionnalité de compactage générationnel, ou d'effectuer des expériences sur un Android Lollipop.

Ajouté 7/5/2015:

Après avoir examiné l'agrégation des rapports de crash pour ce type de crash, il semble que ces plantages de la version 5.0+ d'Android OS (Lollipop avec ART) ne représentent que 0.5% de ce type de crash. Cela signifie que les modifications de l'ART GC ont réduit la fréquence de ces accidents.

Ajouté 6/1/2016:

On dirait que le projet Android a ajouté beaucoup d'informations sur le fonctionnement du GC dans Dalvik 2.0 (alias ART).

Vous pouvez lire à ce sujet ici - Débogage de l'ART de la Collecte des Ordures.

Il discute également de certains outils pour obtenir des informations sur le comportement du GC pour votre application.

L'envoi d'un SIGQUIT à votre processus d'application provoquera essentiellement un ANR et videra l'état de l'application dans un fichier journal pour analyse.

 228
Author: oba, 2019-11-07 00:51:36

Nous voyons cela constamment, partout dans notre application, en utilisant Crashlytics. Le plantage se produit généralement dans le code de la plate-forme. Un petit échantillon:

Android.la base de données.CursorWindow.finalize () a expiré après 10 secondes

Java.util.regex.Matcher.finalize () a expiré après 10 secondes

Android.graphique.Bitmap$BitmapFinalizer.finalize () a expiré après 10 secondes

Org.Apache.http.impl.Connecticut.SingleClientConnManager.finalize () a expiré après 10 secondes

Java.util.simultané.ThreadPoolExecutor.finalize () a expiré après 10 secondes

Android.OS.BinderProxy.finalize () a expiré après 10 secondes

Android.graphique.Chemin.finalize () a expiré après 10 secondes

Les appareils sur lesquels cela se produit sont majoritairement (mais pas exclusivement) des appareils fabriqués par Samsung. Cela pourrait simplement signifier que la plupart de nos utilisateurs utilisent des appareils Samsung; alternativement, cela pourrait indiquer un problème avec les appareils Samsung. Je suis pas vraiment sûr.

Je suppose que cela ne répond pas vraiment à vos questions, mais je voulais juste renforcer le fait que cela semble assez commun et n'est pas spécifique à votre application.

 75
Author: Kevin Coppock, 2020-06-20 09:12:55

J'ai trouvé quelques diapositives sur ce problème.

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

Dans cette diapositive, l'auteur dit que cela semble être un problème avec GC, s'il y a beaucoup d'objets ou d'énormes objets dans le tas. La diapositive inclut également une référence à un exemple d'application et un script python pour analyser ce problème.

Https://github.com/oba2cat3/GCTest

Https://github.com/oba2cat3/logcat2memorygraph

De plus, j'ai trouvé un indice dans le commentaire # 3 de ce côté: https://code.google.com/p/android/issues/detail?id=53418#c3

 15
Author: Christopher, 2014-11-17 12:49:45

Nous avons résolu le problème en arrêtant le 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();
    }
}

Vous pouvez appeler la méthode dans le cycle de vie de l'application, comme attachBaseContext(). Pour la même raison, vous pouvez également spécifier la fabrication du téléphone pour résoudre le problème, c'est à vous de décider.

 8
Author: Enaoi, 2018-01-22 17:33:10

Délai d'attente des récepteurs de diffusion après 10 secondes. Il est possible que vous fassiez un appel asynchrone (incorrect) à partir d'un récepteur de diffusion et que 4.3 le détecte réellement.

 5
Author: danny117, 2014-06-18 01:27:35

Voici une solution efficace de didi pour résoudre ce problème, Puisque ce bug est très commun et difficile à trouver la cause, Il ressemble plus à un problème système, Pourquoi ne pouvons-nous pas l'ignorer directement?Bien sûr, nous pouvons l'ignorer, Voici l'exemple de code:

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

En définissant un gestionnaire d'exceptions non capturées par défaut spécial, l'application peut changer la façon dont les exceptions non capturées sont gérées pour les threads qui accepteraient déjà le comportement par défaut fourni par le système. Lorsque un TimeoutException non intercepté est lancé à partir d'un thread nommé FinalizerWatchdogDaemon, ce gestionnaire spécial bloquera la chaîne du gestionnaire, le gestionnaire système ne sera pas appelé, donc le crash sera évité.

Grâce à la pratique, aucun autre effet néfaste n'a été trouvé. Le système GC fonctionne toujours, les délais d'attente sont allégés à mesure que l'utilisation du processeur diminue.

Pour plus de détails, voir: https://mp.weixin.qq.com/s/uFcFYO2GtWWiblotem2bGg

 5
Author: kiwi, 2019-05-06 06:15:21

Une chose qui est invariablement vraie est qu'à ce moment, le périphérique suffoquerait pour une certaine mémoire (ce qui est généralement la raison pour laquelle GC est probablement déclenché).

Comme mentionné par presque tous les auteurs précédemment, ce problème apparaît lorsque Android essaie d'exécuter GC alors que l'application est en arrière-plan. Dans la plupart des cas où nous l'avons observé, l'utilisateur a mis l'application en pause en verrouillant son écran. Cela peut également indiquer une fuite de mémoire quelque part dans l'application, ou l'appareil étant trop chargé déjà. Donc, le seul moyen légitime de le minimiser est:

  • il n'y a pas de fuites de mémoire, et
  • afin de réduire l'empreinte mémoire de l'application en général.
 4
Author: Sankalp Sharma, 2015-09-23 19:41:51
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();
}
 1
Author: kot32, 2017-08-14 09:14:14

La finalizeQueue peut être trop longue

Je pense que java peut nécessiter GC.SuppressFinalize() & GC.ReRegisterForFinalize () pour permettre à l'utilisateur de réduire explicitement la longueur finalizedQueue

Si le code source de la JVM est disponible, peut implémenter ces méthodes nous-mêmes, telles que android ROM maker

 0
Author: Yessy, 2016-06-20 09:42:57

Cela semble être un bug d'exécution Android. Il semble y avoir finalizer qui s'exécute dans son thread séparé et appelle la méthode finalize() sur les objets s'ils ne sont pas dans le cadre actuel de la stacktrace. Par exemple, le code suivant (créé pour vérifier ce problème) s'est terminé par le plantage.

Ayons un curseur qui fait quelque chose dans la méthode finalize (par exemple SqlCipher ones, do close () qui verrouille la base de données actuellement utilisée)

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

Et nous faisons des choses de longue durée ayant curseur ouvert:

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

Cela provoque l'erreur suivante:

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)

La variante de production avec SqlCipher est très similaire:

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)

Reprendre: Fermer les curseurs DÈS QUE POSSIBLE. Au moins sur Samsung S8 avec Android 7 où le problème a été vu.

 0
Author: vbevans94, 2017-12-22 12:12:27

Pour les classes que vous créez (ie. ne font pas partie de l'Android) il est possible d'éviter complètement le crash.

Toute classe qui implémente finalize() a une probabilité inévitable de plantage comme expliqué par @oba. Donc, au lieu d'utiliser des finaliseurs pour effectuer le nettoyage, utilisez un PhantomReferenceQueue.

Pour un exemple, consultez l'implémentation dans React Native: https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/jni/DestructorThread.java

 0
Author: Ben, 2018-10-31 18:03:32