NullPointerException in Java senza StackTrace


333

Ho avuto istanze del nostro codice Java catturare a NullPointerException, ma quando provo a registrare StackTrace (che sostanzialmente finisce per chiamare Throwable.printStackTrace()), tutto ciò che ottengo è:

java.lang.NullPointerException

Qualcun altro l'ha trovato? Ho provato a cercare su google "traccia java null pointer stack vuoto" ma non ho trovato nulla del genere.


Qual è il contesto? Sono coinvolti più thread? Ho avuto problemi nel tentativo di ottenere la traccia dello stack di un'eccezione in SwingWorker.
Michael Myers

Nessun threading coinvolto qui, solo semplicemente vecchio Java.
Edward Shtern,

1
@Bozho - no - non so ancora come riprodurre NullPointer.
Edward Shtern,


Maggiori informazioni su -XX:-OmitStackTraceInFastThrowin dup: stackoverflow.com/questions/4659151/…
Vadzim

Risposte:


407

Probabilmente stai utilizzando HotSpot JVM (originariamente di Sun Microsystems, successivamente acquistato da Oracle, parte di OpenJDK), che esegue molte ottimizzazioni. Per recuperare le tracce dello stack, è necessario passare l'opzione -XX:-OmitStackTraceInFastThrowalla JVM.

L'ottimizzazione è che quando si verifica un'eccezione (in genere NullPointerException) per la prima volta, viene stampata la traccia dello stack completo e JVM ricorda la traccia dello stack (o forse solo la posizione del codice). Quando tale eccezione si verifica abbastanza spesso, la traccia dello stack non viene più stampata, sia per ottenere prestazioni migliori che per non inondare il registro con tracce di stack identiche.

Per vedere come questo è implementato nella HotSpot JVM, prendine una copia e cerca la variabile globale OmitStackTraceInFastThrow. L'ultima volta che ho visto il codice (nel 2019), era nel file graphKit.cpp .


1
Grazie per il consiglio. Qualche idea se ci sono dei nascondigli nascosti per passare questa opzione (sembra abbastanza innocuo fintanto che la mia applicazione non genera un sacco di eccezioni)?
Edward Shtern,

Non ci sono gotcha nascosti che io conosca. Quando guardi il codice sorgente di Hotspot, puoi vedere che questa opzione è usata solo in un posto (graphKit.cpp). E questo mi sembra perfetto.
Roland Illig,

34
Ho pensato di aggiungere le informazioni aggiuntive che quando la traccia dello stack viene ottimizzata, è perché è stata completamente gestita almeno una volta: jawspeak.com/2010/05/26/…
sharakan

1
Sto eseguendo una JVM OpenJDK, versione 1.8.0u171 (Debian 9), e sembra accettare anche la -XX:-OmitStackTraceInFastThrowbandiera. Devo ancora confermare se questo era il motivo per cui non riuscivo anche a stampare tracce dello stack (ad esempio, usando e.printStackTrace), ma sembra molto probabile. Ho ampliato la risposta per riflettere questa scoperta.
Chris W.

Nel nostro caso, le prime 125 eccezioni avevano una traccia dello stack, quindi le altre 3 rotazioni dei file di registro non ne avevano nessuna. Questa risposta è stata molto utile per trovare il colpevole.
sukhmel,

61

Come hai detto in un commento, stai usando log4j. Ho scoperto (inavvertitamente) un posto dove avevo scritto

LOG.error(exc);

invece del tipico

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

per pigrizia o forse solo per non pensarci. La parte sfortunata di questo è che non si comporta come previsto. L'API del logger accetta effettivamente Object come primo argomento, non una stringa, quindi chiama toString () sull'argomento. Quindi, invece di ottenere la bella traccia dello stack, stampa semplicemente il toString, che nel caso di NPE è piuttosto inutile.

Forse questo è quello che stai vivendo?


+1: Questo spiegherebbe il comportamento descritto, e non sei il solo a scoprirlo :)
Peter Lang

4
In realtà abbiamo una politica standard di non usare mai il primo modulo sopra (LOG.error (exc);) - usiamo sempre la firma con 2 parametri in modo da aggiungere qualche dichiarazione descrittiva ai log invece che solo uno stacktrace grezzo.
Edward Shtern,

5
Certo, ma la politica non significa che sia sempre eseguita correttamente! Ho pensato che valesse la pena menzionarlo, almeno.
Steven Schlansker,

È vero, ma in questo caso è stato ;-)
Edward Shtern

28

Abbiamo visto questo stesso comportamento in passato. Si è scoperto che, per qualche pazza ragione, se una NullPointerException si fosse verificata più volte nello stesso punto del codice, dopo un po 'l'utilizzo Log.error(String, Throwable)avrebbe smesso di includere tracce dello stack completo.

Prova a guardare più indietro nel tuo registro. Potresti trovare il colpevole.

EDIT: questo bug sembra rilevante, ma è stato corretto molto tempo fa, probabilmente non è la causa.


2
Il bug è chiuso, ma il flag -XX: -OmitStackTraceInFastThrow è ancora necessario per aggirare l'ottimizzazione delle prestazioni.
Joshua Goldberg,

