Ich habe diese Frage gestellt, um zu erfahren, wie die Größe des Laufzeitaufrufstapels in der JVM erhöht werden kann. Ich habe eine Antwort darauf und ich habe auch viele nützliche Antworten und Kommentare, die relevant dafür sind, wie Java mit der Situation umgeht, in der ein großer Laufzeitstapel benötigt wird. Ich habe meine Frage mit der Zusammenfassung der Antworten erweitert.
Ursprünglich wollte ich die Größe des JVM-Stacks erhöhen, damit Programme wie a ohne laufen 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));
}
}
Die entsprechende Konfigurationseinstellung ist das java -Xss...
Befehlszeilenflag mit einem ausreichend großen Wert. Für das TT
obige Programm funktioniert es mit OpenJDKs JVM folgendermaßen:
$ javac TT.java
$ java -Xss4m TT
Eine der Antworten hat 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 empfohlen, um java -Xss...
zu vermeiden, dass Speicher für Threads verschwendet wird, die ihn nicht benötigen.
Ich war neugierig, wie groß ein Stapel ist, den das obige Programm genau benötigt, also habe ich ihn n
vergrößert ausgeführt:
- -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 für die obige Funktion ungefähr 16 Bytes pro Stapelrahmen verwendet, was vernünftig ist.
Die obige Aufzählung kann genug sein, anstatt genug zu sein , da die Stapelanforderung nicht deterministisch ist: Sie wird mehrmals mit derselben Quelldatei ausgeführt, und dieselbe ist -Xss...
manchmal erfolgreich und ergibt manchmal eine StackOverflowError
. ZB für 1 << 20, -Xss18m
war genug in 7 von 10 Läufen und -Xss19m
war auch nicht immer genug, aber -Xss20m
war genug (in allen 100 Läufen von 100). Verursacht die Speicherbereinigung, das Einschalten der JIT oder etwas anderes dieses nicht deterministische Verhalten?
Die Stapelverfolgung, die bei a StackOverflowError
(und möglicherweise auch bei anderen Ausnahmen) gedruckt wird, zeigt nur die neuesten 1024 Elemente des Laufzeitstapels. Eine Antwort unten zeigt, wie die genaue erreichte Tiefe gezählt wird (die viel größer als 1024 sein kann).
Viele Leute, 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, in eine Reihe von rekursiven Funktionen in iterative Funktionen umzuwandeln (unter Verwendung eines z. B. Stack
Objekts, das auf dem Heap anstatt auf dem Laufzeitstapel aufgefüllt wird). Für diese spezielle fact
Funktion ist es ziemlich einfach, sie zu konvertieren. Meine iterative Version würde folgendermaßen 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));
}
}
Zu Ihrer Information, wie die obige iterative Lösung zeigt, kann die fact
Funktion die exakte Fakultät von Zahlen über 65 (sogar über 20) nicht berechnen, da der in Java integrierte Typ long
überlaufen würde. Ein Refactoring fact
, bei dem ein BigInteger
statt zurückgegeben wird, long
würde auch bei großen Eingaben genaue Ergebnisse liefern.
Antworten:
Hmm ... es funktioniert bei mir und mit weit weniger als 999 MB Stack:
(Windows JDK 7, Build 17.0-b05-Client-VM und Linux JDK 6 - dieselben Versionsinformationen wie Sie veröffentlicht haben)
quelle
Ich nehme an, Sie haben die "Tiefe von 1024" anhand der wiederkehrenden Linien in der Stapelspur berechnet.
Offensichtlich scheint die Länge des Stack-Trace-Arrays in Throwable auf 1024 begrenzt zu sein. Versuchen Sie das folgende Programm:
quelle
Wenn Sie mit der Thread-Stapelgröße spielen möchten, sollten Sie sich die Option -Xss in der Hotspot-JVM ansehen. Bei Nicht-Hotspot-VMs kann dies etwas anderes sein, da die -X-Parameter für die JVM verteilungsspezifisch sind (IIRC).
Auf Hotspot sieht dies so aus, als
java -Xss16M
ob Sie die Größe 16 Megabyte erreichen möchten.Art
java -X -help
Sie ein, wenn Sie alle verteilungsspezifischen JVM-Parameter anzeigen möchten, die Sie übergeben können. Ich bin nicht sicher, ob dies bei anderen JVMs gleich funktioniert, druckt jedoch alle Hotspot-spezifischen Parameter.Für das, was es wert ist - ich würde empfehlen, die Verwendung rekursiver Methoden in Java einzuschränken. Es ist nicht besonders gut, sie zu optimieren - zum einen unterstützt die JVM keine Tail-Rekursion (siehe Verhindert die JVM Tail-Call-Optimierungen? ). Versuchen Sie, Ihren oben genannten Fakultätscode umzugestalten, um anstelle von rekursiven Methodenaufrufen eine while-Schleife zu verwenden.
quelle
Die einzige Möglichkeit, die Größe des Stapels innerhalb eines Prozesses zu steuern, besteht darin, einen neuen zu starten
Thread
. Sie können dies jedoch auch steuern, indem Sie mit dem-Xss
Parameter einen selbstaufrufenden Sub-Java-Prozess erstellen .quelle
java -Xss...
.Fügen Sie diese Option hinzu
Mit Ihrem Spark-Submit-Befehl wird dieses Problem behoben.
quelle
Es ist schwierig, eine vernünftige Lösung zu finden, da Sie alle vernünftigen Ansätze vermeiden möchten. Das Refactoring einer Codezeile ist die vernünftige 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 Manipulation von Bytecode, um den Code wie folgt zu ändern;
Jede Antwort für n> 127 ist 0. Dadurch wird eine Änderung des Quellcodes vermieden.
quelle
fact
Funktion in der Frage überarbeitet werden kann, um viel weniger Stapelspeicherplatz zu verbrauchen.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 wird sein
2^15 * sizeof(stack-frame)
. Ich weiß nicht, was Stack-Frame-Größe ist, aber 2 ^ 15 ist 32.768. Ziemlich genau ... Nun, wenn es bei 1024 (2 ^ 10) stoppt, müssen Sie es 2 ^ 5 mal größer machen, es ist 32 mal größer als bei Ihrer tatsächlichen Einstellung.quelle
Andere Poster haben gezeigt, wie man den Speicher vergrößert und wie man sich Anrufe merken kann. Ich würde vorschlagen, dass Sie für viele Anwendungen die Stirling-Formel verwenden können, um das große n zu approximieren! sehr schnell mit fast keinem Speicherbedarf.
Werfen Sie einen Blick auf diesen Beitrag, der eine Analyse der Funktion und des Codes enthält:
http://threebrothers.org/brendan/blog/stirlings-approximation-formula-clojure/
quelle
Ich habe Anagram exersize gemacht , was wie das Count Change- Problem ist, aber mit 50 000 Stückelungen (Münzen). Ich bin nicht sicher, ob es iterativ gemacht werden kann, es ist mir egal. Ich weiß nur, dass die Option -xss keine Auswirkung hatte - ich bin immer nach 1024 Stapelrahmen fehlgeschlagen (möglicherweise leistet Scala schlechte Arbeit bei der Lieferung an Java oder die Einschränkung von printStackTrace. Ich weiß es nicht). Dies ist eine schlechte Option, wie sowieso erklärt. 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,
Sie sehen, dass der Stapel exponentiell tiefer wachsen kann, wenn dem Thread exponentiell mehr Stapel zugewiesen werden.
quelle