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 TT
sopra, 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 n
aumentato:
- -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, -Xss18m
era sufficiente in 7 run su 10, e -Xss19m
non era nemmeno sempre abbastanza, ma -Xss20m
era 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 fact
funzione, è 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 fact
funzione non può calcolare il fattoriale esatto dei numeri sopra 65 (in realtà, anche sopra 20), perché il tipo integrato di Java long
andrebbe in overflow. Il refactoring in fact
modo che restituisca a BigInteger
invece di long
produrrebbe risultati esatti anche per input di grandi dimensioni.