NullPointerException em Java sem StackTrace

333

Eu tive instâncias do nosso código Java pegar um NullPointerException, mas quando tento registrar o StackTrace (que basicamente acaba chamando Throwable.printStackTrace()), tudo o que recebo é:

java.lang.NullPointerException

Alguém mais se deparou com isso? Tentei pesquisar no Google por "rastreamento de pilha vazia de ponteiro nulo java", mas não encontrei nada parecido com isso.

Edward Shtern
fonte
Qual é o contexto? Existem vários threads envolvidos? Eu tive problemas ao tentar obter o rastreamento de pilha de uma exceção em um SwingWorker.
Michael Myers
Sem threading envolvido aqui, apenas Java simples.
Edward Shtern
1
@Bozho - não - ainda não sei como reproduzir o NullPointer.
Edward Shtern
1
related: stackoverflow.com/questions/1076191/...
Joshua Goldberg
Mais informações no -XX:-OmitStackTraceInFastThrowdup: stackoverflow.com/questions/4659151/…
Vadzim

Respostas:

407

Você provavelmente está usando o HotSpot JVM (originalmente da Sun Microsystems, posteriormente comprado pela Oracle, parte do OpenJDK), que executa muita otimização. Para recuperar os rastreamentos da pilha, é necessário passar a opção -XX:-OmitStackTraceInFastThrowpara a JVM.

A otimização é que, quando uma exceção (normalmente uma NullPointerException) ocorre pela primeira vez, o rastreamento de pilha completo é impresso e a JVM se lembra do rastreamento de pilha (ou talvez apenas o local do código). Quando essa exceção ocorre com bastante frequência, o rastreamento de pilha não é mais impresso, tanto para obter melhor desempenho quanto para não inundar o log com rastreamentos de pilha idênticos.

Para ver como isso é implementado na JVM do HotSpot, pegue uma cópia dela e procure a variável global OmitStackTraceInFastThrow. A última vez que olhei o código (em 2019), estava no arquivo graphKit.cpp .

Roland Illig
fonte
1
Obrigado pela dica. Alguma idéia se houver alguma dica oculta para passar essa opção (parece bastante inócuo, desde que meu aplicativo não ative uma tonelada de exceções)?
Edward Shtern
Não há truques ocultos que eu conheça. Quando você olha para o código-fonte do Hotspot, pode ver que essa opção é usada apenas em um lugar (graphKit.cpp). E isso parece bom para mim.
Roland Illig
34
Pensei em adicionar o bit adicional de informação que, quando o rastreamento de pilha fica otimizado longe, é porque ele ficou totalmente manipulado pelo menos uma vez: jawspeak.com/2010/05/26/...
sharakan
1
Estou executando um OpenJDK JVM, versão 1.8.0u171 (Debian 9), e parece que ele também aceita a -XX:-OmitStackTraceInFastThrowflag. Ainda estou para confirmar se foi por isso que também não consegui imprimir os rastreamentos de pilha (por exemplo, usando e.printStackTrace), mas parece altamente provável. Expandi a resposta para refletir essa descoberta.
Chris W.
No nosso caso, as 125 primeiras exceções tiveram um rastreamento de pilha e as demais em três rotações de arquivos de log não tiveram. Esta resposta foi muito útil para encontrar o culpado.
sukhmel
61

Como você mencionou em um comentário, você está usando log4j. Descobri (inadvertidamente) um lugar onde havia escrito

LOG.error(exc);

em vez do típico

LOG.error("Some informative message", e);

por preguiça ou talvez simplesmente não pensando nisso. A parte infeliz disso é que não se comporta como você espera. A API do logger, na verdade, leva Object como o primeiro argumento, não uma sequência - e, em seguida, chama toString () no argumento. Portanto, em vez de obter o belo rastreamento de pilha bonita, ele apenas imprime o toString - que no caso do NPE é bastante inútil.

Talvez seja isso que você está enfrentando?

Steven Schlansker
fonte
+1: Isso explicaria o comportamento descrito, e você não é o único que descobriu este :)
Peter Lang
4
Na verdade, temos uma política padrão de nunca usar o primeiro formulário acima (LOG.error (exc);) - sempre usamos a assinatura de 2 parâmetros para adicionar alguma instrução descritiva aos logs, em vez de apenas um rastreamento de pilha bruto.
Edward Shtern
5
Claro, mas a política não significa que é sempre executada corretamente! Achei que valia a pena mencionar, pelo menos.
Steven Schlansker
É verdade, mas, neste caso, foi ;-)
Edward Shtern
28

Vimos esse mesmo comportamento no passado. Aconteceu que, por algum motivo louco, se uma NullPointerException ocorresse no mesmo local no código várias vezes, depois de um tempo, o uso Log.error(String, Throwable)pararia de incluir rastreamentos de pilha completos.

Tente olhar mais para trás em seu log. Você pode encontrar o culpado.

EDIT: este bug parece relevante, mas foi corrigido há muito tempo, provavelmente não é a causa.

Matt Solnit
fonte
2
O bug está fechado, mas o sinalizador -XX: -OmitStackTraceInFastThrow ainda é necessário para solucionar a otimização do desempenho.
30912 Joshua Goldberg
Eu tenho visto muito isso recentemente. Alguma pista sobre o que pode estar causando isso ou como corrigi-lo? O sistema de registro pode ter sido até por dias, ea causa real girado para fora, esquece a busca tediosa ...
Pawel Veselov
5
Pawel, você já tentou a -XX:-OmitStackTraceInFastThrowbandeira da JVM sugerida por Joshua? Consulte também stackoverflow.com/a/2070568/6198 .
Matt Solnit
1
Foi isso para nós. Obrigado.
Andrew Cheong
20

