Stack Overflow nello sviluppo mobile — cos'è, cause e metodi di prevenzione

Autore: IT Sectr Pubblicato: 2026-03-29 Tempo di lettura: 9 min

Stack Overflow è un errore di overflow dello stack di chiamate (java.lang.StackOverflowError) che si verifica quando viene superata la profondità massima dello stack di un thread. Secondo la Java Virtual Machine Specification, la profondità tipica dello stack nella JVM è di 1024 frame per i sistemi a 64 bit. La causa principale è la ricorsione infinita senza una condizione base.

Punti chiave

  • StackOverflowError — un errore JVM quando viene superato il limite di profondità dello stack di chiamate
  • La profondità dello stack è limitata a 512–2048 frame a seconda della configurazione
  • La ricorsione infinita è la causa più comune di StackOverflowError
  • La ricorsione in coda non viene ottimizzata nella JVM, a differenza dei linguaggi funzionali
  • La sostituzione iterativa della ricorsione è un modo affidabile per prevenire l'overflow

Cos'è Stack Overflow

StackOverflowError è un errore fatale della Macchina Virtuale Java (JVM) o di Android Runtime (ART) che si verifica quando lo stack di chiamate di un thread raggiunge la profondità massima consentita. A differenza di OutOfMemoryError (esaurimento dell'Heap), StackOverflowError è correlato a un'area di memoria diversa — lo stack, dove vengono memorizzati i frame di chiamata ai metodi e le variabili locali.

Ogni chiamata a un metodo crea un frame nello stack: indirizzo di ritorno, parametri e variabili locali. Al ritorno dal metodo, il frame viene distrutto. Se un metodo chiama se stesso (ricorsione) senza una condizione base, i frame si accumulano fino a riempire lo stack. La JVM non può allocare un nuovo frame e lancia StackOverflowError con un messaggio «null» (in Java) o con l'indicazione di una riga dello stack che si ripete all'infinito.

La dimensione dello stack di un thread è fissata alla creazione e non cambia durante l'esecuzione. In Android, la dimensione tipica dello stack del thread principale è di 32–48 KB, che dà una profondità di circa 512–1024 frame per metodi senza molte variabili locali. Per i thread in background, la dimensione predefinita è più piccola — 16–24 KB.

Come funziona lo stack di chiamate

Lo stack di chiamate (Call Stack) è una struttura dati LIFO (Last In, First Out) che gestisce l'ordine di esecuzione dei metodi. Ogni volta che il programma chiama un metodo, la JVM crea un frame nello stack e lo posiziona in cima. Quando il metodo viene completato, il frame viene rimosso.

Ogni frame contiene: stack degli operandi (per le istruzioni bytecode), array di variabili locali (incluso this), riferimento al pool di costanti e indirizzo di ritorno. Più variabili locali ha un metodo, maggiore è la dimensione del suo frame e meno metodi possono essere chiamati prima di riempire lo stack. Un metodo con 10 parametri e 20 variabili locali occupa circa 3 volte più spazio di un metodo senza parametri.

Su Android, ART utilizza la propria implementazione dello stack, diversa dalla JVM desktop. ART può aumentare dinamicamente lo stack entro certi limiti, ma esiste comunque un limite rigido per ogni thread. Il thread principale (thread UI) ha lo stack più grande, poiché gestisce l'intero ciclo di vita dell'Activity e l'elaborazione degli eventi.

kotlin
// Ricorsione che porta a StackOverflowError
fun recursiveCall(depth: Int): Int {
    return recursiveCall(depth + 1) // nessuna condizione base
}

// La chiamata causerà StackOverflowError a una profondità di ~1000
recursiveCall(0)

Cause principali dell'overflow dello stack

Cinque scenari tipici portano a StackOverflowError nelle applicazioni mobili. La maggior parte sono correlati alla ricorsione, ma ci sono anche cause meno ovvie.

Ricorsione infinita senza condizione base

La causa più comune. Lo sviluppatore scrive un metodo ricorsivo senza condizione di arresto o con una condizione che non diventa mai true. Ogni chiamata aggiunge un frame e lo stack si riempie in 500–2000 iterazioni a seconda della dimensione del frame. Un esempio tipico: calcolare il fattoriale n! senza verificare n == 0.

Verifica la condizione base all'inizio di ogni metodo ricorsivo. In Kotlin, usa require() o check() per la validazione dei parametri all'inizio. Per ricorsione profonda (più di 100 livelli), considera la sostituzione con un approccio iterativo.

Dipendenze cicliche nei costruttori

La classe A crea un'istanza di B, la classe B crea un'istanza di A — questa è una dipendenza ciclica nei costruttori. Quando si tenta di creare A, viene chiamato il costruttore di B, che chiama il costruttore di A, e così via fino a StackOverflowError. I framework DI (Dagger, Hilt) rilevano questi cicli in fase di compilazione, ma la creazione manuale di oggetti non li rileva.

Utilizza l'Iniezione di Dipendenze con grafi di dipendenze: Dagger o Koin verificano i cicli in fase di compilazione. Se il ciclo è inevitabile, sostituisci la dipendenza diretta con un'interfaccia con inizializzazione lazy o una factory Provider.

kotlin
// Dipendenza ciclica — StackOverflowError
class A(private val b: B)
class B(private val a: A)

// Soluzione lazy
class A(private val bProvider: Provider<B>)

Ricorsione profonda nell'attraversamento di grafi

L'attraversamento di un albero View (ViewGroup.getChildAt()), di un file system o di una struttura JSON tramite ricorsione può superare il limite dello stack a una profondità di più di 500–1000 elementi. Un ViewGroup Android con 20 livelli di annidamento è raro, ma l'analisi ricorsiva di JSON con 2000 oggetti annidati è uno scenario reale.

Sostituisci l'attraversamento ricorsivo con uno iterativo usando uno Stack<T> esplicito o ArrayDeque. Questo elimina completamente il rischio di overflow dello stack, poiché gli oggetti nell'heap non sono limitati dallo stack. BFS (Breadth-First Search) tramite Queue risolve anche il problema.

Gestione errata di onConfigurationChanged

Una causa specifica di Android: chiamate cicliche dei metodi del ciclo di vita quando la configurazione viene gestita in modo errato. Ad esempio, chiamare recreate() all'interno di onConfigurationChanged, che chiama di nuovo onConfigurationChanged, e così via fino a StackOverflowError. Allo stesso modo: setContentView() all'interno di onLayout(), che innesca un'altra misurazione e layout.

Non chiamare recreate() all'interno di metodi correlati ai cambiamenti di configurazione. Per aggiornare l'UI quando cambia il tema, usa setTheme() senza recreate. Per cambiamenti dinamici di orientamento — chiama requestOrientation() una volta, senza un flag nella configurazione.

Serializzazione con riferimenti ciclici

Gson, Moshi o Kotlin Serialization quando tentano di serializzare un oggetto con riferimenti ciclici (A referenzia B, B referenzia A) entrano in ricorsione infinita e falliscono con StackOverflowError. Questo è un problema comune quando si serializzano entità con relazioni bidirezionali (JPA, Room con ForeignKey).

Usa @Transient, @JsonIgnore o @kotlinx.serialization.Transient per un lato del ciclo. Per Gson — JsonSerializer con un limite di profondità esplicito. Per Room — non serializzare mai Entity direttamente, usa mapper DTO.

Come diagnosticare e correggere StackOverflowError

Diagnosticare StackOverflowError è più facile di altri errori di memoria: la traccia dello stack nella maggior parte dei casi mostra una sequenza ripetuta di chiamate. Questo indica immediatamente la ricorsione.

Lettura della traccia dello stack

La traccia dello stack di StackOverflowError è unica: dopo le prime 200–500 righe, lo stesso schema di chiamate inizia a ripetersi. La JVM tronca le righe ripetute alla fine e mostra «... 1234 more». Il numero di righe non ripetute prima di «...» indica la profondità di ricorsione che ha causato l'errore.

Leggi le prime righe della traccia dello stack — mostrano con quale metodo è iniziata la ripetizione. Trova il metodo che chiama se stesso o crea una catena di chiamate che torna a esso. Correggi la condizione base o sostituisci la ricorsione con un ciclo.

Aumentare la dimensione dello stack (soluzione temporanea)

Temporaneamente, il problema può essere risolto aumentando la dimensione dello stack tramite il flag JVM -Xss. Per Android, la dimensione dello stack viene impostata tramite AndroidManifest: android:largeHeap non influisce sullo stack. Per aumentare lo stack del thread nel codice: Thread(ThreadGroup, Runnable, name, stackSize). stackSize è la dimensione desiderata in byte.

kotlin
// Creazione di un thread con stack aumentato
val thread = Thread(null, runnable, "big-stack-thread", 64 * 1024)
thread.start()

Importante: aumentare lo stack non risolve il problema, lo ritarda solo. Con una ricorsione di 10.000 livelli, uno stack di 64 KB verrà sostituito da uno stack di 128 KB, dando 20.000 livelli — ma l'errore si verificherà comunque, solo più tardi. L'unica soluzione corretta è la sostituzione iterativa della ricorsione.

Sostituire la ricorsione con l'iterazione

Gli algoritmi iterativi non usano lo stack di chiamate per memorizzare gli stati intermedi — li memorizzano nell'heap (Stack<T> o ArrayDeque). Attraversamento di albero binario, calcolo del fattoriale, Fibonacci — qualsiasi ricorsione può essere convertita in iterazione usando uno stack esplicito.

kotlin
// Attraversamento iterativo dell'albero — nessun rischio di 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) }
    }
}

