Comment augmenter la taille de la pile Java?

j'ai posé cette question pour savoir comment augmenter la taille de la pile d'appels runtime dans la JVM. J'ai une réponse à cela, et j'ai aussi beaucoup de réponses et de commentaires utiles concernant la façon dont Java gère la situation où une grande pile d'exécution est nécessaire. J'ai étendu ma question au résumé des réponses.

à l'origine, je voulais augmenter la taille de la pile JVM pour que les programmes comme runs 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 drapeau de ligne de commande java -Xss... avec une valeur suffisante. Pour le programme TT ci-dessus, il fonctionne comme ceci avec OpenJDK JVM:

$ javac TT.java
$ java -Xss4m TT

L'une des réponses a également souligné que les indicateurs -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). Ce est recommandé sur java -Xss... pour éviter de gaspiller la mémoire pour les fils qui n'en ont pas besoin.

j'étais curieux de savoir quelle est la taille d'une pile dont le programme a exactement besoin, donc je l'ai lancé n augmenté:

  • - Xss4m peut suffire pour fact(1 << 15)
  • - Xss5m peut suffire pour fact(1 << 17)
  • - Xss7m peut suffire pour fact(1 << 18)
  • - Xss9m peut être suffisant 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 au-dessus il semble que Java utilise environ 16 octets par cadre de pile pour la fonction ci-dessus, ce qui est raisonnable.

l'énumération ci-dessus contient peut suffire au lieu de est suffisant , parce que 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 produit parfois un StackOverflowError . Par exemple: pour 1 < < 20, -Xss18m était suffisant en 7 passages sur 10, et -Xss19m n'était pas toujours suffisant non plus, mais -Xss20m était suffisant (en tout 100 sorties sur 100). Est-ce que le ramassage des ordures, les coups de pied dans le JIT, ou quelque chose d'autre cause ce comportement non déterministe?

la trace de pile imprimée à un StackOverflowError (et peut-être aussi à d'autres exceptions) ne montre que les 1024 éléments les plus récents de la pile runtime. Une réponse ci-dessous montre comment compter la profondeur exacte atteint (ce qui pourrait être beaucoup plus grande que 1024).

de nombreuses personnes qui ont répondu ont fait remarquer qu'il s'agit d'une bonne pratique de codage Sécuritaire d'envisager d'autres implémentations du même algorithme, moins axées sur la pile. En général, il est possible de convertir en un ensemble de fonctions récursives en fonctions itératives (en utilisant par exemple un objet Stack , qui est peuplé sur le tas plutôt que sur la pile runtime). Pour cette fonction particulière fact , 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));
  }
}

FYI, comme la solution itérative ci-dessus le montre, la fonction fact ne peut pas calculer le factoriel exact des nombres au-dessus de 65 (en fait, même au-dessus de 20), parce que le Java intégré type long déborderait. Refactoring fact donc, il serait de retour BigInteger au lieu de long donnerait des résultats exacts pour les grandes entrées.

94
demandé sur pts 2010-09-13 16:43:18

9 réponses

Hmm... il fonctionne pour moi et avec beaucoup moins de 999MB de pile:

> java -Xss4m Test
0

(Windows JDK 7, build 17.0-B05 client VM, et Linux JDK 6 - même information de version que vous avez posté)

64
répondu Jon Skeet 2010-09-17 13:31:29

je suppose que vous avez calculé la "profondeur de 1024" par les lignes récurrentes dans la trace de la pile?

de toute évidence, la longueur du réseau de traces de pile dans Throwable semble être limitée à 1024. Essayez le programme suivant:

public class Test {

    public static void main(String[] args) {

        try {
            System.out.println(fact(1 << 15));
        }
        catch (StackOverflowError e) {
            System.err.println("true recursion level was " + level);
            System.err.println("reported recursion level was " +
                               e.getStackTrace().length);
        }
    }

    private static int level = 0;
    public static long fact(int n) {
        level++;
        return n < 2 ? n : n * fact(n - 1);
    }
}
11
répondu Jay 2018-02-22 15:43:09

si vous voulez jouer avec la taille de la pile de threads, vous voudrez regarder l'option-Xss sur le Hotspot JVM. Il peut être quelque chose de différent sur les VM Non Hotspot puisque les paramètres-X de la JVM sont spécifiques à la distribution, IIRC.

sur Hotspot, ça ressemble à java -Xss16M si vous voulez faire la taille 16 megs.

Type java -X -help si vous voulez voir tous les paramètres JVM spécifiques à la distribution, vous pouvez passer. Je ne suis pas sûr si cela fonctionne de la même façon pour les autres JVM, mais il imprime tous les paramètres spécifiques des points chauds.

pour ce que ça vaut - je recommande de limiter votre utilisation de méthodes récursives en Java. Il n'est pas très efficace de les optimiser, car la JVM ne supporte pas la recursion de queue (voir ). la JVM empêche-t-elle les optimisations d'appel de queue? ). Essayez de refactoriser votre code factoriel ci-dessus pour utiliser une boucle while au lieu d'appels de méthode récursifs.

9
répondu whaley 2017-05-23 12:17:50

