Was ist ein Stack-Trace und wie kann ich damit meine Anwendungsfehler debuggen?
Wenn ich meine Anwendung ausführe, wird manchmal ein Fehler angezeigt, 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 eine Stapelverfolgung? Was kann es mir über den Fehler in meinem Programm sagen?
Zu dieser Frage - ziemlich oft sehe ich eine Frage, bei der ein unerfahrener Programmierer "einen Fehler bekommt", und sie fügen einfach ihre Stapelverfolgung und einen zufälligen Codeblock ein, ohne zu verstehen, was die Stapelverfolgung ist oder wie sie ihn verwenden können. Diese Frage ist als Referenz für unerfahrene Programmierer gedacht, die möglicherweise Hilfe zum Verständnis des Werts einer Stapelverfolgung benötigen.
Antworten
In einfachen Worten ist ein Stack-Trace eine Liste der Methodenaufrufe, bei denen sich die Anwendung in der Mitte befand, als eine Ausnahme ausgelöst wurde.
Einfaches Beispiel
Anhand des Beispiels in der Frage können wir genau bestimmen, wo die Ausnahme in der Anwendung ausgelöst wurde. Werfen wir einen Blick auf die Stapelverfolgung:
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 feststellen, wo unser Fehler aufgetreten 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 die folgende Book.java
Zeile öffnen und betrachten 16
:
15 public String getTitle() {
16 System.out.println(title.toString());
17 return title;
18 }
Dies würde darauf hinweisen, dass sich (wahrscheinlich title
) etwas null
im obigen Code befindet.
Beispiel mit einer Kette von Ausnahmen
Manchmal fangen Anwendungen eine Ausnahme ab und werfen sie erneut als Ursache für eine andere Ausnahme. Dies sieht normalerweise 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 }
Dadurch erhalten Sie möglicherweise eine Stapelverfolgung, 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 anders ist, ist das "Verursacht von". Manchmal haben Ausnahmen mehrere Abschnitte "Verursacht durch". Für diese möchten Sie normalerweise die "Grundursache" finden, die einer der niedrigsten Abschnitte "Verursacht durch" in der Stapelverfolgung ist. In unserem Fall ist es:
Caused by: java.lang.NullPointerException <-- root cause
at com.example.myproject.Book.getId(Book.java:22) <-- important line
Auch mit dieser Ausnahme würden wir auf Linie aussehen wollen 22
von Book.java
sehen , was die verursachen könnten NullPointerException
hier.
Entmutigenderes Beispiel mit Bibliothekscode
Normalerweise sind Stapelspuren viel komplexer als die beiden obigen Beispiele. Hier ist ein Beispiel (es ist lang, zeigt aber mehrere Ebenen verketteter 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 noch viel mehr. Was uns am meisten beschäftigt, ist die Suche nach Methoden, die aus unserem Code stammen und alles im com.example.myproject
Paket enthalten sind. Im zweiten Beispiel (oben) möchten wir zunächst nach der Grundursache suchen:
Caused by: java.sql.SQLException
Alle Methodenaufrufe darunter sind jedoch Bibliothekscode. Wir gehen also zu "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 uns das MyEntityService.java
online ansehen 59
, da dort dieser Fehler entstanden ist (dies ist ein bisschen offensichtlich, was schief gelaufen ist, da die SQLException den Fehler angibt, aber das Debugging-Verfahren ist das, wonach wir suchen).
Ich poste diese Antwort, damit die oberste Antwort (nach Aktivität sortiert) nicht einfach falsch ist.
Was ist ein Stacktrace?
Ein Stacktrace ist ein sehr hilfreiches Debugging-Tool. Es zeigt den Aufrufstapel (dh den Stapel von Funktionen, die bis zu diesem Zeitpunkt aufgerufen wurden) zum Zeitpunkt des Auslösens einer nicht erfassten Ausnahme (oder zum Zeitpunkt des manuellen Generierens des Stacktraces). Dies ist sehr nützlich, da es Ihnen nicht nur zeigt, 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 das, was die Laufzeitumgebung verwendet, um Ihnen mitzuteilen, dass ein Fehler aufgetreten ist. Beliebte Beispiele sind NullPointerException, IndexOutOfBoundsException oder ArithmeticException. Jedes dieser Probleme tritt auf, wenn Sie versuchen, etwas zu tun, das nicht möglich ist. Beispielsweise wird eine NullPointerException ausgelöst, wenn Sie versuchen, ein Null-Objekt 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 soll ich mit Stacktraces / Ausnahmen umgehen?
Finden Sie zunächst heraus, was die Ausnahme verursacht. Versuchen Sie, den Namen der Ausnahme zu 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 NullPointerException-Beispiel können Sie also sicherstellen, dass a
es zu diesem Zeitpunkt niemals null ist. Sie können beispielsweise a
einen Scheck wie diesen initialisieren oder einschließen:
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 beispielsweise eine Netzwerkverbindung in Ihrem Programm verwenden, können Sie den Computer nicht daran hindern, die Internetverbindung zu verlieren (z. B. können Sie den Benutzer nicht daran hindern, die Netzwerkverbindung des Computers zu trennen). In diesem Fall wird die Netzwerkbibliothek wahrscheinlich eine Ausnahme auslösen. Jetzt sollten Sie die Ausnahme abfangen und behandeln . Dies bedeutet, dass Sie im Beispiel mit der Netzwerkverbindung versuchen sollten, die Verbindung erneut zu öffnen oder den Benutzer oder ähnliches zu benachrichtigen. Wenn Sie catch verwenden, fangen Sie immer nur die Ausnahme ab, die Sie abfangen möchten. Verwenden Sie keine breiten catch-Anweisungencatch (Exception e)
, die alle Ausnahmen abfangen würden. Dies ist sehr wichtig, da Sie sonst möglicherweise versehentlich die falsche Ausnahme abfangen und falsch reagieren.
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 nicht verwenden 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, das ArithmeticException
durch eine mögliche Division durch 0 verursachte zu fangen. Er fängt aber auch ein mögliches NullPointerException
, das geworfen wird, wenn a
oder b
sind null
. Dies bedeutet, dass Sie möglicherweise eine erhalten NullPointerException
, diese jedoch als ArithmeticException behandeln und wahrscheinlich das Falsche tun. Im besten Fall vermissen Sie immer noch, dass es eine NullPointerException gab. Solche Dinge erschweren das Debuggen erheblich. Tun Sie das also nicht.
TLDR
- Finden Sie heraus, was die Ursache für die Ausnahme ist, und beheben Sie sie, damit die Ausnahme überhaupt nicht ausgelöst wird.
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 einfach die Ausnahme! Tu das nicht!
- Niemals verwenden
catch (Exception e)
, immer bestimmte Ausnahmen abfangen. Das erspart Ihnen viele Kopfschmerzen.
Um das zu ergänzen, was Rob erwähnt hat. Das Festlegen von Haltepunkten in Ihrer Anwendung ermöglicht die schrittweise Verarbeitung des Stapels. Auf diese Weise kann der Entwickler mithilfe des Debuggers feststellen, an welchem genauen Punkt die Methode etwas Unerwartetes ausführt.
Da Rob die NullPointerException
(NPE) verwendet hat, um etwas Gemeinsames zu veranschaulichen, können wir helfen, dieses Problem auf folgende Weise zu beheben:
Wenn wir eine Methode haben, die Parameter wie: void (String firstName)
In unserem Code möchten wir auswerten, firstName
der einen Wert enthält. Wir würden dies folgendermaßen tun:if(firstName == null || firstName.equals("")) return;
Das Obige verhindert, dass wir firstName
einen unsicheren Parameter verwenden. Daher können wir durch Nullprüfungen vor der Verarbeitung 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, in diesem Fall Hund, und gehen dann den Baum der Möglichkeiten entlang, um sicherzustellen, dass vor der Verarbeitung alles gültig ist. Wenn die Reihenfolge umgekehrt würde, könnte möglicherweise eine NPE ausgelöst werden und unser Programm würde abstürzen.
Es gibt eine weitere Funktion von Stacktrace Throwable Familie angeboten - die Möglichkeit zu manipulieren Stack Trace - Informationen.
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();
}
}
Stapelspur:
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();
}
}
Stapelspur:
Exception in thread "main" java.lang.RuntimeException
at OtherClass.methodX(String.java:99)
at OtherClass.methodY(String.java:55)
So verstehen Sie den Namen : Eine Stapelverfolgung ist eine Liste von Ausnahmen (oder Sie können eine Liste von "Ursache durch" sagen), von der oberflächlichsten Ausnahme (z. B. Service Layer-Ausnahme) bis zur tiefsten Ausnahme (z. B. Datenbankausnahme). Genau wie der Grund, warum wir es "Stapel" nennen, ist, dass der Stapel "First in Last out" (FILO) ist, die tiefste Ausnahme am Anfang aufgetreten ist, dann wurde eine Ausnahmekette mit einer Reihe von Konsequenzen generiert, die Oberflächenausnahme war die letzte eines ist rechtzeitig passiert, aber wir sehen es an erster Stelle.
Schlüssel 1 : Eine schwierige und wichtige Sache, die hier verstanden werden muss, ist: Die tiefste Ursache ist möglicherweise nicht die "Grundursache", denn wenn Sie einen "schlechten Code" schreiben, kann dies zu einer Ausnahme führen, die tiefer liegt als die Ebene. Beispielsweise kann eine fehlerhafte SQL-Abfrage dazu führen, dass die SQLServerException-Verbindung im unteren Bereich anstelle eines Syndax-Fehlers zurückgesetzt wird, der sich möglicherweise genau in der Mitte des Stapels befindet.
-> Finden Sie die Grundursache in der Mitte ist Ihre Aufgabe.
Schlüssel 2 : Eine weitere knifflige, aber wichtige Sache befindet sich in jedem "Cause by" -Block. Die erste Zeile war die tiefste Ebene und hat für diesen Block den ersten Platz belegt. Beispielsweise,
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)
Book.java:16 wurde von Auther.java:25 aufgerufen, was von Bootstrap.java:14 aufgerufen wurde. Book.java:16 war die Hauptursache. Fügen Sie hier ein Diagramm hinzu, das den Trace-Stapel in chronologischer Reihenfolge sortiert.
Nur um die anderen Beispiele zu ergänzen, gibt es innere (verschachtelte) Klassen , die mit dem $
Zeichen angezeigt werden. 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 zu dieser Stapelverfolgung führen:
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)
Die anderen Beiträge beschreiben, was ein Stack-Trace ist, aber es kann immer noch schwierig sein, damit zu arbeiten.
Wenn Sie eine Stapelverfolgung erhalten und die Ursache der Ausnahme verfolgen möchten, ist die Verwendung der Java-Stapelverfolgungskonsole in Eclipse ein guter Ausgangspunkt für das Verständnis . 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 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, gehen Sie zur Menüoption Fenster -> Ansicht anzeigen und wählen Sie Konsole .
Klicken Sie dann im Konsolenfenster auf die folgende Schaltfläche (rechts).
Wählen Sie dann Java Stack Trace Console aus der Dropdown-Liste aus.
Fügen Sie Ihren Stack-Trace in die Konsole ein. Anschließend wird eine Liste mit Links zu Ihrem Quellcode und allen anderen verfügbaren Quellcodes angezeigt.
Folgendes sehen Sie möglicherweise (Bild aus der Eclipse-Dokumentation):
Der jüngste gemacht Methodenaufruf wird die seine Oberseite des Stapels, die die obere Zeile ist ( mit Ausnahme des Meldungstextes). Den Stapel runter zu gehen geht in der Zeit zurück. 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 sie untersuchen möchten. Laden Sie die Quellgläser herunter. Öffnen Sie in Ihrem Projekt den Ordner " Referenzierte Bibliotheken ", um Ihr Glas für Ihr Open-Source-Modul (das mit den Klassendateien) zu finden. Klicken Sie dann mit der rechten Maustaste, wählen Sie Eigenschaften und hängen Sie das Quellglas an.