Para descrever isso, primeiro vamos entender como variáveis e objetos locais são armazenados.
Variáveis locais são armazenadas na pilha :
Se você olhou para a imagem, deve entender como as coisas estão funcionando.
Quando uma chamada de função é chamada por um aplicativo Java, um quadro de pilha é alocado na pilha de chamadas. O quadro da pilha contém os parâmetros do método chamado, seus parâmetros locais e o endereço de retorno do método. O endereço de retorno indica o ponto de execução a partir do qual a execução do programa deve continuar após o retorno do método invocado. Se não houver espaço para um novo quadro de pilha, ele StackOverflowError
será lançado pela Java Virtual Machine (JVM).
O caso mais comum que pode esgotar a pilha de um aplicativo Java é a recursão. Na recursão, um método se chama durante sua execução. A recursão é considerada uma poderosa técnica de programação de uso geral, mas deve ser usada com cautela, para evitar StackOverflowError
.
Um exemplo de lançamento de um StackOverflowError
é mostrado abaixo:
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);
}
}
Neste exemplo, definimos um método recursivo, chamado recursivePrint
que imprime um número inteiro e, em seguida, se chama, com o próximo número inteiro sucessivo como argumento. A recursão termina até que passemos 0
como um parâmetro. No entanto, em nosso exemplo, passamos o parâmetro 1 e seus seguidores crescentes; consequentemente, a recursão nunca terminará.
Uma execução de amostra, usando o -Xss1M
sinalizador que especifica o tamanho da pilha de encadeamentos igual a 1 MB, é mostrada abaixo:
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)
...
Dependendo da configuração inicial da JVM, os resultados podem diferir, mas eventualmente StackOverflowError
serão lançados. Este exemplo é um exemplo muito bom de como a recursão pode causar problemas, se não for implementada com cuidado.
Como lidar com o StackOverflowError
A solução mais simples é inspecionar cuidadosamente o rastreamento de pilha e detectar o padrão repetitivo dos números de linha. Esses números de linha indicam o código que está sendo chamado recursivamente. Depois de detectar essas linhas, você deve inspecionar cuidadosamente seu código e entender por que a recursão nunca termina.
Se você verificou se a recursão foi implementada corretamente, é possível aumentar o tamanho da pilha, para permitir um número maior de chamadas. Dependendo da Java Virtual Machine (JVM) instalada, o tamanho padrão da pilha de encadeamentos pode ser igual a 512 KB ou 1 MB . Você pode aumentar o tamanho da pilha de threads usando o -Xss
sinalizador. Esse sinalizador pode ser especificado pela configuração do projeto ou pela linha de comando. O formato do
-Xss
argumento é:
-Xss<size>[g|G|m|M|k|K]