java löschen - Was ist ein Stack-Trace und wie kann ich ihn zum Debuggen von Anwendungsfehlern verwenden?



ausgeben print (7)

Manchmal, wenn ich meine Anwendung starte, gibt es einen Fehler, der wie folgt aussieht:

Exception in thread "main" java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.java:16)
        at com.example.myproject.Author.getBookTitles(Author.java:25)
        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

Die Leute haben dies als "Stack-Trace" bezeichnet. Was ist ein Stack-Trace? Was kann ich über den Fehler in meinem Programm wissen?

Über diese Frage - ziemlich oft sehe ich eine Frage, wo ein unerfahrener Programmierer "einen Fehler bekommt", und sie fügen einfach ihren Stack-Trace und irgendeinen zufälligen Code-Block ein, ohne zu verstehen, was der Stack-Trace ist oder wie er ihn benutzen kann. Diese Frage dient als Referenz für Anfänger-Programmierer, die Hilfe benötigen, um den Wert eines Stack-Trace zu verstehen.


Answers

Um den anderen Beispielen hinzuzufügen, gibt es innere (verschachtelte) Klassen , die mit dem $ -Zeichen erscheinen. Beispielsweise:

public class Test {

    private static void privateMethod() {
        throw new RuntimeException();
    }

    public static void main(String[] args) throws Exception {
        Runnable runnable = new Runnable() {
            @Override public void run() {
                privateMethod();
            }
        };
        runnable.run();
    }
}

Wird in diesem Stack-Trace resultieren:

Exception in thread "main" java.lang.RuntimeException
        at Test.privateMethod(Test.java:4)
        at Test.access$000(Test.java:1)
        at Test$1.run(Test.java:10)
        at Test.main(Test.java:13)

Es gibt eine weitere Stacktrace-Funktion, die von Throwable family angeboten wird - die Möglichkeit, Stack-Trace-Informationen zu manipulieren .

Standardverhalten:

package test.stack.trace;

public class SomeClass {

    public void methodA() {
        methodB();
    }

    public void methodB() {
        methodC();
    }

    public void methodC() {
        throw new RuntimeException();
    }

    public static void main(String[] args) {
        new SomeClass().methodA();
    }
}

Stapelverfolgung:

Exception in thread "main" java.lang.RuntimeException
    at test.stack.trace.SomeClass.methodC(SomeClass.java:18)
    at test.stack.trace.SomeClass.methodB(SomeClass.java:13)
    at test.stack.trace.SomeClass.methodA(SomeClass.java:9)
    at test.stack.trace.SomeClass.main(SomeClass.java:27)

Manipulierte Stapelverfolgung:

package test.stack.trace;

public class SomeClass {

    ...

    public void methodC() {
        RuntimeException e = new RuntimeException();
        e.setStackTrace(new StackTraceElement[]{
                new StackTraceElement("OtherClass", "methodX", "String.java", 99),
                new StackTraceElement("OtherClass", "methodY", "String.java", 55)
        });
        throw e;
    }

    public static void main(String[] args) {
        new SomeClass().methodA();
    }
}

Stapelverfolgung:

Exception in thread "main" java.lang.RuntimeException
    at OtherClass.methodX(String.java:99)
    at OtherClass.methodY(String.java:55)

Um das hinzuzufügen, was Rob erwähnt hat. Wenn Sie in Ihrer Anwendung Breakpoints setzen, können Sie den Stack Schritt für Schritt bearbeiten. Dies ermöglicht dem Entwickler, den Debugger zu verwenden, um zu sehen, an welchem ​​Punkt die Methode etwas unternimmt, das nicht vorhergesehen wurde.

Da Rob die NullPointerException (NPE) verwendet hat, um etwas NullPointerException zu veranschaulichen, können wir helfen, dieses Problem auf folgende Weise zu NullPointerException :

Wenn wir eine Methode haben, die Parameter wie void (String firstName) : void (String firstName)

In unserem Code möchten wir auswerten, dass firstName einen Wert enthält, wir würden das folgendermaßen machen: if(firstName == null || firstName.equals("")) return;

Das obige verhindert, dass firstName als unsicherer Parameter verwendet wird. Indem wir vor der Verarbeitung Null-Checks durchführen, können wir sicherstellen, dass unser Code ordnungsgemäß ausgeführt wird. Um ein Beispiel zu erweitern, das ein Objekt mit Methoden verwendet, können wir hier nachsehen:

if(dog == null || dog.firstName == null) return;

Das obige ist die richtige Reihenfolge, um nach Nullen zu suchen, wir beginnen mit dem Basisobjekt, dem Hund in diesem Fall, und beginnen dann, den Baum der Möglichkeiten hinunterzugehen, um sicherzustellen, dass alles gültig ist, bevor es verarbeitet wird. Wenn die Reihenfolge umgekehrt würde, könnte möglicherweise eine NPE ausgelöst werden und unser Programm würde abstürzen.


