Para describir esto, primero comprendamos cómo se almacenan las variables y los objetos locales .
Las variables locales se almacenan en la pila :
Si mirabas la imagen, deberías poder entender cómo funcionan las cosas.
Cuando una aplicación Java invoca una llamada de función, se asigna un marco de pila en la pila de llamadas. El marco de la pila contiene los parámetros del método invocado, sus parámetros locales y la dirección de retorno del método. La dirección de retorno indica el punto de ejecución desde el cual, la ejecución del programa continuará después de que regrese el método invocado. Si no hay espacio para un nuevo marco de pila, StackOverflowError
Java Virtual Machine (JVM) lo lanza.
El caso más común que posiblemente puede agotar la pila de una aplicación Java es la recursividad. En la recursividad, un método se invoca durante su ejecución. La recursión se considera una técnica de programación poderosa de uso general, pero debe usarse con precaución para evitarla StackOverflowError
.
A continuación se muestra un ejemplo de tirar un StackOverflowError
:
StackOverflowErrorExample.java:
public class StackOverflowErrorExample {
public static void recursivePrint(int num) {
System.out.println("Number: " + num);
if (num == 0)
return;
else
recursivePrint(++num);
}
public static void main(String[] args) {
StackOverflowErrorExample.recursivePrint(1);
}
}
En este ejemplo, definimos un método recursivo, llamado recursivePrint
que imprime un número entero y luego se llama a sí mismo, con el siguiente número entero sucesivo como argumento. La recursión termina hasta que pasamos 0
como parámetro. Sin embargo, en nuestro ejemplo, pasamos el parámetro de 1 y sus seguidores crecientes, por lo tanto, la recursión nunca terminará.
A continuación se muestra una ejecución de muestra, utilizando el -Xss1M
indicador que especifica el tamaño de la pila de subprocesos para que sea igual a 1 MB:
Number: 1
Number: 2
Number: 3
...
Number: 6262
Number: 6263
Number: 6264
Number: 6265
Number: 6266
Exception in thread "main" java.lang.StackOverflowError
at java.io.PrintStream.write(PrintStream.java:480)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
at java.io.PrintStream.write(PrintStream.java:527)
at java.io.PrintStream.print(PrintStream.java:669)
at java.io.PrintStream.println(PrintStream.java:806)
at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:4)
at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
...
Dependiendo de la configuración inicial de la JVM, los resultados pueden diferir, pero eventualmente StackOverflowError
se arrojarán. Este ejemplo es un muy buen ejemplo de cómo la recursión puede causar problemas, si no se implementa con precaución.
Cómo lidiar con el StackOverflowError
La solución más simple es inspeccionar cuidadosamente el seguimiento de la pila y detectar el patrón repetitivo de los números de línea. Estos números de línea indican el código que se llama recursivamente. Una vez que detecte estas líneas, debe inspeccionar cuidadosamente su código y comprender por qué la recursión nunca termina.
Si ha verificado que la recursión se implementa correctamente, puede aumentar el tamaño de la pila para permitir un mayor número de invocaciones. Dependiendo de la máquina virtual Java (JVM) instalada, el tamaño predeterminado de la pila de subprocesos puede ser igual a 512 KB o 1 MB . Puede aumentar el tamaño de la pila de subprocesos utilizando la -Xss
bandera. Este indicador se puede especificar mediante la configuración del proyecto o mediante la línea de comando. El formato del
-Xss
argumento es:
-Xss<size>[g|G|m|M|k|K]