J'ai posé cette question pour savoir comment augmenter la taille de la pile d'appels en cours d'exécution dans la JVM. J'ai obtenu une réponse à cette question, ainsi que de nombreuses réponses et commentaires utiles sur la manière dont Java gère la situation où une grande pile d'exécution est nécessaire. J'ai complété ma question avec le résumé des réponses.
À l'origine, je voulais augmenter la taille de la pile de la JVM pour que des programmes tels que les exécutions 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 le java -Xss...
avec une valeur suffisamment grande. Pour le programme TT
ci-dessus, cela fonctionne comme suit avec la JVM d'OpenJDK :
$ javac TT.java
$ java -Xss4m TT
L'une des réponses a également souligné que la -X...
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). Il est recommandé d'utiliser cette méthode plutôt que java -Xss...
pour éviter de gaspiller de la mémoire pour des threads qui n'en ont pas besoin.
J'étais curieux de connaître la taille de la pile dont le programme ci-dessus a besoin, et je l'ai donc exécuté. n
augmentée :
- -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 être suffisant 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 trame de pile pour la fonction ci-dessus, ce qui est raisonnable.
L'énumération ci-dessus contient peut suffire au lieu de est suffisant L'exigence de la pile n'est pas déterministe : l'exécuter plusieurs fois avec le même fichier source et les mêmes -Xss...
réussit parfois et produit parfois un StackOverflowError
. Par exemple, pour 1 << 20, -Xss18m
a suffi dans 7 séries sur 10, et -Xss19m
n'a pas toujours suffi non plus, mais -Xss20m
a suffi (pour 100 passages sur 100). Est-ce que le garbage collection, le JIT qui intervient, ou quelque chose d'autre provoque ce comportement non déterministe ?
La trace de la pile imprimée à un StackOverflowError
(et éventuellement à d'autres exceptions) n'affiche que 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 pratique de codage que d'envisager d'autres implémentations du même algorithme, moins gourmandes en ressources. En général, il est possible de convertir un ensemble de fonctions récursives en fonctions itératives (à l'aide d'une fonction de type Stack
qui est alimenté sur le tas et non sur la pile d'exécution). Pour cet objet fact
il est assez facile de la convertir. Ma version itérative ressemblerait à ceci
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 information, comme le montre la solution itérative ci-dessus, l'élément fact
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. Refonte fact
de sorte qu'il renvoie un BigInteger
au lieu de long
donnerait également des résultats exacts pour des entrées importantes.
0 votes
Fact() est appelé 32K fois de manière récursive. Cela devrait représenter moins de 1 Mo de pile :-/
0 votes
@Aaron : + Frais généraux de la fonction, ce qui est BEAUCOUP
4 votes
En dehors de vos problèmes de pile, notez que vous faites exploser votre long et vos ints. 1<<4 est la valeur maximale que je peux utiliser avant de devenir négatif et ensuite 0. Essayez d'utiliser BigInteger
0 votes
Je ne suis pas sûr que la surcharge de la fonction soit si importante que cela - je pense que vous devriez toujours être en mesure de faire 2^15 appels dans l'ordre de quelques mégaoctets de l'espace de la pile.
0 votes
@gameaddict : Je ne me soucie pas de BigInteger, voir ce paragraphe dans la question originale : Je sais que mon programme imprime le résultat modulo 2^64 (complément à 2), c'est très bien.
9 votes
Note : Vous fixez la taille de la pile de chaque thread et produisez un résultat sans signification, tout cela pour éviter de remanier une ligne de code. Je suis heureux que vous ayez mis de l'ordre dans vos priorités. :P
0 votes
Note importante : les programmes peuvent fonctionner sans
StackOverFlow
mais pas les programmeurs.