Ho posto questa domanda per sapere come aumentare la dimensione dello stack di chiamate di runtime nella JVM. Ho una risposta a questo e ho anche molte risposte e commenti utili relativi a come Java gestisce la situazione in cui è necessario un grande stack di runtime. Ho esteso la mia domanda con il riepilogo delle risposte.
Inizialmente volevo aumentare la dimensione dello stack JVM in modo che i programmi funzionino senza un file StackOverflowError.
public class TT {
public static long fact(int n) {
return n < 2 ? 1 : n * fact(n - 1);
}
public static void main(String[] args) {
System.out.println(fact(1 << 15));
}
}
L'impostazione di configurazione corrispondente è il java -Xss...flag della riga di comando con un valore sufficientemente grande. Per il programma TTsopra, funziona in questo modo con la JVM di OpenJDK:
$ javac TT.java
$ java -Xss4m TT
Una delle risposte ha anche sottolineato che i -X...flag dipendono dall'implementazione. Stavo usando
java version "1.6.0_18"
OpenJDK Runtime Environment (IcedTea6 1.8.1) (6b18-1.8.1-0ubuntu1~8.04.3)
OpenJDK 64-Bit Server VM (build 16.0-b13, mixed mode)
È anche possibile specificare uno stack grande solo per un thread (vedere in una delle risposte come). Questo è consigliato java -Xss...per evitare di sprecare memoria per i thread che non ne hanno bisogno.
Ero curioso di sapere quanto fosse grande uno stack esattamente necessario al programma sopra, quindi l'ho eseguito naumentato:
- -Xss4m può essere sufficiente per
fact(1 << 15) - -Xss5m può essere sufficiente per
fact(1 << 17) - -Xss7m può essere sufficiente per
fact(1 << 18) - -Xss9m può essere sufficiente per
fact(1 << 19) - -Xss18m può essere sufficiente per
fact(1 << 20) - -Xss35m può essere sufficiente per
fact(1 << 21) - -Xss68m può essere sufficiente per
fact(1 << 22) - -Xss129m può essere sufficiente per
fact(1 << 23) - -Xss258m può essere sufficiente per
fact(1 << 24) - -Xss515m può essere sufficiente per
fact(1 << 25)
Dai numeri sopra sembra che Java stia usando circa 16 byte per stack frame per la funzione sopra, il che è ragionevole.
L'enumerazione precedente contiene può essere sufficiente invece di è sufficiente , perché il requisito dello stack non è deterministico: eseguirlo più volte con lo stesso file sorgente e lo stesso a -Xss...volte riesce ea volte produce un file StackOverflowError. Ad esempio, per 1 << 20, -Xss18mera sufficiente in 7 run su 10, e -Xss19mnon era nemmeno sempre abbastanza, ma -Xss20mera sufficiente (in tutto 100 run su 100). La raccolta dei rifiuti, l'attivazione del JIT o qualcos'altro causa questo comportamento non deterministico?
La traccia dello stack stampata in un StackOverflowError(e possibilmente anche in altre eccezioni) mostra solo i 1024 elementi più recenti dello stack di runtime. Una risposta di seguito mostra come contare la profondità esatta raggiunta (che potrebbe essere molto più grande di 1024).
Molte persone che hanno risposto hanno sottolineato che è una pratica di codifica buona e sicura considerare implementazioni alternative e meno affamate di stack dello stesso algoritmo. In generale, è possibile convertire un insieme di funzioni ricorsive in funzioni iterative (usando un esStack oggetto, , che viene popolato sull'heap invece che sullo stack di runtime). Per questa particolare factfunzione, è abbastanza facile convertirla. La mia versione iterativa sarebbe simile a:
public class TTIterative {
public static long fact(int n) {
if (n < 2) return 1;
if (n > 65) return 0; // Enough powers of 2 in the product to make it (long)0.
long f = 2;
for (int i = 3; i <= n; ++i) {
f *= i;
}
return f;
}
public static void main(String[] args) {
System.out.println(fact(1 << 15));
}
}
Cordiali saluti, come mostra la soluzione iterativa sopra, la factfunzione non può calcolare il fattoriale esatto dei numeri sopra 65 (in realtà, anche sopra 20), perché il tipo integrato di Java longandrebbe in overflow. Il refactoring in factmodo che restituisca a BigIntegerinvece di longprodurrebbe risultati esatti anche per input di grandi dimensioni.