web-dev-qa-db-fra.com

Comment gérer: Java.util.concurrent.TimeoutException: Android.os.BinderProxy.finalize () a expiré après une erreur de 10 secondes?

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

enter image description here

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'ici, nous n'avons pas eu la chance de reproduire le problème en interne ou de déterminer ce qui l'aurait causé.

Avez-vous une idée de ce qui peut causer cela? Tout ce qui éclaire la question 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

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
142
emmby

Divulgation complète - Je suis l'auteur de l'exposé 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 les autres développeurs qui l'ont rencontré - et nous sommes tous arrivés au même point: ce problème ne peut être évité, mais seulement minimisé.

J'ai examiné de plus près l'implémentation par défaut du code du collecteur Android Garbage afin de mieux comprendre pourquoi cette exception est Thrown et quelles pourraient en être les causes possibles. J'ai même trouvé une cause possible lors de l'expérimentation.

La racine du problème se situe au moment où un périphérique «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 User Land pendant un certain temps et en désactivant Screen, réduisant ainsi les cycles du processeur. , etc. La façon dont cela est fait est au niveau du système Linux où les processus sont en pause à mi-parcours. Cela peut se produire à tout moment au cours de l'exécution normale de l'application, mais cela s'arrêtera lors d'un appel système natif, car la commutation de contexte est effectuée au niveau du noyau. Donc, c’est là que le GC Dalvik rejoint l’histoire . Le code du GC Dalvik (tel qu’implémenté dans le projet Dalvik sur le site AOSP) n’est pas un morceau de code compliqué. La manière dont cela fonctionne est décrite dans mes diapositives DroidCon. Ce que je n’ai pas abordé, 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_membrement_horaire,
  2. supprimer un objet pour la liste des objets à libérer,
  3. libérer l'objet - finalize() et appeler destroy() si nécessaire,
  4. prendre end_timestamp,
  5. calculer (end_timestamp - starting_timestamp) et comparer avec une valeur de délai d'attente codée en dur de 10 secondes,
  6. si le délai est écoulé - lancez le concurrent.TimeoutException et tuez le processus.

Considérons maintenant le scénario suivant:

L'application fonctionne le long de sa tâche. ce n'est pas une application orientée utilisateur, elle s'exécute en arrière-plan. Au cours de cette opération en arrière-plan, les objets sont créés, utilisés et doivent être collectés pour libérer de la mémoire. L'application ne s'embarrasse pas avec un Wakelock, car cela affecterait négativement la batterie et semblerait inutile. cela signifie que l'application invoquera le GC de temps à autre. Normalement, le cycle du GC est terminé sans accroc. Parfois (très rarement) le système décidera de dormir au milieu de la course du GC. Cela se produira si vous exécutez votre application suffisamment longtemps et surveillez de près les journaux de la mémoire Dalvik. Maintenant - tenez compte de la logique d'horodatage de la boucle de base du CPG - il est possible pour le périphérique de démarrer l'exécution, de procéder à un start_stamp et de passer en veille à l'appel natif destroy() sur un objet système. quand il se réveillera et reprendra son exécution, la destroy() se terminera et le prochain end_stamp sera l'heure à laquelle l'appel destroy() a été pris + l'heure de veille. Si le temps de sommeil était long - plus de 10 secondes, l'exception concurrent.timeout sera levée.

Je l'ai constaté dans les graphiques générés à partir du script d'analyse Python - pour les applications système Android, et pas seulement pour mes propres applications surveillées. collecter suffisamment de journaux, vous finirez par le voir.

Ligne de fond:

Le problème ne peut être évité - vous le rencontrerez si votre application s'exécute en arrière-plan. Vous pouvez atténuer l'effet en prenant un wakelock et empêcher le périphérique de dormir, mais c'est une tout autre histoire, un nouveau mal de tête et peut-être une autre conversation.

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

Je n'ai pas encore eu l'occasion de passer en revue le code GC Dalvik 2 (a.k.a ART), doté d'une nouvelle fonctionnalité de compactage générationnel, ni de réaliser des expériences sur un Lollipop Android.

Ajouté le 05/5/2015:

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

Ajouté le 01/06/2016:

