Stack Overflow no desenvolvimento móvel — o que é, causas e métodos de prevenção

Autor: IT Sectr Publicado: 2026-03-29 Tempo de leitura: 9 min

Stack Overflow é um erro de estouro da pilha de chamadas (java.lang.StackOverflowError) que ocorre quando a profundidade máxima da pilha de uma thread é excedida. De acordo com a Java Virtual Machine Specification, a profundidade típica da pilha na JVM é de 1024 quadros para sistemas de 64 bits. A causa principal é a recursão infinita sem uma condição base.

Pontos principais

  • StackOverflowError — um erro da JVM ao exceder o limite de profundidade da pilha de chamadas
  • A profundidade da pilha é limitada a 512–2048 quadros conforme a configuração
  • A recursão infinita é a causa mais comum de StackOverflowError
  • A recursão tail não é otimizada na JVM, ao contrário das linguagens funcionais
  • A substituição iterativa da recursão é uma forma confiável de prevenir o estouro

O que é Stack Overflow

StackOverflowError é um erro fatal da Máquina Virtual Java (JVM) ou Android Runtime (ART) que ocorre quando a pilha de chamadas de uma thread atinge a profundidade máxima permitida. Ao contrário do OutOfMemoryError (falta de Heap), o StackOverflowError está relacionado a uma área de memória diferente — a pilha, onde os quadros de chamadas de métodos e variáveis locais são armazenados.

Cada chamada de método cria um quadro na pilha: endereço de retorno, parâmetros e variáveis locais. Ao retornar do método, o quadro é destruído. Se um método chama a si mesmo (recursão) sem uma condição base, os quadros se acumulam até encher a pilha. A JVM não consegue alocar um novo quadro e lança StackOverflowError com uma mensagem «null» (em Java) ou com a indicação de uma linha de pilha que se repete infinitamente.

O tamanho da pilha de uma thread é fixo na criação e não muda durante a execução. No Android, o tamanho típico da pilha da thread principal é de 32–48 KB, o que dá uma profundidade de aproximadamente 512–1024 quadros para métodos sem muitas variáveis locais. Para threads em segundo plano, o tamanho padrão é menor — 16–24 KB.

Como funciona a pilha de chamadas

A pilha de chamadas (Call Stack) é uma estrutura de dados LIFO (Last In, First Out) que gerencia a ordem de execução dos métodos. Cada vez que o programa chama um método, a JVM cria um quadro na pilha e o coloca no topo. Quando o método é concluído, o quadro é removido.

Cada quadro contém: pilha de operandos (para instruções bytecode), array de variáveis locais (incluindo this), referência ao pool de constantes e endereço de retorno. Quanto mais variáveis locais um método tiver, maior será o tamanho do seu quadro e menos métodos poderão ser chamados antes de encher a pilha. Um método com 10 parâmetros e 20 variáveis locais ocupa aproximadamente 3 vezes mais espaço que um método sem parâmetros.

No Android, a ART usa sua própria implementação de pilha, diferente da JVM de desktop. A ART pode aumentar a pilha dinamicamente dentro de certos limites, mas ainda existe um limite rígido para cada thread. A thread principal (thread de UI) tem a maior pilha, pois lida com todo o ciclo de vida da Activity e o processamento de eventos.

kotlin
// Recursão que leva a StackOverflowError
fun recursiveCall(depth: Int): Int {
    return recursiveCall(depth + 1) // sem condição base
}

// A chamada causará StackOverflowError a uma profundidade de ~1000
recursiveCall(0)

Principais causas do estouro de pilha

Cinco cenários típicos levam ao StackOverflowError em aplicações móveis. A maioria está relacionada à recursão, mas também existem causas menos óbvias.

Recursão infinita sem condição base

A causa mais comum. O desenvolvedor escreve um método recursivo sem condição de parada ou com uma condição que nunca se torna true. Cada chamada adiciona um quadro, e a pilha enche em 500–2000 iterações dependendo do tamanho do quadro. Um exemplo típico: calcular o fatorial n! sem verificar n == 0.

Verifique a condição base no início de cada método recursivo. Em Kotlin, use require() ou check() para validar parâmetros no início. Para recursão profunda (mais de 100 níveis), considere substituir por uma abordagem iterativa.

Dependências cíclicas em construtores

A classe A cria uma instância de B, a classe B cria uma instância de A — isso é uma dependência cíclica em construtores. Ao tentar criar A, o construtor de B é chamado, que chama o construtor de A, e assim até StackOverflowError. Frameworks de DI (Dagger, Hilt) detectam esses ciclos em tempo de compilação, mas a criação manual de objetos não os capta.

