NullPointerException in Java ohne StackTrace

332

Ich habe Instanzen unseres Java-Codes abfangen lassen NullPointerException, aber wenn ich versuche, die StackTrace zu protokollieren (die im Grunde genommen aufruft Throwable.printStackTrace()), bekomme ich nur:

java.lang.NullPointerException

Ist noch jemand darauf gestoßen? Ich habe versucht, nach "Java Null Pointer Leerer Stack Trace" zu googeln, bin aber auf so etwas nicht gestoßen.

Edward Shtern
quelle
Was ist der Kontext? Sind mehrere Threads beteiligt? Ich hatte Probleme beim Versuch, die Stapelverfolgung einer Ausnahme in einem SwingWorker abzurufen.
Michael Myers
Hier ist kein Threading erforderlich, nur altes Java.
Edward Shtern
1
@Bozho - nein - ich bin mir noch nicht sicher, wie ich den NullPointer reproduzieren soll.
Edward Shtern
1
Verwandte: stackoverflow.com/questions/1076191/…
Joshua Goldberg
Weitere Informationen -XX:-OmitStackTraceInFastThrowim Dup: stackoverflow.com/questions/4659151/…
Vadzim

Antworten:

406

Sie verwenden wahrscheinlich die HotSpot-JVM (ursprünglich von Sun Microsystems, später von Oracle gekauft, Teil des OpenJDK), die viele Optimierungen durchführt. Um die Stack-Traces zurückzugewinnen, müssen Sie die Option -XX:-OmitStackTraceInFastThrowan die JVM übergeben.

Die Optimierung besteht darin, dass beim erstmaligen Auftreten einer Ausnahme (normalerweise einer NullPointerException) die vollständige Stapelverfolgung gedruckt wird und die JVM die Stapelverfolgung (oder möglicherweise nur den Speicherort des Codes) speichert. Wenn diese Ausnahme häufig genug auftritt, wird die Stapelverfolgung nicht mehr gedruckt, um eine bessere Leistung zu erzielen und das Protokoll nicht mit identischen Stapelverfolgungen zu überfluten.

Um zu sehen, wie dies in der HotSpot-JVM implementiert ist , nehmen Sie eine Kopie davon und suchen Sie nach der globalen Variablen OmitStackTraceInFastThrow. Das letzte Mal, als ich mir den Code angesehen habe (2019), war er in der Datei graphKit.cpp .

Roland Illig
quelle
1
Danke für den Tipp. Irgendeine Idee, ob es versteckte Fallstricke gibt, diese Option zu übergeben (es scheint ziemlich harmlos zu sein, solange meine Anwendung nicht viele Ausnahmen auslöst)?
Edward Shtern
Es gibt keine versteckten Fallstricke, von denen ich weiß. Wenn Sie sich den Hotspot-Quellcode ansehen, sehen Sie, dass diese Option nur an einer Stelle verwendet wird (graphKit.cpp). Und das sieht für mich gut aus.
Roland Illig
34
Ich dachte, ich würde die zusätzliche Information hinzufügen, dass, wenn der Stack-Trace wegoptimiert wird
sharakan
1
Ich verwende eine OpenJDK-JVM, Version 1.8.0u171 (Debian 9), und sie scheint auch das -XX:-OmitStackTraceInFastThrowFlag zu akzeptieren . Ich muss noch bestätigen, ob ich aus diesem Grund auch keine Stack-Traces gedruckt habe (z. B. mit e.printStackTrace), aber es scheint sehr wahrscheinlich. Ich habe die Antwort erweitert, um diese Entdeckung widerzuspiegeln.
Chris W.
In unserem Fall hatten die ersten 125 Ausnahmen eine Stapelverfolgung, und der Rest über 3 Umdrehungen von Protokolldateien hatte keine. Diese Antwort war sehr hilfreich, um den Schuldigen zu finden.
Sukhmel
61

Wie Sie in einem Kommentar erwähnt haben, verwenden Sie log4j. Ich entdeckte (versehentlich) einen Ort, an dem ich geschrieben hatte

LOG.error(exc);

anstelle des typischen

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

