it-swarm.com.de

Wie kann man die Java Stackgröße erhöhen?

Ich habe diese Frage gestellt, um zu erfahren, wie die Größe des Runtime-Aufrufstapels in der JVM erhöht werden kann. Ich habe eine Antwort darauf und auch viele nützliche Antworten und Kommentare, die sich darauf beziehen, wie Java behandelt die Situation, in der ein großer Laufzeitstapel benötigt wird. Ich habe meine Frage erweitert mit der Zusammenfassung der Antworten.

Ursprünglich wollte ich den JVM-Stack vergrößern, damit Programme wie ohne StackOverflowError ausgeführt werden.

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));
  }
}

Die entsprechende Konfigurationseinstellung ist das Befehlszeilenflag Java -Xss... Mit einem ausreichend großen Wert. Für das obige Programm TT funktioniert es mit der JVM von OpenJDK folgendermaßen:

$ javac TT.Java
$ Java -Xss4m TT

In einer der Antworten wurde auch darauf hingewiesen, dass die -X... - Flags implementierungsabhängig sind. Ich habe benutzt

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)

Es ist auch möglich, einen großen Stapel nur für einen Thread anzugeben (siehe in einer der Antworten, wie). Dies wird über Java -Xss... Empfohlen, um die Verschwendung von Speicher für Threads zu vermeiden, die diesen nicht benötigen.

Ich war neugierig, wie groß der Stack ist, den das Programm genau benötigt, also habe ich ihn n erhöht:

  • -Xss4m kann ausreichen für fact(1 << 15)
  • -Xss5m kann ausreichen für fact(1 << 17)
  • -Xss7m kann ausreichen für fact(1 << 18)
  • -Xss9m kann ausreichen für fact(1 << 19)
  • -Xss18m kann ausreichen für fact(1 << 20)
  • -Xss35m kann ausreichen für fact(1 << 21)
  • -Xss68m kann ausreichen für fact(1 << 22)
  • -Xss129m kann ausreichen für fact(1 << 23)
  • -Xss258m kann ausreichen für fact(1 << 24)
  • -Xss515m kann ausreichen für fact(1 << 25)

Aus den obigen Zahlen geht hervor, dass Java ungefähr 16 Bytes pro Stack-Frame für die obige Funktion verwendet, was vernünftig ist.

Die obige Aufzählung enthält kann ausreichen anstelle von reicht aus, da die Stack-Anforderung nicht deterministisch ist: Sie wird mehrmals mit derselben Quelldatei und demselben -Xss... ist manchmal erfolgreich und liefert manchmal ein StackOverflowError. Z.B. Für 1 << 20 war -Xss18m in 7 von 10 Läufen ausreichend, und -Xss19m war auch nicht immer ausreichend, aber -Xss20m war ausreichend (insgesamt 100 Läufe) von 100). Verursacht die Garbage Collection, die JIT oder etwas anderes dieses nicht deterministische Verhalten?

In der Stapelablaufverfolgung, die bei StackOverflowError gedruckt wird (und möglicherweise auch bei anderen Ausnahmen), werden nur die neuesten 1024 Elemente des Laufzeitstapels angezeigt. Die folgende Antwort zeigt, wie die genaue erreichte Tiefe (die viel größer als 1024 sein kann) gezählt wird.

Viele Personen, die geantwortet haben, haben darauf hingewiesen, dass es eine gute und sichere Codierungspraxis ist, alternative, weniger stapelhungrige Implementierungen desselben Algorithmus in Betracht zu ziehen. Im Allgemeinen ist es möglich, eine Menge von rekursiven Funktionen in iterative Funktionen umzuwandeln (unter Verwendung eines beispielsweise Stack -Objekts, das auf dem Heap statt auf dem Laufzeitstapel aufgefüllt wird). Für diese spezielle fact -Funktion ist es recht einfach, sie zu konvertieren. Meine iterative Version würde so aussehen:

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));
  }
}

Wie die obige iterative Lösung zeigt, kann die Funktion fact die exakte Fakultät von Zahlen über 65 (tatsächlich sogar über 20) nicht berechnen, da Java eingebaut ist Typ long würde überlaufen. Refactoring fact würde also ein BigInteger anstelle von long zurückgeben und auch für große Eingaben genaue Ergebnisse liefern.

111
pts

Hmm ... es funktioniert bei mir und mit weit weniger als 999MB Stack:

> Java -Xss4m Test
0

(Windows JDK 7, Build 17.0-b05-Client-VM und Linux JDK 6 - dieselben Versionsinformationen, die Sie veröffentlicht haben)

