OutOfMemoryError é uma exceção fatal que ocorre quando a Máquina Virtual Java (JVM) ou o Android Runtime (ART) não consegue alocar memória para um novo objeto devido à falta de espaço no Heap. De acordo com a Square Engineering, 70% dos OutOfMemoryError em aplicativos móveis são causados por vazamentos de memória, não pela ultrapassagem real do limite. Compreender as causas do OOM é a chave para a estabilidade do aplicativo.
Principais Conclusões
OutOfMemoryError (OOM) é uma exceção da família VirtualMachineError em Java/Kotlin que sinaliza a incapacidade de alocar memória para um novo objeto. Diferente das exceções verificadas, OOM é um Error e não requer tratamento através de catch — embora tecnicamente possa ser capturado. Após ocorrer um OOM, o aplicativo geralmente fica em um estado instável e é recomendado encerrá-lo.
No Android, cada aplicativo tem um limite de Heap definido pelo fabricante do dispositivo. Para smartphones modernos com 6+ GB de RAM, o limite é de 256–512 MB, para dispositivos econômicos — 128–192 MB. Quando o volume total de todos os objetos vivos excede esse limite, o ART lança OutOfMemoryError.
É importante entender: OOM nem sempre significa que o dispositivo ficou sem memória física. Significa que o aplicativo esgotou seu limite de Heap definido pelo sistema. Outros aplicativos podem ter memória livre, mas seu aplicativo não pode usá-la devido ao isolamento de processos no Android.
Cinco cenários levam regularmente ao OOM em aplicativos móveis. Cada cenário está associado a um tipo específico de dado ou operação.
Bitmap é o principal consumidor de memória em aplicativos Android. Carregar uma imagem FullHD (1920 × 1080) no tamanho original ocupa 8,3 MB no formato ARGB_8888. Se houver 50 dessas imagens em um RecyclerView, são 415 MB, excedendo o Heap de qualquer dispositivo. Carregar imagens sem inSampleSize garante OOM em dispositivos fracos.
Use Glide ou Coil para redimensionamento automático. Essas bibliotecas carregam imagens com tamanho compatível com a View, não com a resolução original. Para uso direto de BitmapFactory.Options, aplique inSampleSize: calcule-o como uma potência de dois para que o tamanho final não exceda 2048 × 2048 pixels. Adicionalmente, use RGB_565 em vez de ARGB_8888 para imagens sem transparência — isso reduz o consumo de memória pela metade.
fun loadScaledBitmap(path: String, reqWidth: Int): Bitmap? {
val opts = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeFile(path, opts)
opts.inSampleSize = calculateSampleSize(opts.outWidth, reqWidth)
opts.inJustDecodeBounds = false
return BitmapFactory.decodeFile(path, opts)
}
Um único vazamento de alguns KB não causará OOM. Mas dezenas de vazamentos em cada tela se acumulam: cada transição de tela adiciona um vazamento, o GC não consegue liberar objetos e o Heap enche. Um padrão típico: o usuário abre e fecha a tela de perfil 20 vezes → o Heap cresce 200 MB → o aplicativo falha com OOM.
Instale o LeakCanary no projeto para detecção automática de vazamentos. Ele mostrará cada objeto vazado com um stack trace exato. Após corrigir todos os vazamentos, o consumo de Heap se torna estável: ao fechar uma tela, a memória retorna ao nível base.
Carregar arquivos inteiros em byte[] é um caminho direto para OOM. Um arquivo JSON de 50 MB durante a análise criará uma string do mesmo tamanho mais um modelo DOM. Arquivos de vídeo carregados na memória, buffers de áudio e grandes conjuntos de dados protobuf — todos podem exceder o limite de Heap em uma única operação.
Processe dados grandes usando fluxos: InputStream com buffer de 4–8 KB, analisador JSON em streaming (Jackson ou Gson com JsonReader), MediaCodec para vídeo. Nunca chame File.readBytes() em arquivos maiores que 10% do Heap disponível.
A criação intensiva de objetos em um loop sem GC intermediário pode levar ao OOM, especialmente em dispositivos com Heap pequeno. Exemplo: gerar 100.000 objetos em um for-loop que não cabem no Heap antes que o GC possa coletá-los. Isso é mais comum em jogos e editores gráficos.
Use Object Pool para objetos que são criados e destruídos em massa. Para dados numéricos, use primitivos (FloatArray em vez de List<Float>). RecyclerView com ViewHolder Pool resolve esse problema para componentes de UI.
Fragmentação é um estado onde há memória livre suficiente no total, mas nenhum bloco contíguo para um novo objeto. O ART compacta o Heap durante o GC, mas nem sempre com sucesso. Arrays grandes (Bitmap, byte[]) são os mais sensíveis à fragmentação.
O ART no Android 8+ usa GC Geracional, que reduz a fragmentação separando objetos jovens e velhos. No entanto, evite alocar fragmentos de diferentes tamanhos no mesmo pool — tente usar buffers pré-alocados de tamanho fixo.
O limite de Heap no Android não é uma constante — depende do fabricante, modelo do dispositivo e versão do SO. O Google define requisitos mínimos através do Documento de Definição de Compatibilidade (CDD), mas os fabricantes definem os valores reais.
| Categoria do Dispositivo | Heap Típico | largeHeap |
|---|---|---|
| Econômico (1–2 GB RAM) | 128–192 MB | 256–384 MB |
| Intermediário (3–4 GB RAM) | 256–384 MB | 512 MB |
| Flagship (6+ GB RAM) | 384–512 MB | 768 MB–1 GB |
| Tablets (4+ GB RAM) | 256–512 MB | 768 MB |
| Wear OS | 32–64 MB | N/A |
Você pode solicitar um limite aumentado através de android:largeHeap="true" no manifesto. Use com cuidado: aumentar o Heap não resolve o problema de vazamentos e pode piorar a experiência do usuário se o sistema for forçado a matar outros aplicativos para liberar memória para o seu. Para Wear OS, o limite de Heap é mínimo — apenas 32–64 MB, largeHeap não está disponível aqui, e a economia de memória é duplamente crítica.
Diagnosticar OOM requer analisar um Heap Dump e entender quais objetos consomem memória. O Android Studio fornece todas as ferramentas necessárias.
Passo 1: Capture o momento do OOM. No Android Memory Profiler, clique em Record memory allocations e execute o cenário que causa a falha. O Profiler mostrará um pico nas alocações antes do OOM. Se o OOM não for reproduzível, reduza o Heap via android:smallHeap na compilação de depuração ou use DDMS com invocação manual de GC.
Passo 2: Tire um Heap Dump no pico de carga (antes do OOM). Abra o Dump no Android Studio: a aba Classes está ordenada por Retained Size. Os maiores objetos são Bitmap, byte[], String. Para cada Bitmap, verifique o tamanho (largura × altura × 4 bytes) e o caminho de carga via Stack Trace.
Passo 3: Analise a quantidade de objetos duplicados. Se você vir 200 Fragment ou Activity idênticos — isso é um vazamento. Se 500 Bitmaps com o mesmo tamanho — é um problema de cache de imagens. O MAT (Memory Analyzer Tool) fornece uma análise mais profunda com uma Dominator Tree mostrando quais objetos retêm 80% do Heap.
// Comando de Heap Dump via adb
adb shell am dumpheap com.example.app /data/local/tmp/dump.hprof
adb pull /data/local/tmp/dump.hprof.
Uma estratégia abrangente de prevenção de OOM inclui cinco níveis de proteção: desde decisões arquitetônicas até monitoramento em produção.
ViewModel + Repository separa os dados da UI e impede a retenção da View ao girar a tela. A ViewModel sobrevive à Activity, seus dados não são perdidos e a View pode ser recriada sem duplicar dados na memória. Use StateFlow em vez de LiveData para gerenciamento explícito de estados.
Glide é uma biblioteca obrigatória para trabalhar com imagens. Ela automaticamente redimensiona, armazena em cache (disco + memória) e recicla Bitmap. Configure diskCacheStrategy e skipMemoryCache para listas grandes. Para imagens animadas, use Glide com GIF/WebP — eles ocupam menos memória que uma sequência de Bitmaps.
Firebase Performance Monitoring rastreia o consumo de memória em tempo real. Defina um alerta quando o uso do Heap exceder 80% do limite — é um sinal para investigar. O Crashlytics coleta OOM como uma exceção e mostra o último estado conhecido do Heap antes da falha. Para Android 11+, use ApplicationExitInfo para detectar terminações por OOM.
Certifique-se de testar o aplicativo em dispositivos com Heap mínimo (128–192 MB). Um emulador com tela pequena e Heap pequeno emula um dispositivo econômico. Se o aplicativo funcionar nesse dispositivo, não haverá problemas de OOM em flagships. Use o Firebase Test Lab com dispositivos reais de diferentes categorias de preço.
// Verificação do Heap disponível antes de operação pesada
fun canAllocate(requiredBytes: Long): Boolean {
val runtime = Runtime.getRuntime()
val free = runtime.freeMemory()
return free > requiredBytes * 2 // margem de 50%
}
Perguntas Frequentes
Tecnicamente sim, mas não é recomendado. Após o OOM, o aplicativo está em um estado instável: novas alocações podem falhar e alguns objetos podem estar parcialmente criados. A única ação razoável no catch é registrar e reiniciar a Activity.
O limite de Heap varia entre dispositivos. Uma operação que requer 300 MB falhará em um dispositivo com limite de 192 MB, mas terá sucesso em um flagship com 512 MB. Teste em dispositivos com especificações mínimas para detectar cenários de OOM.
largeHeap aumenta o limite, mas não acelera o aplicativo. As pausas do GC se tornam mais longas, pois coletar um Heap grande leva mais tempo. O sistema pode matar aplicativos em segundo plano para fornecer memória. Use largeHeap apenas para aplicativos que objetivamente precisam de muita memória (câmeras, editores).
OOM é uma exceção dentro de um aplicativo quando o Heap é insuficiente. A morte do sistema (Low Memory Killer) é uma decisão do kernel Linux de matar um processo para liberar memória para outros aplicativos. Em uma morte do sistema, o aplicativo não recebe uma exceção — o processo simplesmente termina.
A fórmula: largura × altura × bytesPerPixel. ARGB_8888 = 4 B/pixel, RGB_565 = 2 B/pixel. Um Bitmap FullHD (1920 × 1080) em ARGB_8888 = 8,3 MB. Um Bitmap 4K (3840 × 2160) = 33 MB. Sempre redimensione as imagens para o tamanho necessário para exibição na tela.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também