durch Faulheit oder vielleicht einfach nicht darüber nachdenken. Der unglückliche Teil davon ist, dass es sich nicht so verhält, wie Sie es erwarten. Die Logger-API verwendet Object als erstes Argument, nicht als Zeichenfolge, und ruft dann für das Argument toString () auf. Anstatt die schöne hübsche Stapelverfolgung zu erhalten, wird nur der toString ausgedruckt - was im Fall von NPE ziemlich nutzlos ist.

Vielleicht erleben Sie das gerade?

Steven Schlansker
quelle
+1: Dies würde das beschriebene Verhalten erklären, und Sie sind nicht der einzige, der dies entdeckt hat :)
Peter Lang
4
Wir haben tatsächlich die Standardrichtlinie, niemals das erste Formular oben zu verwenden (LOG.error (exc);) - wir verwenden immer die 2-Parameter-Signatur, damit wir den Protokollen eine beschreibende Anweisung hinzufügen, anstatt nur eine rohe Stapelverfolgung.
Edward Shtern
5
Sicher, aber Richtlinien bedeuten nicht, dass sie immer korrekt ausgeführt werden! Ich dachte, es wäre zumindest erwähnenswert.
Steven Schlansker
Stimmt, aber in diesem Fall war es ;-)
Edward Shtern
28

Wir haben das gleiche Verhalten in der Vergangenheit gesehen. Es stellte sich heraus, dass aus irgendeinem verrückten Grund, wenn eine NullPointerException mehrmals an derselben Stelle im Code auftrat, nach einer Weile die Verwendung Log.error(String, Throwable)der vollständigen Stapelspuren eingestellt wurde.

Versuchen Sie, in Ihrem Protokoll weiter zurückzublicken. Sie können den Täter finden.

BEARBEITEN: Dieser Fehler klingt relevant, wurde aber vor so langer Zeit behoben, dass er wahrscheinlich nicht die Ursache ist.

Matt Solnit
quelle
2
Der Fehler ist geschlossen, aber das Flag -XX: -OmitStackTraceInFastThrow wird weiterhin benötigt, um die Leistungsoptimierung zu umgehen.
Joshua Goldberg
Ich habe das in letzter Zeit viel gesehen. Gibt es Hinweise darauf, was dies verursachen könnte oder wie es behoben werden kann? Das Protokollierungssystem ist möglicherweise seit Tagen in Betrieb, und die eigentliche Ursache hat sich herausgestellt, ohne Rücksicht auf die mühsame Suche ...
Pawel Veselov
5
Pawel, hast du die -XX:-OmitStackTraceInFastThrowvon Joshua vorgeschlagene JVM-Flagge ausprobiert ? Siehe auch stackoverflow.com/a/2070568/6198 .
Matt Solnit
1
Das war es für uns. Vielen Dank.
Andrew Cheong
20

Hier ist eine Erklärung: Hotspot hat dazu geführt, dass Ausnahmen ihre Stapelspuren in der Produktion verloren haben - und das Update

Ich habe es unter Mac OS X getestet

  • Java-Version "1.6.0_26"
  • Java (TM) SE-Laufzeitumgebung (Build 1.6.0_26-b03-383-11A511)
  • 64-Bit-Server-VM von Java HotSpot (TM) (Build 20.1-b02-383, gemischter Modus)

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

Für dieses spezifische Codefragment scheinen 12288 Iterationen (+ Häufigkeit?) Die Grenze zu sein, an der JVM beschlossen hat, eine vorab zugewiesene Ausnahme zu verwenden ...

Benoît Guérout
quelle
10

exception.toString gibt Ihnen nicht die StackTrace, sondern gibt nur zurück

eine kurze Beschreibung dieses Wurfs. Das Ergebnis ist die Verkettung von:

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

Verwenden Sie exception.printStackTracestattdessen, um StackTrace auszugeben.