Vereinfacht ausgedrückt, ist eine Stack-Ablaufverfolgung eine Liste der Methodenaufrufe, in denen sich die Anwendung gerade befand, als eine Ausnahme ausgelöst wurde.

Einfaches Beispiel

Mit dem Beispiel in der Frage können wir genau bestimmen, wo die Ausnahme in der Anwendung geworfen wurde. Werfen wir einen Blick auf die Stack-Trace:

Exception in thread "main" java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.java:16)
        at com.example.myproject.Author.getBookTitles(Author.java:25)
        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

Dies ist eine sehr einfache Stapelverfolgung. Wenn wir am Anfang der Liste von "at ..." beginnen, können wir sagen, wo unser Fehler passiert ist. Was wir suchen, ist der oberste Methodenaufruf, der Teil unserer Anwendung ist. In diesem Fall ist es:

at com.example.myproject.Book.getTitle(Book.java:16)

Um dies zu debuggen, können wir Book.java öffnen und Zeile 16 , die wie folgt aussieht:

15   public String getTitle() {
16      System.out.println(title.toString());
17      return title;
18   }

Dies würde anzeigen, dass etwas (wahrscheinlich title ) im obigen Code null ist.

Beispiel mit einer Kette von Ausnahmen

Manchmal fangen Anwendungen eine Exception ab und werfen sie erneut als Ursache für eine andere Exception. Das sieht typischerweise so aus:

34   public void getBookIds(int id) {
35      try {
36         book.getId(id);    // this method it throws a NullPointerException on line 22
37      } catch (NullPointerException e) {
38         throw new IllegalStateException("A book has a null property", e)
39      }
40   }

Dies könnte Ihnen eine Stack-Ablaufverfolgung geben, die wie folgt aussieht:

Exception in thread "main" java.lang.IllegalStateException: A book has a null property
        at com.example.myproject.Author.getBookIds(Author.java:38)
        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)
Caused by: java.lang.NullPointerException
        at com.example.myproject.Book.getId(Book.java:22)
        at com.example.myproject.Author.getBookIds(Author.java:36)
        ... 1 more

Was an diesem einen Unterschied ist, ist das "verursacht durch". Manchmal haben Ausnahmen mehrere Abschnitte "verursacht durch". Für diese möchten Sie normalerweise die "Ursache" finden, die eine der niedrigsten "Caused by" -Abschnitten in der Stack-Ablaufverfolgung sein wird. In unserem Fall ist es:

Caused by: java.lang.NullPointerException <-- root cause
        at com.example.myproject.Book.getId(Book.java:22) <-- important line

Mit dieser Ausnahme möchten wir uns erneut Zeile 22 von Book.java zu sehen, was hier die NullPointerException verursachen könnte.

Mehr entmutigendes Beispiel mit Bibliothekscode

Normalerweise sind Stack-Traces viel komplexer als die beiden obigen Beispiele. Hier ist ein Beispiel (es ist ein langer, aber zeigt mehrere Ebenen von verketteten Ausnahmen):

javax.servlet.ServletException: Something bad happened
    at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:60)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at com.example.myproject.ExceptionHandlerFilter.doFilter(ExceptionHandlerFilter.java:28)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at com.example.myproject.OutputBufferFilter.doFilter(OutputBufferFilter.java:33)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    at org.mortbay.jetty.Server.handle(Server.java:326)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
    at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:943)
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:756)
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
    at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.java:228)
    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
Caused by: com.example.myproject.MyProjectServletException
    at com.example.myproject.MyServlet.doPost(MyServlet.java:169)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
    at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166)
    at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:30)
    ... 27 more
Caused by: org.hibernate.exception.ConstraintViolationException: could not insert: [com.example.myproject.MyEntity]
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96)
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:64)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2329)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2822)
    at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:71)
    at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268)
    at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:321)
    at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)
    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
    at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:56)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
    at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:50)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
    at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:705)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:693)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:689)
    at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:344)
    at $Proxy19.save(Unknown Source)
    at com.example.myproject.MyEntityService.save(MyEntityService.java:59) <-- relevant call (see notes below)
    at com.example.myproject.MyServlet.doPost(MyServlet.java:164)
    ... 32 more
Caused by: java.sql.SQLException: Violation of unique constraint MY_ENTITY_UK_1: duplicate value(s) for column(s) MY_COLUMN in statement [...]
    at org.hsqldb.jdbc.Util.throwError(Unknown Source)
    at org.hsqldb.jdbc.jdbcPreparedStatement.executeUpdate(Unknown Source)
    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105)
    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:57)
    ... 54 more