On dirait que le projet Android a ajouté de nombreuses informations sur le fonctionnement du GC dans Dalvik 2.0 (a.k.a ART). Vous pouvez en savoir plus à ce sujet ici - Débogage de la récupération de place ART . Il aborde également certains outils permettant d’obtenir des informations sur le comportement du CPG pour votre application. L'envoi d'un fichier SIGQUIT au processus de votre application provoquera essentiellement un ANR et dumpera l'état de l'application dans un fichier journal pour analyse.

182
oba

Nous voyons cela constamment, partout dans notre application, en utilisant Crashlytics. Le crash se produit généralement bien en code de plateforme. Un petit échantillon:

Android.database.CursorWindow.finalize () a expiré après 10 secondes

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

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

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

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

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

Android.graphics.Path.finalize () a expiré après 10 secondes

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

Je suppose que cela ne répond pas vraiment à vos questions, mais je voulais simplement souligner que cela semble assez courant et n'est pas spécifique à votre application.

63
kcoppock

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

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

Dans ces diapositives, l'auteur explique qu'il semble y avoir un problème avec GC, s'il y a beaucoup d'objets ou d'énormes objets dans heap. La diapositive comprend é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 n ° 3 de ce côté: https://code.google.com/p/Android/issues/detail?id=53418#c3

14
Christopher

Les récepteurs de diffusion expirent après 10 secondes. Vous effectuez éventuellement un appel asynchrone (incorrect) à partir d'un récepteur de diffusion et le 4.3 le détecte réellement.

4
danny117

Nous avons résolu le problème en arrêtant la 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, à vous de décider.

4
Enaoi
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
kot32

Une chose qui est invariablement vraie est qu’à ce moment-là, le périphérique suffoquerait pour avoir un peu de mémoire (ce qui est généralement la raison pour laquelle GC est le plus susceptible de se déclencher). 

Comme mentionné par presque tous les auteurs plus tôt, ce problème se pose lorsque Android tente d'exécuter GC lorsque l'application est en arrière-plan. Dans la plupart des cas où nous l'avons observé, l'utilisateur a suspendu l'application en verrouillant son écran . Cela pourrait également indiquer une fuite de mémoire quelque part dans l'application, ou le périphérique déjà trop chargé . minimiser c'est:

  • pour s'assurer qu'il n'y a pas de fuites de mémoire, et
  • réduire l'empreinte mémoire de l'application en général.
1
Sankalp Sharma

La file d'attente finalize 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 machine virtuelle Java est disponible, vous pouvez implémenter vous-même cette méthode, telle qu'Android ROM maker 

0
Yessy

Cela ressemble à un bug d'Android Runtime. Il semble qu'un finaliseur s'exécute dans son thread séparé et appelle la méthode finalize () sur des objets s'ils ne figurent pas dans l'image actuelle de stacktrace .

Ayons un curseur qui fait quelque chose dans la méthode finalize (par exemple, ceux de SqlCipher, ferme () qui se verrouille sur la base de données en cours d'utilisation)

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 trucs longs avec le 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)

Résumé: Fermer les curseurs dès que possible. Au moins sur Samsung S8 avec Android 7 où le problème a été vu.

0
vbevans94

Voici une solution efficace de didi pour résoudre ce problème, puisque ce bogue est très courant et qu'il est difficile d'en 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, ici est 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 par défaut non capturé spécial, l'application peut modifier 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. Quand une TimeoutException non capturée est lancée à partir d'un thread nommé FinalizerWatchdogDaemon, ce gestionnaire spécial bloquera la chaîne de gestionnaires, le gestionnaire de système ne sera pas appelé et un crash sera évité.

Grâce à la pratique, aucun autre effet négatif n'a été trouvé. Le système CPG fonctionne toujours, les délais d'attente sont réduits à mesure que l'utilisation du processeur diminue.

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

0
kiwi

Pour les classes que vous créez (c.-à-d. Ne faisant pas partie d'Android), il est possible d'éviter complètement le crash. 

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

Pour un exemple, consultez la mise en œuvre dans React Native: https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/Java/com/facebook/jni/DestructorThread.Java

0
Ben