Aqui está uma explicação: ponto de acesso fez com que as exceções perdessem seus rastreamentos de pilha na produção - e a correção

Eu testei no Mac OS X

  • versão java "1.6.0_26"
  • Java (TM) SE Runtime Environment (compilação 1.6.0_26-b03-383-11A511)
  • VM do servidor Java HotSpot (TM) de 64 bits (compilação 20.1-b02-383, modo misto)

    Object string = "abcd";
    int i = 0;
    while (i < 12289) {
        i++;
        try {
            Integer a = (Integer) string;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Para esse fragmento específico de código, 12288 iterações (+ frequência?) Parecem ser o limite em que a JVM decidiu usar a exceção pré-alocada ...

Benoît Guérout
fonte
10

exception.toString não fornece o StackTrace, ele apenas retorna

uma breve descrição deste jogável. O resultado é a concatenação de:

* the name of the class of this object
* ": " (a colon and a space)
* the result of invoking this object's getLocalizedMessage() method

Use em exception.printStackTracevez disso para gerar o StackTrace.

Peter Lang
fonte
Desculpe, eu errei no meu post original. Estou registrando isso no Log4J, que usa printStackTrace ().
Edward Shtern
1
Você já tentou usar getStackTrace()para garantir que o problema não está no seu criador de logs?
Peter Lang
1
Se você estiver usando log4j, certifique-se de enviar a exceção como parte do argumento para o método de log. Vou postar uma resposta com isso.
Ravi Wallau
@raviaw point válido! @ Edward Shtern: você pode confirmar que definitivamente está usando a forma 2-arg do método log4j? Sei que você mencionou em uma resposta mais adiante que é a política da empresa fazê-lo, mas você tem certeza absoluta de que, neste caso, está seguindo a política?
precisa saber é o seguinte
Pode ser um tiro no escuro, mas é possível que a exceção tenha origem em algum código de terceiros? Talvez seja um invólucro de exceção (mal gravado), cujo toString () simplesmente retorna o nome da classe da exceção empacotada e que falha ao fornecer o rastreamento de pilha subjacente. Tente colocar algo como logger.info ("Classe de exceção =" + exc.class.getCanonicalName ()) em seu bloco catch e veja o que você recebe.
22410 KarstenF
4

Sugestão alternativa - se você estiver usando o Eclipse, poderá definir um ponto de interrupção no próprio NullPointerException (na perspectiva Debug, vá para a guia "Pontos de interrupção" e clique no pequeno ícone que possui um!)

Marque as opções "capturadas" e "não capturadas" - agora, quando você acionar o NPE, você imediatamente interromperá o processo e poderá ver como exatamente ele é tratado e por que não está recebendo um rastreamento de pilha.

Steven Schlansker
fonte
1

toString()retorna apenas o nome da exceção e a mensagem opcional. Eu sugeriria ligar

exception.printStackTrace()

para despejar a mensagem ou se você precisar dos detalhes sangrentos:

 StackTraceElement[] trace = exception.getStackTrace()
Sheldon Young
fonte
Veja acima - estou com erro de ortografia - estou usando printStackTrace ().
Edward Shtern
1

(Sua pergunta ainda não está clara se o seu código está chamando printStackTrace()ou se está sendo feito por um manipulador de log.)

Aqui estão algumas explicações possíveis sobre o que pode estar acontecendo:

  • O criador de logs / manipulador que está sendo usado foi configurado para emitir apenas a sequência de mensagens da exceção, não um rastreamento de pilha completo.

  • Seu aplicativo (ou alguma biblioteca de terceiros) está registrando a exceção usando, LOG.error(ex);em vez da forma de 2 argumentos (por exemplo), o método log4j Logger.

  • A mensagem vem de um lugar diferente de onde você pensa que é; por exemplo, está realmente chegando a algum método de biblioteca de terceiros ou a algumas coisas aleatórias que sobraram de tentativas anteriores de depuração.

  • A exceção que está sendo registrada sobrecarregou alguns métodos para ocultar o rastreamento de pilha. Se for esse o caso, a exceção não será uma NullPointerException genuína, mas haverá algum subtipo personalizado do NPE ou mesmo uma exceção não conectada.

Eu acho que a última explicação possível é bastante improvável, mas as pessoas pelo menos consideram fazer esse tipo de coisa para "impedir" a engenharia reversa. Obviamente, ele realmente consegue dificultar a vida de desenvolvedores honestos.

Stephen C
fonte
1

Quando você está usando o AspectJ em seu projeto, pode ser que algum aspecto oculte sua parte do rastreamento da pilha. Por exemplo, hoje eu tive:

java.lang.NullPointerException:
  at com.company.product.MyTest.test(MyTest.java:37)

Esse rastreamento de pilha foi impresso ao executar o teste através do Maven.

Por outro lado, ao executar o teste no IntelliJ, um rastreamento de pilha diferente foi impresso:

java.lang.NullPointerException
  at com.company.product.library.ArgumentChecker.nonNull(ArgumentChecker.java:67)
  at ...
  at com.company.product.aspects.CheckArgumentsAspect.wrap(CheckArgumentsAspect.java:82)
  at ...
  at com.company.product.MyTest.test(MyTest.java:37)
Roland Illig
fonte
0

Isso produzirá a exceção, use apenas para depurar, você deve lidar melhor com as exceções.

import java.io.PrintWriter;
import java.io.StringWriter;
    public static String getStackTrace(Throwable t)
    {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw, true);
        t.printStackTrace(pw);
        pw.flush();
        sw.flush();
        return sw.toString();
    }
Michael D. Irizarry
fonte