70
Jon Skeet

Ich nehme an, Sie haben die "Tiefe von 1024" anhand der wiederkehrenden Linien in der Stapelspur berechnet.

Offensichtlich ist die Länge des Stack-Trace-Arrays in Throwable auf 1024 begrenzt. Versuchen Sie Folgendes:

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
Jay

Wenn Sie mit der Thread-Stack-Größe spielen möchten, sollten Sie sich die Option -Xss in der Hotspot-JVM ansehen. Bei Nicht-Hotspot-VMs kann dies anders sein, da die -X-Parameter für die JVM verteilungsspezifisch sind (IIRC).

Auf Hotspot sieht das so aus: Java -Xss16M wenn du die größe 16 megs machen willst.

Art Java -X -help Wenn Sie alle verteilungsspezifischen JVM-Parameter anzeigen möchten, die Sie übergeben können, bin ich mir nicht sicher, ob dies bei anderen JVMs gleich funktioniert, aber es werden alle Hotspot-spezifischen Parameter gedruckt.

Ich würde empfehlen, die Verwendung von rekursiven Methoden in Java einzuschränken. Es ist nicht zu großartig, um sie zu optimieren - zum einen unterstützt die JVM keine Schwanzrekursion (siehe Verhindert die JVM Schwanzaufrufoptimierungen? ). Versuchen Sie, Ihren obigen Fakultätscode umzugestalten, um eine while-Schleife anstelle von rekursiven Methodenaufrufen zu verwenden.

9
whaley

Die einzige Möglichkeit, die Größe des Stapels innerhalb des Prozesses zu steuern, besteht darin, ein neues Thread zu starten. Sie können aber auch steuern, indem Sie einen selbstaufrufenden Unterprozess Java mit dem Parameter -Xss Erstellen.

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);
        }
    }

}
8
Dennis C

Fügen Sie diese Option hinzu

--driver-Java-options -Xss512m

mit dem Befehl spark-submit können Sie dieses Problem beheben.

3
Guibin Zhang

Es ist schwierig, eine vernünftige Lösung zu finden, da Sie unbedingt alle vernünftigen Ansätze vermeiden möchten. Refactoring einer Codezeile ist die sinnvolle Lösung.

Hinweis: Die Verwendung von -Xss legt die Stapelgröße jedes Threads fest und ist eine sehr schlechte Idee.

Ein anderer Ansatz ist die Bytecode-Manipulation, um den Code wie folgt zu ändern.

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

jede Antwort für n> 127 lautet 0. Vermeiden Sie daher, den Quellcode zu ändern.

1
Peter Lawrey

Ich habe Anagram excersize , das ist wie Count Change Problem, aber mit 50 000 Stückelungen (Münzen). Ich bin nicht sicher, ob es iterativ durchgeführt werden kann , es ist mir egal. Ich weiß nur, dass die Option -xss keine Auswirkung hatte - ich bin immer nach 1024 Stack-Frames gescheitert (möglicherweise führt scala eine fehlerhafte Übermittlung an Java oder eine Einschränkung von printStackTrace aus. Ich weiß es nicht). Dies ist, wie bereits erläutert, eine schlechte Option. Sie möchten nicht, dass alle Threads in der App monströs sind. Ich habe jedoch einige Experimente mit neuem Thread (Stapelgröße) durchgeführt. Das funktioniert in der Tat,

  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)

Sie sehen, dass der Stapel exponentiell tiefer wachsen kann, wenn dem Thread exponentiell mehr Stapel zugewiesen werden.

0
Val

Seltsam! Sie sagen, dass Sie eine Rekursion von 1 << 15 Tiefe erzeugen möchten ??? !!!!

Ich würde vorschlagen, es NICHT zu versuchen. Die Größe des Stapels ist 2^15 * sizeof(stack-frame). Ich weiß nicht, wie groß der Stack-Frame ist, aber 2 ^ 15 ist 32.768. Ziemlich viel ... Nun, wenn es bei 1024 (2 ^ 10) endet, müssen Sie es 2 ^ 5 mal größer machen, es ist 32 mal größer als mit Ihrer aktuellen Einstellung.

0
helios

Andere Plakate haben darauf hingewiesen, wie Sie das Gedächtnis erhöhen und Anrufe auswendig lernen können. Ich würde vorschlagen, dass Sie für viele Anwendungen die Stirling-Formel verwenden können, um ein großes n zu approximieren! Sehr schnell und fast ohne Speicherbedarf.

Schauen Sie sich diesen Beitrag an, der eine Analyse der Funktion und des Codes enthält:

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

0
abscondment