Come aumentare la dimensione dello stack Java?


123

Ho posto questa domanda per sapere come aumentare la dimensione dello stack di chiamate di runtime nella JVM. Ho una risposta a questo e ho anche molte risposte e commenti utili relativi a come Java gestisce la situazione in cui è necessario un grande stack di runtime. Ho esteso la mia domanda con il riepilogo delle risposte.

Inizialmente volevo aumentare la dimensione dello stack JVM in modo che i programmi funzionino senza un file 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));
  }
}

L'impostazione di configurazione corrispondente è il java -Xss...flag della riga di comando con un valore sufficientemente grande. Per il programma TTsopra, funziona in questo modo con la JVM di OpenJDK:

$ javac TT.java
$ java -Xss4m TT

Una delle risposte ha anche sottolineato che i -X...flag dipendono dall'implementazione. Stavo usando

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)

È anche possibile specificare uno stack grande solo per un thread (vedere in una delle risposte come). Questo è consigliato java -Xss...per evitare di sprecare memoria per i thread che non ne hanno bisogno.

Ero curioso di sapere quanto fosse grande uno stack esattamente necessario al programma sopra, quindi l'ho eseguito naumentato:

  • -Xss4m può essere sufficiente per fact(1 << 15)
  • -Xss5m può essere sufficiente per fact(1 << 17)
  • -Xss7m può essere sufficiente per fact(1 << 18)
  • -Xss9m può essere sufficiente per fact(1 << 19)
  • -Xss18m può essere sufficiente per fact(1 << 20)
  • -Xss35m può essere sufficiente per fact(1 << 21)
  • -Xss68m può essere sufficiente per fact(1 << 22)
  • -Xss129m può essere sufficiente per fact(1 << 23)
  • -Xss258m può essere sufficiente per fact(1 << 24)
  • -Xss515m può essere sufficiente per fact(1 << 25)

Dai numeri sopra sembra che Java stia usando circa 16 byte per stack frame per la funzione sopra, il che è ragionevole.

L'enumerazione precedente contiene può essere sufficiente invece di è sufficiente , perché il requisito dello stack non è deterministico: eseguirlo più volte con lo stesso file sorgente e lo stesso a -Xss...volte riesce ea volte produce un file StackOverflowError. Ad esempio, per 1 << 20, -Xss18mera sufficiente in 7 run su 10, e -Xss19mnon era nemmeno sempre abbastanza, ma -Xss20mera sufficiente (in tutto 100 run su 100). La raccolta dei rifiuti, l'attivazione del JIT o qualcos'altro causa questo comportamento non deterministico?

La traccia dello stack stampata in un StackOverflowError(e possibilmente anche in altre eccezioni) mostra solo i 1024 elementi più recenti dello stack di runtime. Una risposta di seguito mostra come contare la profondità esatta raggiunta (che potrebbe essere molto più grande di 1024).

Molte persone che hanno risposto hanno sottolineato che è una pratica di codifica buona e sicura considerare implementazioni alternative e meno affamate di stack dello stesso algoritmo. In generale, è possibile convertire un insieme di funzioni ricorsive in funzioni iterative (usando un esStack oggetto, , che viene popolato sull'heap invece che sullo stack di runtime). Per questa particolare factfunzione, è abbastanza facile convertirla. La mia versione iterativa sarebbe simile a:

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

Cordiali saluti, come mostra la soluzione iterativa sopra, la factfunzione non può calcolare il fattoriale esatto dei numeri sopra 65 (in realtà, anche sopra 20), perché il tipo integrato di Java longandrebbe in overflow. Il refactoring in factmodo che restituisca a BigIntegerinvece di longprodurrebbe risultati esatti anche per input di grandi dimensioni.


Sembra più semplice di quello che è. fact () viene chiamato 32K volte ricorsivamente. Dovrebbe essere inferiore a 1 MB di stack. : - /
Aaron Digulla

@Aaron: + Funzione in testa, che è ..
MOLTO

4
A parte i tuoi problemi di stack. nota che stai facendo esplodere i tuoi long e int. 1 << 4 è il valore massimo che posso utilizzare prima di diventare negativo e quindi in 0. Prova a utilizzare BigInteger
Sean

Non sono sicuro che l'overhead della funzione sia effettivamente così tanto - penso che dovresti comunque essere in grado di effettuare 2 ^ 15 chiamate nell'ordine di pochi megabyte di spazio nello stack.
Neil Coffey

7
Nota: stai impostando la dimensione dello stack di ogni thread e produci un risultato senza senso, il tutto per evitare il refactoring di una riga di codice. Sono contento che tu abbia risolto le tue priorità. : P
Peter Lawrey

Risposte:


78

Hmm ... funziona per me e con molto meno di 999 MB di stack:

> java -Xss4m Test
0

(Windows JDK 7, VM client build 17.0-b05 e Linux JDK 6: stesse informazioni sulla versione pubblicate)


1
molto probabilmente era per il mio commento, l'ho cancellato quando mi sono reso conto della stessa cosa che ha postato Neil.
Sean

Grazie a questa domanda e alla tua risposta, sono riuscito a completare la mia assegnazione. La mia funzione DFS doveva ricorrere a un grafico con ~ 10 ^ 5 vertici. Infine ha funzionato con -Xss129m: D
bholagabbar

11

Presumo che tu abbia calcolato la "profondità di 1024" dalle linee ricorrenti nello stack trace?

Ovviamente, la lunghezza dell'array di traccia dello stack in Throwable sembra essere limitata a 1024. Prova il seguente programma:

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

9

Se vuoi giocare con la dimensione dello stack del thread, ti consigliamo di guardare l'opzione -Xss sulla JVM Hotspot. Potrebbe essere qualcosa di diverso sulle VM non Hotspot poiché i parametri -X della JVM sono specifici della distribuzione, IIRC.

Su Hotspot, sembra che java -Xss16Mtu voglia ottenere la dimensione di 16 mega.

genere java -X -help se si desidera visualizzare tutti i parametri JVM specifici della distribuzione che è possibile trasferire. Non sono sicuro che funzioni allo stesso modo su altre JVM, ma stampa tutti i parametri specifici dell'hotspot.

Per quello che vale, consiglierei di limitare l'uso di metodi ricorsivi in ​​Java. Non è troppo eccezionale per ottimizzarli: per esempio, la JVM non supporta la ricorsione in coda (vedi La JVM impedisce l'ottimizzazione delle chiamate in coda? ). Prova a refactoring del codice fattoriale sopra per utilizzare un ciclo while invece di chiamate di metodo ricorsive.


8

L'unico modo per controllare la dimensione dello stack all'interno del processo è avviarne uno nuovo Thread. Ma puoi anche controllare creando un processo Java secondario con il -Xssparametro.

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

}

Grazie per questa risposta informativa, è bello conoscere altre opzioni oltre a java -Xss....
punti

1
Mi sono entusiasmato per questo, ma dopo aver letto docs.oracle.com/javase/6/docs/api/java/lang/Thread.html#Thread - il costruttore di stacksize - l'entusiasmo è andato via.
kellogs

Mi chiedo quali siano le piattaforme quando il documento dice solo - "Su alcune piattaforme"
Dennis C

3

Aggiungi questa opzione

--driver-java-options -Xss512m

al tuo comando spark-submit risolverà questo problema.


2

È difficile fornire una soluzione sensata poiché desideri evitare tutti gli approcci sensati. Il refactoring di una riga di codice è la soluzione ragionevole.

Nota: l'uso di -Xss imposta la dimensione dello stack di ogni thread ed è una pessima idea.

Un altro approccio è la manipolazione del codice byte per modificare il codice come segue;

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

dato ogni risposta per n> 127 è 0. Questo evita di cambiare il codice sorgente.


1
Grazie per aver sottolineato che l'impostazione di una dimensione dello stack elevata sprecherebbe memoria per i thread che non ne hanno bisogno. Grazie anche per aver sottolineato che la factfunzione nella domanda può essere rifattorizzata per utilizzare molto meno spazio nello stack.
punti

1
@pts, i tuoi ringraziamenti sono annotati. Penso che questa sia una domanda sensata dato un caso d'uso molto più complesso, ma quelli sono molto rari.
Peter Lawrey,

0

Strano! Stai dicendo che vuoi generare una ricorsione di 1 << 15 profondità ??? !!!!

Suggerirei di NON provarlo. La dimensione della pila sarà 2^15 * sizeof(stack-frame). Non so quale sia la dimensione dello stack frame, ma 2 ^ 15 è 32,768. Praticamente ... Beh, se si ferma a 1024 (2 ^ 10) dovrai renderlo 2 ^ 5 volte più grande, è 32 volte più grande rispetto alla tua impostazione attuale.



0

Ho fatto Anagram excersize , che è come il problema Count Change ma con 50.000 denominazioni (monete). Non sono sicuro che possa essere fatto in modo iterativo , non mi interessa. So solo che l'opzione -xss non ha avuto effetto: ho sempre fallito dopo 1024 stack frame (potrebbe essere che scala non funzioni correttamente nella consegna a java o limitazioni printStackTrace. Non lo so). Questa è una cattiva opzione, come spiegato comunque. Non vuoi che tutti i thread nell'app siano mostruosi. Tuttavia, ho fatto alcuni esperimenti con il nuovo thread (dimensione dello stack). Funziona davvero,

  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)

Si vede che lo stack può crescere in modo esponenziale più in profondità con uno stack esponenzialmente assegnato al thread.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.