In diesem Beispiel gibt es viel mehr. Worüber wir hauptsächlich besorgt sind, ist die Suche nach Methoden, die aus unserem Code stammen , was alles im Paket com.example.myproject . Aus dem zweiten Beispiel (oben) möchten wir zuerst nach der Ursache suchen:

Caused by: java.sql.SQLException

Alle Methodenaufrufe darunter sind jedoch Bibliothekscode. Wir gehen also zum "Caused by" darüber und suchen nach dem ersten Methodenaufruf, der aus unserem Code stammt.

at com.example.myproject.MyEntityService.save(MyEntityService.java:59)

Wie in den vorherigen Beispielen sollten wir MyEntityService.java in Zeile 59 , da hier der Fehler entstanden ist (dieser ist ein bisschen offensichtlich, was schief gelaufen ist, da die SQLException den Fehler angibt, aber das Debugging-Verfahren ist das, wonach wir suchen) .


Die anderen Beiträge beschreiben, was ein Stack-Trace ist, aber es kann immer noch schwierig sein, damit zu arbeiten.

Wenn Sie eine Stack-Ablaufverfolgung erhalten und die Ursache der Ausnahme verfolgen möchten, sollten Sie die Java-Stack-Trace-Konsole in Eclipse verwenden . Wenn Sie eine andere IDE verwenden, gibt es möglicherweise eine ähnliche Funktion, aber diese Antwort bezieht sich auf Eclipse.

Stellen Sie zunächst sicher, dass alle Ihre Java-Quellen in einem Eclipse-Projekt verfügbar sind.

Klicken Sie dann in der Java- Perspektive auf die Registerkarte Konsole (normalerweise unten). Wenn die Konsolenansicht nicht sichtbar ist, wechseln Sie zur Menüoption Fenster -> Ansicht anzeigen und wählen Sie Konsole .

Klicken Sie dann im Konsolenfenster auf den folgenden Button (rechts)

und wählen Sie Java Stack Trace Console aus der Dropdown-Liste.

Fügen Sie Ihren Stack-Trace in die Konsole ein. Es wird dann eine Liste von Links in Ihren Quellcode und anderen verfügbaren Quellcode zur Verfügung stellen.

Dies können Sie sehen (Bild aus der Eclipse-Dokumentation):

Der zuletzt durchgeführte Methodenaufruf ist der Anfang des Stapels, dh die oberste Zeile (ohne den Nachrichtentext). Den Stapel hinunter gehen geht zurück in der Zeit. Die zweite Zeile ist die Methode, die die erste Zeile usw. aufruft.

Wenn Sie Open-Source-Software verwenden, müssen Sie möglicherweise die Quellen herunterladen und an Ihr Projekt anhängen, wenn Sie dies untersuchen möchten. Laden Sie die Quell-Jars herunter, öffnen Sie in Ihrem Projekt den Ordner Referenced Libraries , um Ihr jar für Ihr Open-Source-Modul (das mit den Klassendateien) zu finden, klicken Sie mit der rechten Maustaste, wählen Sie Eigenschaften und hängen Sie das Quelljar an.


Ich schreibe diese Antwort so, dass die oberste Antwort (wenn nach Aktivität sortiert) nicht einfach falsch ist.

Was ist ein Stacktrace?

Ein Stacktrace ist ein sehr hilfreiches Debugging-Tool. Es zeigt den Aufruf-Stack (dh den Stapel von Funktionen, die bis zu diesem Zeitpunkt aufgerufen wurden) zum Zeitpunkt, zu dem eine nicht abgefangene Ausnahme ausgelöst wurde (oder die Zeit, zu der der Stacktrace manuell generiert wurde). Dies ist sehr nützlich, da es nicht nur anzeigt, wo der Fehler aufgetreten ist, sondern auch, wie das Programm an dieser Stelle des Codes gelandet ist. Dies führt zur nächsten Frage:

Was ist eine Ausnahme?

Eine Ausnahme ist, was die Laufzeitumgebung verwendet, um Ihnen mitzuteilen, dass ein Fehler aufgetreten ist. Populäre Beispiele sind NullPointerException, IndexOutOfBoundsException oder ArithmeticException. Jedes von diesen wird verursacht, wenn Sie versuchen, etwas zu tun, das nicht möglich ist. Zum Beispiel wird eine NullPointerException ausgelöst, wenn Sie versuchen, ein Null-Objekt zu dereferenzieren:

Object a = null;
a.toString();                 //this line throws a NullPointerException

Object[] b = new Object[5];
System.out.println(b[10]);    //this line throws an IndexOutOfBoundsException,
                              //because b is only 5 elements long
int ia = 5;
int ib = 0;
ia = ia/ib;                   //this line throws an  ArithmeticException with the 
                              //message "/ by 0", because you are trying to
                              //divide by 0, which is not possible.

Wie sollte ich mit Stacktraces / Exceptions umgehen?

