Estamos vendo um número de TimeoutExceptions
in GcWatcher.finalize, BinderProxy.finalize
e PlainSocketImpl.finalize
. Mais de 90% deles acontecem no Android 4.3. Estamos recebendo relatórios sobre isso do Crittercism de usuários em campo.
O erro é uma variação 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)
Até agora, não tivemos sorte em reproduzir o problema internamente ou em descobrir o que poderia ter causado o problema.
Alguma idéia do que pode causar isso? Alguma idéia de como depurar isso e descobrir qual parte do aplicativo causa isso? Tudo o que lança luz sobre o assunto ajuda.
Mais 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
android
garbage-collection
empapar
fonte
fonte
Respostas:
Divulgação completa - Sou o autor da palestra mencionada anteriormente no TLV DroidCon.
Tive a chance de examinar esse problema em muitos aplicativos Android e discuti-lo com outros desenvolvedores que o encontraram - e todos chegamos ao mesmo ponto: esse problema não pode ser evitado, apenas minimizado.
Analisei mais de perto a implementação padrão do código do coletor de lixo do Android, para entender melhor por que essa exceção é lançada e quais poderiam ser as possíveis causas. Eu até encontrei uma possível causa raiz durante a experimentação.
A raiz do problema está no momento em que um dispositivo "entra no modo de suspensão" por um tempo - isso significa que o sistema operacional decidiu diminuir o consumo de bateria, interrompendo a maioria dos processos de User Land por um tempo e desligando a tela, reduzindo os ciclos da CPU , etc. A maneira como isso é feito - é no nível do sistema Linux, onde os processos são Pausados no meio da execução. Isso pode acontecer a qualquer momento durante a execução normal do aplicativo, mas será interrompido em uma chamada do sistema nativo, pois a alternância de contexto é feita no nível do kernel. Então - é aqui que o Dalvik GC se junta à história.
O código do Dalvik GC (conforme implementado no projeto Dalvik no site da AOSP) não é um trecho complicado de código. A maneira básica de como funciona é abordada nos meus slides do DroidCon. O que eu não cobri é o loop básico do GC - no ponto em que o coletor tem uma lista de objetos para finalizar (e destruir). A lógica do loop na base pode ser simplificada assim:
starting_timestamp
,finalize()
e chamar nativo,destroy()
se necessário,end_timestamp
,end_timestamp - starting_timestamp
) e compare com um valor de tempo limite codificado de 10 segundos,java.util.concurrent.TimeoutException
e mate o processo.Agora considere o seguinte cenário:
O aplicativo é executado ao mesmo tempo.
Este não é um aplicativo voltado para o usuário, é executado em segundo plano.
Durante essa operação em segundo plano, os objetos são criados, usados e precisam ser coletados para liberar memória.
O aplicativo não se incomoda com um WakeLock - pois isso afetará adversamente a bateria e parece desnecessário.
Isso significa que o Aplicativo invocará o GC de tempos em tempos.
Normalmente, a execução do GC é concluída sem problemas.
Às vezes (muito raramente) o sistema decide dormir no meio da execução do GC.
Isso acontecerá se você executar o aplicativo por tempo suficiente e monitorar os logs de memória Dalvik de perto.
Agora - considere a lógica do registro de data e hora do loop básico do GC - é possível que o dispositivo inicie a execução, faça uma
start_stamp
e vá dormir nadestroy()
chamada nativa em um objeto do sistema.Quando ele acorda e retoma a execução, a
destroy()
finalização será concluída e a próximaend_stamp
será a hora em que adestroy()
ligação foi realizada + o tempo de suspensão.Se o tempo de sono for longo (mais de 10 segundos),
java.util.concurrent.TimeoutException
será lançado.Vi isso nos gráficos gerados a partir do script python de análise - para aplicativos do sistema Android, não apenas para meus próprios aplicativos monitorados.
Colete logs suficientes e você o verá eventualmente.
Bottom line:
O problema não pode ser evitado - você o encontrará se o aplicativo for executado em segundo plano.
Você pode atenuar usando um WakeLock e impedir que o dispositivo durma, mas isso é uma história completamente diferente, e uma nova dor de cabeça e talvez outra conversa em outro golpe.
Você pode minimizar o problema reduzindo as chamadas do GC - tornando o cenário menos provável (as dicas estão nos slides).
Ainda não tive a chance de revisar o código do Dalvik 2 (também conhecido como ART) GC - que possui um novo recurso de Compactação Geracional ou realizei experimentos em um pirulito Android.
Adicionado 7/5/2015:
Após revisar a agregação de relatórios de falhas para esse tipo de falha, parece que essas falhas da versão 5.0 ou superior do Android OS (Lollipop with ART) representam apenas 0,5% desse tipo de falha. Isso significa que as alterações do ART GC reduziram a frequência dessas falhas.
Adicionado 1/6/2016:
Parece que o projeto Android adicionou muitas informações sobre como o GC funciona no Dalvik 2.0 (também conhecido como ART).
Você pode ler sobre isso aqui - Depurando ART Garbage Collection .
Ele também discute algumas ferramentas para obter informações sobre o comportamento do GC para seu aplicativo.
Enviar um SIGQUIT para o processo do aplicativo causará essencialmente um ANR e despejará o estado do aplicativo em um arquivo de log para análise.
fonte
Vemos isso constantemente, em todo o nosso aplicativo, usando Crashlytics. A falha geralmente acontece no código da plataforma. Uma pequena amostra:
Os dispositivos nos quais isso acontece são predominantemente (mas não exclusivamente) dispositivos fabricados pela Samsung. Isso pode significar apenas que a maioria dos nossos usuários está usando dispositivos Samsung; alternativamente, isso pode indicar um problema nos dispositivos Samsung. Eu não tenho certeza.
Suponho que isso realmente não responda às suas perguntas, mas eu só queria reforçar que isso parece bastante comum e não é específico para o seu aplicativo.
fonte
Encontrei alguns slides sobre esse problema.
http://de.slideshare.net/DroidConTLV/android-crash-analysis-and-the-dalvik-garbage-collector-tools-and-tips
Nestes slides, o autor diz que parece haver um problema com o GC, se houver muitos objetos ou objetos enormes no heap. O slide também inclui uma referência a um aplicativo de exemplo e um script python para analisar esse problema.
https://github.com/oba2cat3/GCTest
https://github.com/oba2cat3/logcat2memorygraph
Além disso, encontrei uma dica no comentário nº 3 deste lado: https://code.google.com/p/android/issues/detail?id=53418#c3
fonte
Resolvemos o problema parando o
FinalizerWatchdogDaemon
.Você pode chamar o método no ciclo de vida do aplicativo, como
attachBaseContext()
. Pelo mesmo motivo, você também pode especificar a fabricação do telefone para resolver o problema, a decisão é sua.fonte
Tempo limite dos receptores de transmissão após 10 segundos. Possivelmente você está fazendo uma chamada assíncrona (incorreta) de um receptor de broadcast e o 4.3 realmente a detecta.
fonte
Aqui está uma solução eficaz da didi para resolver esse problema. Como esse bug é muito comum e difícil de encontrar a causa, parece mais um problema do sistema. Por que não podemos ignorá-lo diretamente? É claro que podemos ignorá-lo. é o código de amostra:
Ao definir um manipulador de exceções não capturado padrão especial, o aplicativo pode alterar a maneira como as exceções não capturadas são tratadas para os segmentos que já aceitariam qualquer comportamento padrão fornecido pelo sistema. Quando um não capturado
TimeoutException
é lançado de um encadeamento chamadoFinalizerWatchdogDaemon
, esse manipulador especial bloqueia a cadeia do manipulador, o manipulador do sistema não será chamado e, portanto, a falha será evitada.Através da prática, nenhum outro efeito ruim foi encontrado. O sistema do GC ainda está funcionando, os tempos limite são aliviados à medida que o uso da CPU diminui.
Para obter mais detalhes, consulte: https://mp.weixin.qq.com/s/uFcFYO2GtWWiblotem2bGg
fonte
Uma coisa que é invariavelmente verdadeira é que, neste momento, o dispositivo estaria sufocando alguma memória (que geralmente é o motivo do GC provavelmente ser acionado).
Como mencionado por quase todos os autores anteriormente, esse problema aparece quando o Android tenta executar o GC enquanto o aplicativo está em segundo plano. Na maioria dos casos em que o observamos, o usuário pausou o aplicativo bloqueando sua tela. Isso também pode indicar vazamento de memória em algum lugar do aplicativo ou o dispositivo já está carregado demais. Portanto, a única maneira legítima de minimizá-lo é:
fonte
fonte
O finalizeQueue pode ser muito longo
Eu acho que o Java pode exigir GC.SuppressFinalize () e GC.ReRegisterForFinalize () para permitir que o usuário reduza explicitamente o comprimento finalizedQueue
se o código fonte da JVM estiver disponível, podemos implementar esses métodos, como o ROM ROM Android
fonte
Parece um bug do Android Runtime. Parece haver um finalizador que é executado em seu thread separado e chama o método finalize () nos objetos se eles não estiverem no quadro atual do rastreamento de pilha. Por exemplo, o código a seguir (criado para verificar esse problema) terminou com a falha.
Vamos ter um cursor que faça algo no método finalize (por exemplo, SqlCipher, do close () que bloqueia o banco de dados que está sendo usado no momento)
E fazemos algumas coisas de execução longa abrindo o cursor:
Isso causa o seguinte erro:
A variante de produção com SqlCipher é muito semelhante:
Resumo: Feche os cursores o mais rápido possível. Pelo menos no Samsung S8 com Android 7, onde o problema foi visto.
fonte
Para as classes que você cria (ou seja, não faz parte do Android), é possível evitar completamente a falha.
Qualquer classe que implementa
finalize()
tem alguma probabilidade inevitável de travar, conforme explicado pelo @oba. Portanto, em vez de usar finalizadores para executar a limpeza, use aPhantomReferenceQueue
.Por exemplo, confira a implementação no React Native: https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/jni/DestructorThread.java
fonte