Por que é possível recuperar de um StackOverflowError?

100

Estou surpreso em como é possível continuar a execução mesmo depois que um StackOverflowErrorocorreu em Java.

Sei que StackOverflowErroré um subalterno da classe Erro. A classe Error foi decumentada como "uma subclasse de Throwable que indica problemas sérios que um aplicativo razoável não deve tentar detectar."

Isso parece mais uma recomendação do que uma regra, subtendendo que capturar um erro como StackOverflowError é de fato permitido e depende da razoabilidade do programador não fazê-lo. E veja, eu testei esse código e ele termina normalmente.

public class Test
{
    public static void main(String[] args)
    {
        try {
            foo();
        } catch (StackOverflowError e) {
            bar();
        }
        System.out.println("normal termination");
    }

    private static void foo() {
        System.out.println("foo");
        foo();
    }

    private static void bar() {
        System.out.println("bar");
    }
}

Como isso pode ser? Acho que, no momento em que StackOverflowError é lançado, a pilha deve estar tão cheia que não há espaço para chamar outra função. O bloco de tratamento de erros está sendo executado em uma pilha diferente ou o que está acontecendo aqui?

user3370796
fonte
57
Eu cometo erros no StackOverflow o tempo todo. Mas não me impede de voltar.
10
Ei, cara ... Ouvi dizer que você gostou de estouro de pilha, então colocamos um estouro de pilha em stackoverflow.com!
Pierre Henry
Porque as arquiteturas modernas usam Ponteiros de Frame para facilitar o desenrolar das pilhas, mesmo as parciais. Contanto que o código + contexto para fazer isso não precise ser alocado dinamicamente fora da pilha, não deve haver um problema.
RBarryYoung

Respostas:

119

Quando a pilha transborda e StackOverflowErroré lançada, o tratamento usual de exceção a desenrola. Desenrolar a pilha significa:

  • abortar a execução da função atualmente ativa
  • exclua seu frame de pilha, prossiga com a função de chamada
  • abortar a execução do chamador
  • exclua seu frame de pilha, prossiga com a função de chamada
  • e assim por diante...

... até que a exceção seja detectada. Isso é normal (de fato, necessário) e independente de qual exceção é lançada e por quê. Como você detectou a exceção fora da primeira chamada a foo(), os milhares de fooquadros de pilha que preencheram a pilha foram todos desenrolados e a maior parte da pilha está livre para ser usada novamente.


fonte
1
@fge Sinta-se à vontade para editar, considerei uma quebra de parágrafo, mas não consegui encontrar um lugar onde parecesse bom.
1
Você poderia usar marcadores ... Estou relutante em editar a postagem de outras pessoas;)
fge
1
O ponto é que o mais interno footerminou com um estado indefinido, portanto, qualquer objeto que ele possa ter tocado deve ser considerado como quebrado. Como você não sabe em qual função o estouro de pilha ocorreu, apenas que deve ser descendente do trybloco que o capturou, qualquer objeto que possa ser modificado por qualquer método acessível a partir daí agora é suspeito. Normalmente não vale a pena descobrir o que aconteceu e tentar consertar.
Simon Richter,
2
@delnan, acho que a resposta está incompleta sem entrar em detalhes porque isso é uma má ideia. A diferença para uma exceção lançada explicitamente é que Errors não pode ser antecipado mesmo ao escrever um código seguro de exceção.
Simon Richter
1
@SimonRichter Não, a pergunta é bem específica. Não se trata de lidar com Errors. O OP está pedindo apenas sobre StackOverflowError, e está pedindo uma coisa específica sobre o tratamento desse erro: como uma chamada de método pode não falhar quando esse erro é detectado.
Bakuriu
23

Quando o StackOverflowError é lançado, a pilha está cheia. No entanto, quando é detectado , todas essas foochamadas foram retiradas da pilha. barpode ser executado normalmente porque a pilha não está mais transbordando de foos. (Observe que não acho que o JLS garante que você possa se recuperar de um estouro de pilha como este.)

user2357112 suporta Monica
fonte
12

