J'ai posé cette question pour savoir comment augmenter la taille de la pile d'appels d'exécution dans la JVM. J'ai une réponse à cela, et j'ai également de nombreuses réponses et commentaires utiles sur la façon dont Java gère la situation où une grande pile d'exécution est nécessaire. J'ai prolongé ma question avec le résumé des réponses.
À l'origine, je voulais augmenter la taille de la pile JVM afin que les programmes comme s'exécutent sans 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));
}
}
Le paramètre de configuration correspondant est l' java -Xss...
indicateur de ligne de commande avec une valeur suffisamment grande. Pour le programme TT
ci-dessus, cela fonctionne comme ceci avec la JVM d'OpenJDK:
$ javac TT.java
$ java -Xss4m TT
L'une des réponses a également souligné que les -X...
indicateurs dépendent de la mise en œuvre. J'utilisais
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)
Il est également possible de spécifier une grande pile pour un seul thread (voir dans l'une des réponses comment). Ceci est recommandé java -Xss...
pour éviter de gaspiller de la mémoire pour les threads qui n'en ont pas besoin.
J'étais curieux de savoir quelle était la taille exacte d'une pile dont le programme ci-dessus avait besoin, alors je l'ai exécuté en n
augmentant:
- -Xss4m peut suffire pour
fact(1 << 15)
- -Xss5m peut suffire pour
fact(1 << 17)
- -Xss7m peut suffire pour
fact(1 << 18)
- -Xss9m peut suffire pour
fact(1 << 19)
- -Xss18m peut suffire pour
fact(1 << 20)
- -Xss35m peut suffire pour
fact(1 << 21)
- -Xss68m peut suffire pour
fact(1 << 22)
- -Xss129m peut suffire pour
fact(1 << 23)
- -Xss258m peut suffire pour
fact(1 << 24)
- -Xss515m peut suffire pour
fact(1 << 25)
D'après les chiffres ci-dessus, il semble que Java utilise environ 16 octets par image de pile pour la fonction ci-dessus, ce qui est raisonnable.
L'énumération ci-dessus contient peut être suffisante au lieu d' être suffisante , car l'exigence de pile n'est pas déterministe: l'exécuter plusieurs fois avec le même fichier source et le même -Xss...
réussit parfois et donne parfois un fichier StackOverflowError
. Par exemple, pour 1 << 20, -Xss18m
c'était suffisant en 7 courses sur 10, et ce -Xss19m
n'était pas toujours suffisant non plus, mais -Xss20m
c'était suffisant (au total 100 courses sur 100). Le garbage collection, le démarrage du JIT ou autre chose provoquent-ils ce comportement non déterministe?
La trace de pile imprimée à un StackOverflowError
(et peut-être aussi à d'autres exceptions) montre uniquement les 1024 éléments les plus récents de la pile d'exécution. Une réponse ci-dessous montre comment compter la profondeur exacte atteinte (qui peut être beaucoup plus grande que 1024).
De nombreuses personnes qui ont répondu ont souligné que c'est une bonne et sûre pratique de codage d'envisager des implémentations alternatives, moins gourmandes en pile, du même algorithme. En général, il est possible de convertir un ensemble de fonctions récursives en fonctions itératives (en utilisant un Stack
objet par exemple , qui est rempli sur le tas au lieu de sur la pile d'exécution). Pour cette fact
fonction particulière , il est assez facile de la convertir. Ma version itérative ressemblerait à:
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));
}
}
Pour info, comme le montre la solution itérative ci-dessus, la fact
fonction ne peut pas calculer la factorielle exacte des nombres supérieurs à 65 (en fait, même supérieurs à 20), car le type intégré Java long
déborderait. La refactorisation fact
afin qu'elle renvoie un BigInteger
au lieu de long
produirait également des résultats exacts pour les entrées importantes.