Use Injeção de Dependência com grafos de dependência: Dagger ou Koin verificam ciclos em tempo de compilação. Se o ciclo for inevitável, substitua a dependência direta por uma interface com inicialização lazy ou uma fábrica Provider.

kotlin
// Dependência cíclica — StackOverflowError
class A(private val b: B)
class B(private val a: A)

// Solução lazy
class A(private val bProvider: Provider<B>)

Recursão profunda em travessia de grafos

A travessia de uma árvore View (ViewGroup.getChildAt()), sistema de arquivos ou estrutura JSON via recursão pode exceder o limite da pilha a uma profundidade de mais de 500–1000 elementos. Um ViewGroup do Android com 20 níveis de aninhamento é raro, mas a análise recursiva de JSON com 2000 objetos aninhados é um cenário real.

Substitua a travessia recursiva por uma iterativa usando um Stack<T> explícito ou ArrayDeque. Isso elimina completamente o risco de estouro de pilha, já que os objetos no heap não são limitados pela pilha. BFS (Breadth-First Search) via Queue também resolve o problema.

Manipulação incorreta de onConfigurationChanged

Uma causa específica do Android: chamadas cíclicas de métodos do ciclo de vida quando a configuração é manipulada incorretamente. Por exemplo, chamar recreate() dentro de onConfigurationChanged, que chama novamente onConfigurationChanged, e assim até StackOverflowError. Similarmente: setContentView() dentro de onLayout(), que desencadeia outra medição e layout.

Não chame recreate() dentro de métodos relacionados a mudanças de configuração. Para atualizar a UI ao mudar o tema, use setTheme() sem recreate. Para mudanças dinâmicas de orientação — chame requestOrientation() uma vez, sem uma flag na configuração.

Serialização com referências cíclicas

Gson, Moshi ou Kotlin Serialization ao tentar serializar um objeto com referências cíclicas (A referencia B, B referencia A) entram em recursão infinita e falham com StackOverflowError. Este é um problema comum ao serializar entidades com relacionamentos bidirecionais (JPA, Room com ForeignKey).

Use @Transient, @JsonIgnore ou @kotlinx.serialization.Transient para um lado do ciclo. Para Gson — JsonSerializer com um limite de profundidade explícito. Para Room — nunca serialize Entity diretamente, use mapeadores DTO.

Como diagnosticar e corrigir StackOverflowError

Diagnosticar StackOverflowError é mais fácil que outros erros de memória: o stack trace na maioria dos casos mostra uma sequência repetida de chamadas. Isso aponta imediatamente para recursão.

Leitura do stack trace

O stack trace de StackOverflowError é único: após as primeiras 200–500 linhas, o mesmo padrão de chamadas começa a se repetir. A JVM trunca as linhas repetidas no final e mostra «... 1234 more». O número de linhas não repetidas antes de «...» indica a profundidade de recursão que causou o erro.

Leia as primeiras linhas do stack trace — elas mostram com qual método a repetição começou. Encontre o método que chama a si mesmo ou cria uma cadeia de chamadas que retorna a ele. Corrija a condição base ou substitua a recursão por um loop.

Aumentar o tamanho da pilha (solução temporária)

Temporariamente, o problema pode ser resolvido aumentando o tamanho da pilha através da flag JVM -Xss. Para Android, o tamanho da pilha é definido via AndroidManifest: android:largeHeap não afeta a pilha. Para aumentar a pilha da thread no código: Thread(ThreadGroup, Runnable, name, stackSize). stackSize é o tamanho desejado em bytes.

kotlin
// Criação de uma thread com pilha aumentada
val thread = Thread(null, runnable, "big-stack-thread", 64 * 1024)
thread.start()

Importante: aumentar a pilha não resolve o problema, apenas o adia. Com recursão de 10 000 níveis, uma pilha de 64 KB será substituída por uma de 128 KB, dando 20 000 níveis — mas o erro ainda ocorrerá, apenas mais tarde. A única solução correta é a substituição iterativa da recursão.

Substituir recursão por iteração

Algoritmos iterativos não usam a pilha de chamadas para armazenar estados intermediários — eles os armazenam no heap (Stack<T> ou ArrayDeque). Travessia de árvore binária, cálculo de fatorial, Fibonacci — qualquer recursão pode ser convertida em iteração usando uma pilha explícita.

