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 de JVM para que los programas se ejecuten sin un 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 la línea de comandos con un valor suficientemente grande. Para el programa TT
anterior, funciona así con la JVM de OpenJDK:
$ javac TT.java
$ java -Xss4m TT
Una de las respuestas también ha señalado que las -X...
banderas dependen de la implementación. 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 en una de las respuestas cómo). Esto se recomienda 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 he ejecutado n
aumentado:
- -Xss4m puede ser suficiente para
fact(1 << 15)
- -Xss5m puede ser suficiente para
fact(1 << 17)
- -Xss7m puede ser suficiente para
fact(1 << 18)
- -Xss9m puede ser suficiente para
fact(1 << 19)
- -Xss18m puede ser suficiente para
fact(1 << 20)
- -Xss35m puede ser suficiente para
fact(1 << 21)
- -Xss68m puede ser suficiente para
fact(1 << 22)
- -Xss129m puede ser suficiente para
fact(1 << 23)
- -Xss258m puede ser suficiente para
fact(1 << 24)
- -Xss515m puede ser suficiente para
fact(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 a -Xss...
veces tiene éxito y a veces produce un archivo StackOverflowError
. Por ejemplo, para 1 << 20, -Xss18m
fue suficiente en 7 carreras de 10, y -Xss19m
tampoco siempre fue suficiente, pero -Xss20m
fue suficiente (en todas las 100 carreras de 100). ¿La recolección de basura, la activación del JIT o algo más 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 puede ser mucho mayor que 1024).
Muchas personas que respondieron han señalado que es una práctica de codificación buena y segura considerar implementaciones alternativas del mismo algoritmo que requieren menos pilas. En general, es posible convertir a un conjunto de funciones recursivas en funciones iterativas (usando un Stack
objeto, por ejemplo , que se llena en el montón en lugar de en la pila en tiempo de ejecución). Para esta fact
función en particular , es bastante fácil convertirlo. 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 fact
función no puede calcular el factorial exacto de números por encima de 65 (en realidad, incluso por encima de 20), porque el tipo integrado de Java long
se desbordaría. La refactorización fact
para que devuelva un en BigInteger
lugar de long
también produciría resultados exactos para entradas grandes.