Ho visto questo recentemente molto. Qualche indizio su cosa potrebbe causare questo o su come risolverlo? Il sistema di registrazione potrebbe essere attivo da giorni e la causa reale è stata eliminata, non importa la noiosa ricerca ...
Pawel Veselov,

5
Pawel, hai provato la -XX:-OmitStackTraceInFastThrowbandiera JVM suggerita da Joshua? Vedi anche stackoverflow.com/a/2070568/6198 .
Matt Solnit,

1
Questo è stato per noi. Grazie.
Andrew Cheong,

20

Ecco una spiegazione: l' hotspot ha causato delle eccezioni nel perdere le tracce dello stack durante la produzione e la correzione

L'ho provato su Mac OS X

  • versione java "1.6.0_26"
  • Java (TM) SE Runtime Environment (build 1.6.0_26-b03-383-11A511)
  • Macchina virtuale Java HotSpot (TM) a 64 bit (build 20.1-b02-383, modalità mista)

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

Per questo specifico frammento di codice, 12288 iterazioni (+ frequenza?) Sembra essere il limite in cui JVM ha deciso di utilizzare l'eccezione preallocata ...


10

exception.toString non ti dà StackTrace, restituisce solo

una breve descrizione di questo lancio. Il risultato è la concatenazione di:

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

Utilizzare exception.printStackTraceinvece per generare StackTrace.


Mi dispiace, ho sbagliato a scrivere nel mio post originale. Sto registrando questi tramite Log4J, che utilizza printStackTrace ().
Edward Shtern,

1
Hai provato a utilizzare getStackTrace()per assicurarti che il problema non riguardi il tuo logger?
Peter Lang

1
Se si utilizza log4j, assicurarsi di inviare l'eccezione come parte dell'argomento al metodo di registro. Invierò una risposta con quello.
Ravi Wallau,

@raviaw punto valido! @Edward Shtern: puoi confermare che stai sicuramente usando la forma 2-arg del metodo log4j? So che hai menzionato in una risposta più in basso che è la politica aziendale a farlo, ma sei ASSOLUTAMENTE sicuro che in questo caso stai seguendo la politica?
KarstenF

Potrebbe essere un colpo lungo, ma è possibile che l'eccezione abbia origine in un codice di terze parti? Forse è un wrapper di eccezioni (scritto male), il cui toString () restituisce semplicemente il nome della classe dell'eccezione di wrapping e che non riesce a fornire la traccia dello stack sottostante. Prova a inserire qualcosa come logger.info ("Exception class =" + exc.class.getCanonicalName ()) nel tuo blocco catch e vedi cosa ottieni.
KarstenF,

4

Suggerimento alternativo: se si utilizza Eclipse, è possibile impostare un punto di interruzione su NullPointerException stesso (nella prospettiva Debug, andare alla scheda "Punti di interruzione" e fare clic sulla piccola icona che contiene un!)

Controlla entrambe le opzioni "catturati" e "non rilevati": ora quando si attiva l'NPE, si romperà immediatamente il punto di interruzione e sarà quindi possibile scorrere e vedere come viene gestito esattamente e perché non si ottiene una traccia dello stack.


1

toString()restituisce solo il nome dell'eccezione e il messaggio opzionale. Suggerirei di chiamare

exception.printStackTrace()

per scaricare il messaggio o se hai bisogno dei dettagli gory:

 StackTraceElement[] trace = exception.getStackTrace()

Vedi sopra - Mi sono sbagliato - Sto usando printStackTrace ().
Edward Shtern,

1

(La tua domanda non è ancora chiara se il tuo codice sta chiamando printStackTrace()o se questo viene fatto da un gestore di registrazione.)

Ecco alcune possibili spiegazioni su ciò che potrebbe accadere:

  • Il logger / gestore utilizzato è stato configurato per generare solo la stringa di messaggi dell'eccezione, non una traccia dello stack completo.

  • L'applicazione (o una libreria di terze parti) sta registrando l'eccezione utilizzando LOG.error(ex);anziché la forma a 2 argomenti (ad esempio) del metodo log4j Logger.

  • Il messaggio proviene da un posto diverso da dove pensi che sia; ad esempio, sta effettivamente arrivando un metodo di libreria di terze parti o alcune cose casuali lasciate da precedenti tentativi di debug.

  • L'eccezione che viene registrata ha sovraccaricato alcuni metodi per oscurare lo stacktrace. In tal caso, l'eccezione non sarà una vera NullPointerException, ma sarà un sottotipo personalizzato di NPE o anche qualche eccezione non connessa.

Penso che l'ultima possibile spiegazione sia abbastanza improbabile, ma le persone almeno contemplano di fare questo tipo di cose per "prevenire" il reverse engineering. Ovviamente riesce davvero solo a rendere la vita difficile agli sviluppatori onesti.


1

Quando si utilizza AspectJ nel progetto, è possibile che alcuni aspetti nascondano la parte della traccia dello stack. Ad esempio, oggi ho avuto:

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

Questa traccia dello stack è stata stampata durante l'esecuzione del test tramite infallibile Maven.

D'altra parte, durante l'esecuzione del test in IntelliJ, è stata stampata una traccia stack diversa:

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

Questo produrrà l'eccezione, usare solo per il debug dovresti gestire meglio le tue eccezioni.

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();
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.