Finde zuerst heraus, was die Ausnahme verursacht. Versuchen Sie, den Namen der Ausnahme googeln, um herauszufinden, was die Ursache für diese Ausnahme ist. Meistens wird es durch falschen Code verursacht. In den obigen Beispielen werden alle Ausnahmen durch falschen Code verursacht. Für das Beispiel NullPointerException können Sie also sicherstellen, dass a zu diesem Zeitpunkt nie null ist. Sie könnten beispielsweise a solchen Scheck initialisieren oder hinzufügen:

if (a!=null) {
    a.toString();
}

Auf diese Weise wird die fehlerhafte Zeile nicht ausgeführt, wenn a==null . Gleiches gilt für die anderen Beispiele.

Manchmal können Sie nicht sicherstellen, dass Sie keine Ausnahme erhalten. Wenn Sie z. B. in Ihrem Programm eine Netzwerkverbindung verwenden, können Sie nicht verhindern, dass der Computer die Internetverbindung verliert (z. B. können Sie den Benutzer nicht davon abhalten, die Netzwerkverbindung des Computers zu trennen). In diesem Fall wird die Netzwerkbibliothek wahrscheinlich eine Ausnahme auslösen. Jetzt sollten Sie die Ausnahme fangen und damit umgehen . Dies bedeutet, dass Sie im Beispiel mit der Netzwerkverbindung versuchen sollten, die Verbindung erneut zu öffnen oder den Benutzer oder etwas Ähnliches zu benachrichtigen. Wenn Sie catch verwenden, fangen Sie immer nur die Ausnahme ab, die Sie abfangen möchten. Verwenden Sie keine allgemeinen catch-Anweisungen wie catch (Exception e) , die alle Ausnahmen abfangen. Dies ist sehr wichtig, da Sie sonst versehentlich die falsche Ausnahme abfangen und falsch reagieren könnten.

try {
    Socket x = new Socket("1.1.1.1", 6789);
    x.getInputStream().read()
} catch (IOException e) {
    System.err.println("Connection could not be established, please try again later!")
}

Warum sollte ich keinen catch (Exception e) ?

Lassen Sie uns anhand eines kleinen Beispiels zeigen, warum Sie nicht alle Ausnahmen abfangen sollten:

int mult(Integer a,Integer b) {
    try {
        int result = a/b
        return result;
    } catch (Exception e) {
        System.err.println("Error: Division by zero!");
        return 0;
    }
}

Dieser Code versucht, die ArithmeticException die durch eine mögliche Division durch 0 verursacht wird, NullPointerException Er fängt aber auch eine mögliche NullPointerException , die ausgelöst wird, wenn a oder b null . Dies bedeutet, dass Sie möglicherweise eine NullPointerException , die Sie jedoch als ArithmeticException behandeln und wahrscheinlich das Falsche tun. Im besten Fall vermisst man immer noch, dass es eine NullPointerException gab. So etwas macht Debugging viel schwieriger, also tu das nicht.

TLDR

  1. Finden Sie heraus, was die Ursache der Ausnahme ist, und beheben Sie sie, sodass die Ausnahme überhaupt nicht ausgelöst wird.
  2. Wenn 1. nicht möglich ist, fangen Sie die spezifische Ausnahme ab und behandeln Sie sie.

    • Fügen Sie niemals nur einen Versuch / Fang hinzu und ignorieren Sie dann die Ausnahme! Tu das nicht!
    • Verwenden Sie niemals catch (Exception e) , fangen Sie immer bestimmte Exceptions ein. Das wird dir viele Kopfschmerzen ersparen.

Dies ist der beste Weg, den ich gefunden habe:

  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>2.4</version>
    <configuration>
      <archive>
        <manifest>
        <addClasspath>true</addClasspath>
        <mainClass>com.myDomain.etc.MainClassName</mainClass>
        <classpathPrefix>dependency-jars/</classpathPrefix>
        </manifest>
      </archive>
    </configuration>
  </plugin>
  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>2.5.1</version>
    <executions>
      <execution>
        <id>copy-dependencies</id>
        <phase>package</phase>
        <goals>
            <goal>copy-dependencies</goal>
        </goals>
        <configuration>
            <outputDirectory>
               ${project.build.directory}/dependency-jars/
            </outputDirectory>
        </configuration>
      </execution>
    </executions>
  </plugin>

Bei dieser Konfiguration befinden sich alle Abhängigkeiten in /dependency-jars. Meine Anwendung hat keine MainKlasse, nur Kontext-Klassen, aber eine meiner Abhängigkeiten enthält eine Mainclass ( com.myDomain.etc.MainClassName), die den JMX-Server startet und einen startoder einen stopParameter erhält . So konnte ich meine Bewerbung so starten:

java -jar ./lib/TestApp-1.0-SNAPSHOT.jar start

Ich warte, dass es für euch alle nützlich ist.





java debugging stack-trace