Quando o StackOverFlow ocorre, a JVM vai para a captura, liberando a pilha.

No seu exemplo, ele se livra de todos os foo empilhados.

Nicolas Defranoux
fonte
8

Porque a pilha não transborda. Um nome melhor pode ser AttemptToOverflowStack. Basicamente, o que significa é que a última tentativa de ajustar o frame da pilha falha porque não há espaço livre suficiente na pilha. A pilha pode realmente ter muito espaço restante, mas não espaço suficiente. Portanto, qualquer operação que dependesse do sucesso da chamada (normalmente uma invocação de método), nunca é executada e tudo o que resta é o programa lidar com esse fato. O que significa que realmente não é diferente de qualquer outra exceção. Na verdade, você pode capturar a exceção na função que está fazendo a chamada.

Jmoreno
fonte
1
Apenas tome cuidado se você fizer isso para que seu manipulador de exceções não exija mais espaço de pilha do que o disponível!
Vince
2

Como já foi respondido , é possível executar código e, em particular, chamar funções, após capturar a, StackOverflowErrorpois o procedimento normal de tratamento de exceções da JVM desenrola a pilha entre throwos catchpontos e, liberando espaço de pilha para você usar. E sua experiência confirma que é o caso.

No entanto, isso não é exatamente o mesmo que dizer que, em geral, é possível recuperar de a StackOverflowError.

A StackOverflowErrorIS-A VirtualMachineError, que IS-AN Error. Como você observou, Java fornece alguns conselhos vagos para Error:

indica problemas sérios que um aplicativo razoável não deve tentar detectar

e você, razoavelmente, conclui que deve soar como pegar um Errorpode estar OK em algumas circunstâncias. Observe que realizar um experimento não demonstra que algo é, em geral, seguro de fazer. Apenas as regras da linguagem Java e as especificações das classes que você usa podem fazer isso. A VirtualMachineErroré uma classe especial de exceção, porque a Java Language Specification e a Java Virtual Machine Specification fornecem informações sobre a semântica dessa exceção. Em particular, o último diz :

Uma implementação de Java Virtual Machine lança um objeto que é uma instância de uma subclasse da classe VirtualMethodErrorquando um erro interno ou limitação de recurso a impede de implementar a semântica descrita neste capítulo. Esta especificação não pode prever onde erros internos ou limitações de recursos podem ser encontrados e não determina com precisão quando eles podem ser relatados. Assim, qualquer uma das VirtualMethodErrorsubclasses definidas abaixo podem ser lançadas a qualquer momento durante a operação da Java Virtual Machine:

...

  • StackOverflowErrorObservação: A implementação da Java Virtual Machine ficou sem espaço de pilha para um encadeamento, normalmente porque o encadeamento está fazendo um número ilimitado de invocações recursivas como resultado de uma falha no programa em execução.

O problema crucial é que você "não pode prever" onde ou quando um StackOverflowErrorserá lançado. Não há garantias sobre onde ele não será lançado. Você não pode confiar que ele será lançado na entrada de um método, por exemplo. Pode ser lançado em um ponto dentro de um método.

Essa imprevisibilidade é potencialmente desastrosa. Como pode ser lançado dentro de um método, ele poderia ser lançado em parte por meio de uma sequência de operações que a classe considera uma operação "atômica", deixando o objeto em um estado parcialmente modificado e inconsistente. Com o objeto em um estado inconsistente, qualquer tentativa de usar esse objeto pode resultar em comportamento incorreto. Em todos os casos práticos, você não pode saber qual objeto está em um estado inconsistente, portanto, você deve assumir que nenhum objeto é confiável. Qualquer operação de recuperação ou tentativa de continuar após a exceção ser detectada pode, portanto, ter um comportamento incorreto. A única coisa segura a fazer é, portanto, não pegar umStackOverflowError, mas sim para permitir que o programa seja encerrado. (Na prática, você pode tentar fazer algum registro de erros para ajudar na solução de problemas, mas não pode confiar que esse registro funcione corretamente). Ou seja, você não pode se recuperar de forma confiável de aStackOverflowError .

Raedwald
fonte