Come prevenire Stack Overflow

Prevenire StackOverflowError è un insieme di regole e strumenti che identificano potenziali cicli ricorsivi prima che raggiungano la produzione.

Limite di profondità di ricorsione nella build di debug

Aggiungi un contatore di profondità protettivo nei metodi ricorsivi nelle build di debug. Se la profondità supera una soglia (ad esempio, 1000), lancia un'eccezione con un messaggio chiaro. Questo trasforma uno StackOverflowError con una traccia illeggibile in un'eccezione di business comprensibile.

kotlin
fun safeRecursive(n: Int, depth: Int = 0): Int {
    if (depth > 1000) {
        throw IllegalStateException("La ricorsione ha superato i 1000 livelli")
    }
    return if (n <= 1) n
           else safeRecursive(n - 1, depth + 1)
}

Analisi statica del codice

Detekt (Kotlin) e Infer (Facebook) trovano potenziali ricorsioni infinite a livello di analisi statica. Detekt ha una regola PotentiallyInfiniteRecursion che avverte sulle auto-chiamate senza modifica dei parametri. Attivala nel set di regole CI e imposta la gravità su error.

Code Review con focus sulla ricorsione

Nella revisione del codice, presta attenzione a: qualsiasi metodo con auto-chiamata, chiamate ricorsive all'interno di lambda (funzioni inline di Kotlin), chiamate cicliche tra classi diverse, ricorsione nei delegati di proprietà. Per ogni metodo ricorsivo, verifica: esiste una condizione base, il parametro cambia a ogni passo, la modifica del parametro garantisce il raggiungimento della condizione base.