la seule façon de contrôler la taille de la pile dans le processus est de lancer un nouveau Thread . Mais vous pouvez également contrôler en créant un processus sub Java auto-appelant avec le paramètre -Xss .

public class TT {
    private static int level = 0;

    public static long fact(int n) {
        level++;
        return n < 2 ? n : n * fact(n - 1);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(null, null, "TT", 1000000) {
            @Override
            public void run() {
                try {
                    level = 0;
                    System.out.println(fact(1 << 15));
                } catch (StackOverflowError e) {
                    System.err.println("true recursion level was " + level);
                    System.err.println("reported recursion level was "
                            + e.getStackTrace().length);
                }
            }

        };
        t.start();
        t.join();
        try {
            level = 0;
            System.out.println(fact(1 << 15));
        } catch (StackOverflowError e) {
            System.err.println("true recursion level was " + level);
            System.err.println("reported recursion level was "
                    + e.getStackTrace().length);
        }
    }

}
7
répondu Dennis C 2018-02-22 15:45:37

ajouter cette option ""

--driver-java-options -Xss512m

à votre commande spark-submit corrigera ce problème.

3
répondu Guibin Zhang 2018-07-26 11:16:04

il est difficile de donner une solution raisonnable puisque vous êtes désireux d'éviter toutes les approches saines. Refactoring une ligne de code est la senible solution.

Note: utiliser-Xss définit la taille de la pile de chaque thread et est une très mauvaise idée.

une autre approche consiste à modifier le code octet comme suit:

public static long fact(int n) { 
    return n < 2 ? n : n > 127 ? 0 : n * fact(n - 1); 
}

donné chaque réponse pour n > 127 est 0. Cela évite de changer le code source.

1
répondu Peter Lawrey 2010-09-17 21:21:48

Bizarre! Vous dites que vous voulez générer une recursion de 1<<15 profondeur ???!!!!

je vous conseille de ne pas essayer. La taille de la pile sera 2^15 * sizeof(stack-frame) . Je ne sais pas ce qu'est la taille de la pile, mais 2^15 est 32.768. A peu près... Eh bien, si elle s'arrête à 1024 (2^10) vous aurez à faire 2^5 fois plus grand, il est, 32 fois plus grand qu'avec votre réglage réel.

0
répondu helios 2010-09-15 14:24:08

D'autres affiches ont montré comment augmenter la mémoire et qu'on pouvait mémoriser les appels. Je suggère que pour de nombreuses applications, vous pouvez utiliser la formule de Stirling pour se rapprocher du grand n! très rapidement avec presque aucune empreinte mémoire.

jetez un coup d'oeil à ce poste, qui a une certaine analyse de la fonction et du code:

http://threebrothers.org/brendan/blog/stirlings-approximation-formula-clojure /

0
répondu abscondment 2010-11-25 08:53:48

I did Anagram excersize , qui est comme Count Change problème, mais avec 50 000 coupures (pièces de monnaie). Je suis pas sûr que cela peut être fait de manière itérative , je ne m'inquiète pas. Je sais juste que l'option-xss n'a eu aucun effet -- j'ai toujours échoué après 1024 stack frames (peut-être que scala fait du mauvais travail en livrant à java ou à la limitation printStackTrace. Je ne sais pas). C'est une mauvaise option, comme expliqué de toute façon. Vous ne voulez pas tous les fils dans l'application pour être monstrueux. Cependant, j'ai fait quelques expériences avec de nouveaux Thread (stack size). Cela fonctionne en effet,

  def measureStackDepth(ss: Long): Long = {
    var depth: Long = 0
      val thread: Thread = new Thread(null, new Runnable() {
        override def run() {
          try {
          def sum(n: Long): Long = {depth += 1; if (n== 0) 0 else sum(n-1) + 1}
          println("fact = " + sum(ss * 10))
          } catch {
            case e: StackOverflowError => // eat the exception, that is expected
          }
        }
      }, "deep stack for money exchange", ss)
      thread.start()
      thread.join()
    depth
  }                                               //> measureStackDepth: (ss: Long)Long


  for (ss <- (0 to 10)) println("ss = 10^" +  ss + " allows stack of size " -> measureStackDepth((scala.math.pow (10, ss)).toLong) )
                                                  //> fact = 10
                                                  //| (ss = 10^0 allows stack of size ,11)
                                                  //| fact = 100
                                                  //| (ss = 10^1 allows stack of size ,101)
                                                  //| fact = 1000
                                                  //| (ss = 10^2 allows stack of size ,1001)
                                                  //| fact = 10000
                                                  //| (ss = 10^3 allows stack of size ,10001)
                                                  //| (ss = 10^4 allows stack of size ,1336)
                                                  //| (ss = 10^5 allows stack of size ,5456)
                                                  //| (ss = 10^6 allows stack of size ,62736)
                                                  //| (ss = 10^7 allows stack of size ,623876)
                                                  //| (ss = 10^8 allows stack of size ,6247732)
                                                  //| (ss = 10^9 allows stack of size ,62498160)

vous voyez que la pile peut croître de façon exponentielle plus profondément avec une quantité exponentielle de la pile affectée au fil.

0
répondu Val 2014-06-28 16:27:03