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.


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


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 .


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?


+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.


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 ...


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.


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.


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()

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.


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)

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();
    }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.