Peter Lang
quelle
Entschuldigung, ich habe in meinem ursprünglichen Beitrag falsch geschrieben. Ich protokolliere diese über Log4J, das printStackTrace () verwendet.
Edward Shtern
1
Haben Sie versucht getStackTrace(), sicherzustellen, dass das Problem nicht bei Ihrem Logger liegt?
Peter Lang
1
Wenn Sie log4j verwenden, müssen Sie die Ausnahme als Teil des Arguments an die Protokollmethode senden. Ich werde damit eine Antwort posten.
Ravi Wallau
@raviaw gültiger Punkt! @ Edward Shtern: Können Sie bestätigen, dass Sie definitiv die 2-Argumente-Form der log4j-Methode verwenden? Ich weiß, dass Sie in einer Antwort weiter unten erwähnt haben, dass dies die Unternehmensrichtlinie ist, aber sind Sie ABSOLUT sicher, dass Sie in diesem Fall die Richtlinie befolgen?
KarstenF
Es mag ein langer Weg sein, aber ist es möglich, dass die Ausnahme von einem Code eines Drittanbieters stammt? Möglicherweise handelt es sich um einen (schlecht geschriebenen) Ausnahme-Wrapper, dessen toString () einfach den Klassennamen der umschlossenen Ausnahme zurückgibt und der den zugrunde liegenden Stack-Trace nicht bereitstellt. Fügen Sie etwas wie logger.info ("Exception class =" + exc.class.getCanonicalName ()) in Ihren catch-Block ein und sehen Sie, was Sie erhalten.
KarstenF
4

Alternativer Vorschlag - Wenn Sie Eclipse verwenden, können Sie einen Haltepunkt für NullPointerException selbst festlegen (gehen Sie in der Debug-Perspektive zur Registerkarte "Haltepunkte" und klicken Sie auf das kleine Symbol mit einem!).

Aktivieren Sie sowohl die Optionen "Abgefangen" als auch "Nicht erfasst". Wenn Sie jetzt die NPE auslösen, erhalten Sie sofort einen Haltepunkt. Anschließend können Sie durchgehen und sehen, wie genau damit umgegangen wird und warum Sie keine Stapelverfolgung erhalten.

Steven Schlansker
quelle
1

toString()Gibt nur den Ausnahmenamen und die optionale Nachricht zurück. Ich würde vorschlagen anzurufen

exception.printStackTrace()

um die Nachricht zu sichern, oder wenn Sie die blutigen Details benötigen:

 StackTraceElement[] trace = exception.getStackTrace()
Sheldon Young
quelle
Siehe oben - Ich habe falsch geschrieben - Ich verwende printStackTrace ().
Edward Shtern
1

(Ihre Frage ist immer noch unklar, ob Ihr Code aufgerufen wird printStackTrace()oder ob dies von einem Protokollierungshandler durchgeführt wird.)

Hier sind einige mögliche Erklärungen darüber, was passieren könnte:

  • Der verwendete Logger / Handler wurde so konfiguriert, dass nur die Nachrichtenzeichenfolge der Ausnahme ausgegeben wird, keine vollständige Stapelverfolgung.

  • Ihre Anwendung (oder eine Bibliothek eines Drittanbieters) protokolliert die Ausnahme mithilfe LOG.error(ex);der 2-Argument-Form (zum Beispiel) der log4j-Logger-Methode.

  • Die Nachricht kommt von einem anderen Ort, als Sie denken. Zum Beispiel handelt es sich tatsächlich um eine Bibliotheksmethode eines Drittanbieters oder um zufällige Dinge, die von früheren Debugversuchen übrig geblieben sind.

  • Die Ausnahme, die protokolliert wird, hat einige Methoden überladen, um die Stapelverfolgung zu verdecken. In diesem Fall handelt es sich bei der Ausnahme nicht um eine echte NullPointerException, sondern um einen benutzerdefinierten Subtyp von NPE oder sogar um eine nicht verbundene Ausnahme.

Ich denke, dass die letztmögliche Erklärung ziemlich unwahrscheinlich ist, aber die Leute denken zumindest darüber nach, so etwas zu tun, um Reverse Engineering zu "verhindern". Natürlich gelingt es nur wirklich, ehrlichen Entwicklern das Leben schwer zu machen.

Stephen C.
quelle
1

Wenn Sie AspectJ in Ihrem Projekt verwenden, kann es vorkommen, dass ein Aspekt seinen Teil der Stapelverfolgung verbirgt. Zum Beispiel hatte ich heute:

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

Diese Stapelverfolgung wurde gedruckt, als der Test über Mavens todsicheres Feuer ausgeführt wurde.

Andererseits wurde beim Ausführen des Tests in IntelliJ eine andere Stapelverfolgung gedruckt:

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
quelle
0

Dadurch wird die Ausnahme ausgegeben. Verwenden Sie sie nur zum Debuggen. Sie sollten Ihre Ausnahmen besser behandeln.

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
quelle