Trasformazione tail-recursive (limitata)

Kotlin supporta il modificatore tailrec: se un metodo ricorsivo è marcato tailrec e la chiamata è in coda (ultima operazione), il compilatore lo converte in iterazione. Tuttavia, tailrec funziona solo per auto-chiamate (il metodo chiama se stesso direttamente), non funziona per la ricorsione mutua e non è supportato nelle versioni di Kotlin compatibili con Android precedenti alla 1.5.

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

Domande frequenti

Si può catturare StackOverflowError con try-catch?

, ma solo a livello Java. Error, come Exception, è un Throwable. Tuttavia, dopo StackOverflowError lo stack è danneggiato — i frame che non sono entrati non possono completarsi correttamente. Tentare di creare un nuovo oggetto nel blocco catch può causare un altro StackOverflowError.

Qual è la dimensione predefinita dello stack in Android?

Per il thread principale — 32–48 KB, per i thread in background — 16–24 KB. La dimensione esatta dipende dalla versione di Android e dal produttore del dispositivo. ART utilizza l'espansione dinamica dello stack, ma non più di 2× il valore iniziale.

La ricorsione in coda può prevenire StackOverflowError?

In Kotlin — sì, se il metodo è marcato tailrec. Il compilatore converte la ricorsione in coda in iterazione, eliminando completamente la crescita dello stack. In Java, la ricorsione in coda non viene ottimizzata dalla JVM (a differenza di linguaggi funzionali come Scala).

Perché StackOverflowError si verifica sull'emulatore ma non sul dispositivo?

La dimensione dello stack sull'emulatore e su un dispositivo reale può differire. L'emulatore utilizza JVM desktop con uno stack tipico di 512–1024 KB, mentre Android ART utilizza 32–48 KB. L'errore si manifesterà su ART prima che sulla JVM desktop.

In che modo StackOverflowError differisce da OutOfMemoryError?

Area di memoria: StackOverflowError è un errore dello stack (frame di chiamate), OutOfMemoryError è un errore dell'heap (oggetti). StackOverflowError è quasi sempre causato da ricorsione, mentre OutOfMemoryError è causato da perdite di memoria o oggetti grandi.

Riepilogo

  • StackOverflowError — overflow dello stack di chiamate quando viene superato il limite di profondità di ricorsione
  • La profondità dello stack in Android è di 512–1024 frame sul thread principale
  • La ricorsione infinita è la causa principale; verifica la condizione base in ogni metodo ricorsivo
  • Le dipendenze cicliche nei costruttori — una causa meno ovvia ma comune di overflow
  • La sostituzione iterativa della ricorsione tramite Stack<T> esplicito elimina completamente il rischio
  • tailrec in Kotlin converte la ricorsione in coda in iterazione a livello di compilatore
  • L'analisi statica (Detekt, Infer) trova ricorsioni potenzialmente infinite prima dell'esecuzione

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche