¿Cómo aumentar el tamaño de la pila de Java?

Resuelto pts asked hace 14 años • 9 respuestas

Hice esta pregunta para saber cómo aumentar el tamaño de la pila de llamadas en tiempo de ejecución en la JVM. Tengo una respuesta a esto, y también tengo muchas respuestas útiles y comentarios relevantes sobre cómo Java maneja la situación en la que se necesita una gran pila de tiempo de ejecución. He ampliado mi pregunta con el resumen de las respuestas.

Originalmente quería aumentar el tamaño de la pila JVM para que programas como se ejecuten sin un archivo 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));
  }
}

El valor de configuración correspondiente es el java -Xss...indicador de línea de comando con un valor suficientemente grande. Para el programa TTanterior, funciona así con la JVM de OpenJDK:

$ javac TT.java
$ java -Xss4m TT

Una de las respuestas también señaló que las -X...banderas dependen de la implementación. yo estaba 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)

También es posible especificar una pila grande solo para un hilo (vea cómo hacerlo en una de las respuestas). Se recomienda esto java -Xss...para evitar desperdiciar memoria en subprocesos que no la necesitan.

Tenía curiosidad por saber qué tamaño de pila necesita exactamente el programa anterior, así que lo ejecuté ncon mayor tamaño:

  • -Xss4m puede ser suficiente parafact(1 << 15)
  • -Xss5m puede ser suficiente parafact(1 << 17)
  • -Xss7m puede ser suficiente parafact(1 << 18)
  • -Xss9m puede ser suficiente parafact(1 << 19)
  • -Xss18m puede ser suficiente parafact(1 << 20)
  • -Xss35m puede ser suficiente parafact(1 << 21)
  • -Xss68m puede ser suficiente parafact(1 << 22)
  • -Xss129m puede ser suficiente parafact(1 << 23)
  • -Xss258m puede ser suficiente parafact(1 << 24)
  • -Xss515m puede ser suficiente parafact(1 << 25)

De los números anteriores parece que Java está usando aproximadamente 16 bytes por marco de pila para la función anterior, lo cual es razonable.

La enumeración anterior contiene puede ser suficiente en lugar de es suficiente , porque el requisito de la pila no es determinista: ejecutarlo varias veces con el mismo archivo fuente y el mismo -Xss...a veces tiene éxito y otras veces produce un archivo StackOverflowError. Por ejemplo, para 1 << 20, -Xss18mfue suficiente en 7 ejecuciones de 10, y -Xss19mtampoco siempre fue suficiente, pero -Xss20mfue suficiente (en todas 100 ejecuciones de 100). ¿La recolección de basura, el JIT u otra cosa causan este comportamiento no determinista?

El seguimiento de la pila impreso en a StackOverflowError(y posiblemente también en otras excepciones) muestra solo los 1024 elementos más recientes de la pila en tiempo de ejecución. Una respuesta a continuación demuestra cómo contar la profundidad exacta alcanzada (que podría ser mucho mayor que 1024).

Muchas personas que respondieron señalaron que es una práctica de codificación buena y segura considerar implementaciones alternativas del mismo algoritmo que requieren menos pila. En general, es posible convertir un conjunto de funciones recursivas en funciones iterativas (usando, por ejemplo, un Stackobjeto, que se completa en el montón en lugar de en la pila de tiempo de ejecución). Para esta factfunción en particular, es bastante fácil convertirla. Mi versión iterativa se vería así:

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

Para su información, como lo muestra la solución iterativa anterior, la factfunción no puede calcular el factorial exacto de números superiores a 65 (en realidad, incluso superiores a 20), porque el tipo integrado de Java longse desbordaría. Refactorizar factpara que devuelva a BigIntegeren lugar de longtambién produciría resultados exactos para entradas grandes.

pts avatar Sep 13 '10 19:09 pts
Aceptado

Hmm... funciona para mí y con mucho menos de 999 MB de pila:

> java -Xss4m Test
0

(Windows JDK 7, compilación 17.0-b05 de la máquina virtual cliente y Linux JDK 6: la misma información de versión que publicó)

Jon Skeet avatar Sep 13 '2010 12:09 Jon Skeet

¿Supongo que calculó la "profundidad de 1024" mediante las líneas recurrentes en el seguimiento de la pila?

Obviamente, la longitud de la matriz de seguimiento de la pila en Throwable parece estar limitada a 1024. Pruebe el siguiente programa:

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);
    }
}
Jay avatar Sep 15 '2010 08:09 Jay

Si quieres jugar con el tamaño de la pila de subprocesos, querrás mirar la opción -Xss en Hotspot JVM. Puede ser algo diferente en máquinas virtuales que no son Hotspot, ya que los parámetros -X de la JVM son específicos de la distribución, IIRC.

En Hotspot, esto se ve así java -Xss16Msi quieres hacer el tamaño de 16 megas.

Escriba java -X -helpsi desea ver todos los parámetros de JVM específicos de la distribución que puede pasar. No estoy seguro de si esto funciona igual en otras JVM, pero imprime todos los parámetros específicos de Hotspot.

Por si sirve de algo, recomendaría limitar el uso de métodos recursivos en Java. No es muy bueno para optimizarlos; por un lado, la JVM no admite la recursividad de cola (consulte ¿La JVM impide las optimizaciones de llamadas de cola? ). Intente refactorizar su código factorial anterior para usar un bucle while en lugar de llamadas a métodos recursivos.

whaley avatar Sep 13 '2010 14:09 whaley

La única forma de controlar el tamaño de la pila dentro del proceso es iniciar uno nuevo Thread. Pero también puedes controlar creando un subproceso Java autollamado con el -Xssparámetro.

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

}
Dennis C avatar Sep 17 '2010 13:09 Dennis C

Es difícil dar una solución sensata ya que se desea evitar todos los enfoques sensatos. Refactorizar una línea de código es la solución sensata.

Nota: Usar -Xss establece el tamaño de la pila de cada hilo y es una muy mala idea.

Otro enfoque es la manipulación del código de bytes para cambiar el código de la siguiente manera;

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

dada cada respuesta para n > 127 es 0. Esto evita cambiar el código fuente.

Peter Lawrey avatar Sep 17 '2010 21:09 Peter Lawrey