kotlin
// Travessia iterativa de árvore — sem risco de StackOverflow
fun traverseIterative(root: Node?) {
    val stack = ArrayDeque<Node>()
    stack.push(root)
    while (stack.isNotEmpty()) {
        val node = stack.pop() ?: continue
        process(node)
        node.right?.let { stack.push(it) }
        node.left?.let { stack.push(it) }
    }
}

Como prevenir Stack Overflow

Prevenir StackOverflowError é um conjunto de regras e ferramentas que identificam possíveis ciclos recursivos antes que cheguem à produção.

Limite de profundidade de recursão em build de depuração

Adicione um contador de profundidade protetor em métodos recursivos em builds de depuração. Se a profundidade exceder um limite (por exemplo, 1000), lance uma exceção com uma mensagem clara. Isso transforma um StackOverflowError com trace ilegível em uma exceção de negócio compreensível.

kotlin
fun safeRecursive(n: Int, depth: Int = 0): Int {
    if (depth > 1000) {
        throw IllegalStateException("A recursão excedeu 1000 níveis")
    }
    return if (n <= 1) n
           else safeRecursive(n - 1, depth + 1)
}

Análise estática de código

Detekt (Kotlin) e Infer (Facebook) encontram possíveis recursões infinitas no nível de análise estática. Detekt tem uma regra PotentiallyInfiniteRecursion que alerta sobre auto-chamadas sem alteração de parâmetros. Ative-a no conjunto de regras de CI e configure a gravidade como error.

Code Review com foco em recursão

Na revisão de código, preste atenção a: qualquer método com auto-chamada, chamadas recursivas dentro de lambdas (funções inline do Kotlin), chamadas cíclicas entre diferentes classes, recursão em property delegates. Para cada método recursivo, verifique: se há uma condição base, se o parâmetro muda a cada passo, se a mudança de parâmetro garante alcançar a condição base.

Transformação tail-recursive (limitada)

Kotlin suporta o modificador tailrec: se um método recursivo é marcado com tailrec e a chamada é tail (última operação), o compilador o converte em iteração. No entanto, tailrec funciona apenas para auto-chamadas (o método chama a si mesmo diretamente), não funciona para recursão mútua e não é suportado em versões do Kotlin compatíveis com Android anteriores à 1.5.

kotlin
tailrec fun factorial(n: Int, acc: Int = 1): Int {
    return if (n <= 1) acc
           else factorial(n - 1, acc * n) // chamada tail
}

Perguntas frequentes

É possível capturar StackOverflowError com try-catch?

Sim, mas apenas no nível Java. Error, assim como Exception, é um Throwable. No entanto, após StackOverflowError a pilha está danificada — os quadros que não couberam não podem ser concluídos corretamente. Tentar criar um novo objeto no bloco catch pode causar outro StackOverflowError.

Qual é o tamanho de pilha padrão no Android?

Para a thread principal — 32–48 KB, para threads em segundo plano — 16–24 KB. O tamanho exato depende da versão do Android e do fabricante do dispositivo. ART usa expansão dinâmica de pilha, mas não mais que 2× o valor inicial.

A recursão tail pode prevenir StackOverflowError?

Em Kotlin — sim, se o método for marcado com tailrec. O compilador converte a recursão tail em iteração, eliminando completamente o crescimento da pilha. Em Java, a recursão tail não é otimizada pela JVM (ao contrário de linguagens funcionais como Scala).

Por que StackOverflowError ocorre no emulador mas não no dispositivo?

O tamanho da pilha no emulador e em um dispositivo real pode diferir. O emulador usa JVM de desktop com uma pilha típica de 512–1024 KB, enquanto o Android ART usa 32–48 KB. O erro se manifestará no ART antes que na JVM de desktop.

Como StackOverflowError difere de OutOfMemoryError?

Área de memória: StackOverflowError é um erro de pilha (quadros de chamadas), OutOfMemoryError é um erro de heap (objetos). StackOverflowError é quase sempre causado por recursão, enquanto OutOfMemoryError é causado por vazamentos de memória ou objetos grandes.

Resumo

  • StackOverflowError — estouro da pilha de chamadas ao exceder o limite de profundidade de recursão
  • A profundidade da pilha no Android é de 512–1024 quadros na thread principal
  • A recursão infinita é a causa principal; verifique a condição base em cada método recursivo
  • Dependências cíclicas em construtores — uma causa menos óbvia mas comum de estouro
  • A substituição iterativa da recursão via Stack<T> explícito elimina completamente o risco
  • tailrec em Kotlin converte recursão tail em iteração no nível do compilador
  • Análise estática (Detekt, Infer) encontra recursões potencialmente infinitas antes da execução

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.

Discutir o projeto

Leia também