Che cos'è un'analisi dello stack e come posso utilizzarla per eseguire il debug degli errori dell'applicazione?
A volte quando eseguo la mia applicazione mi dà un errore che assomiglia a:
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)
La gente l'ha definita "traccia dello stack". Cos'è una traccia dello stack? Cosa può dirmi dell'errore che sta accadendo nel mio programma?
Riguardo a questa domanda - molto spesso vedo sorgere una domanda in cui un programmatore inesperto sta "ricevendo un errore", e semplicemente incolla la traccia dello stack e un blocco casuale di codice senza capire cosa sia la traccia dello stack o come utilizzarla. Questa domanda è intesa come riferimento per i programmatori inesperti che potrebbero aver bisogno di aiuto per comprendere il valore di un'analisi dello stack.
Risposte
In termini semplici, un'analisi dello stack è un elenco delle chiamate al metodo che l'applicazione era nel mezzo quando è stata generata un'eccezione.
Semplice esempio
Con l'esempio fornito nella domanda, possiamo determinare esattamente dove è stata generata l'eccezione nell'applicazione. Diamo uno sguardo alla traccia dello stack:
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)
Questa è una traccia dello stack molto semplice. Se iniziamo dall'inizio della lista di "a ...", possiamo dire dove si è verificato il nostro errore. Quello che stiamo cercando è la chiamata al metodo più in alto che fa parte della nostra applicazione. In questo caso, è:
at com.example.myproject.Book.getTitle(Book.java:16)
Per eseguire il debug di questo, possiamo aprire Book.java
e guardare la riga 16
, che è:
15 public String getTitle() {
16 System.out.println(title.toString());
17 return title;
18 }
Ciò indicherebbe che qualcosa (probabilmente title
) è null
nel codice sopra.
Esempio con una catena di eccezioni
A volte le applicazioni rilevano un'eccezione e la rilanciano come causa di un'altra eccezione. Questo in genere assomiglia a:
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 }
Questo potrebbe darti una traccia dello stack simile a:
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
Ciò che è diverso in questo è il "Caused by". A volte le eccezioni avranno più sezioni "Caused by". Per questi, in genere si desidera trovare la "causa principale", che sarà una delle sezioni "Caused by" più basse nell'analisi dello stack. Nel nostro caso, è:
Caused by: java.lang.NullPointerException <-- root cause
at com.example.myproject.Book.getId(Book.java:22) <-- important line
Di nuovo, con questa eccezione vorremmo guardare la riga 22
di Book.java
per vedere cosa potrebbe causare il NullPointerException
qui.
Esempio più scoraggiante con il codice della libreria
Di solito le tracce dello stack sono molto più complesse dei due esempi precedenti. Ecco un esempio (è lungo, ma mostra diversi livelli di eccezioni concatenate):
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 questo esempio, c'è molto di più. Ciò di cui siamo maggiormente preoccupati è cercare metodi che provengono dal nostro codice , che sarebbe qualsiasi cosa nel com.example.myproject
pacchetto. Dal secondo esempio (sopra), vorremmo prima cercare la causa principale, che è:
Caused by: java.sql.SQLException
Tuttavia, tutte le chiamate al metodo sotto quelle sono codice libreria. Quindi passeremo al "Caused by" sopra di esso e cercheremo la prima chiamata al metodo proveniente dal nostro codice, che è:
at com.example.myproject.MyEntityService.save(MyEntityService.java:59)
Come negli esempi precedenti, dovremmo guardare in MyEntityService.java
linea 59
, perché è lì che ha avuto origine questo errore (questo è un po 'ovvio cosa è andato storto, poiché SQLException indica l'errore, ma la procedura di debug è ciò che stiamo cercando).
Sto postando questa risposta in modo che la risposta più in alto (se ordinata per attività) non sia semplicemente sbagliata.
Cos'è uno Stacktrace?
Uno stacktrace è uno strumento di debug molto utile. Mostra lo stack di chiamate (ovvero, lo stack di funzioni che sono state chiamate fino a quel punto) nel momento in cui è stata lanciata un'eccezione non rilevata (o l'ora in cui lo stacktrace è stato generato manualmente). Questo è molto utile perché non solo mostra dove si è verificato l'errore, ma anche come è finito il programma in quel punto del codice. Questo porta alla prossima domanda:
Cos'è un'eccezione?
Un'eccezione è ciò che l'ambiente di runtime utilizza per segnalare che si è verificato un errore. Esempi popolari sono NullPointerException, IndexOutOfBoundsException o ArithmeticException. Ognuno di questi si verifica quando si tenta di fare qualcosa che non è possibile. Ad esempio, verrà generata un'eccezione NullPointerException quando si tenta di dereferenziare un oggetto Null:
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.
Come devo gestire Stacktraces / Exceptions?
In un primo momento, scopri cosa sta causando l'eccezione. Prova a cercare su Google il nome dell'eccezione per scoprire qual è la causa di tale eccezione. Il più delle volte sarà causato da un codice errato. Negli esempi riportati sopra, tutte le eccezioni sono causate da un codice errato. Quindi, per l'esempio NullPointerException, puoi assicurarti che a
non sia mai nullo in quel momento. Potresti, ad esempio, inizializzare a
o includere un controllo come questo:
if (a!=null) {
a.toString();
}
In questo modo, la linea incriminata non viene eseguita se a==null
. Lo stesso vale per gli altri esempi.
A volte non puoi assicurarti di non ottenere un'eccezione. Ad esempio, se si utilizza una connessione di rete nel programma, non è possibile impedire al computer di perdere la connessione a Internet (ad esempio, non è possibile impedire all'utente di disconnettere la connessione di rete del computer). In questo caso la libreria di rete genererà probabilmente un'eccezione. Ora dovresti catturare l'eccezione e gestirla . Ciò significa che, nell'esempio con la connessione di rete, dovresti provare a riaprire la connessione o avvisare l'utente o qualcosa del genere. Inoltre, ogni volta che si utilizza catch, intercettare sempre solo l'eccezione che si desidera catturare, non utilizzare istruzioni catch generiche comecatch (Exception e)
queste catturerebbero tutte le eccezioni. Questo è molto importante, perché altrimenti potresti accidentalmente rilevare l'eccezione sbagliata e reagire nel modo sbagliato.
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!")
}
Perché non dovrei usare catch (Exception e)
?
Facciamo un piccolo esempio per mostrare perché non dovresti semplicemente catturare tutte le eccezioni:
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;
}
}
Ciò che questo codice sta cercando di fare è catturare la ArithmeticException
causa di una possibile divisione per 0. Ma cattura anche un possibile NullPointerException
che viene lanciato se a
o b
sono null
. Ciò significa che potresti ottenere un NullPointerException
ma lo tratterai come un'eccezione ArithmeticException e probabilmente farai la cosa sbagliata. Nel migliore dei casi ti manca ancora che ci fosse una NullPointerException. Cose del genere rendono il debug molto più difficile, quindi non farlo.
TLDR
- Scopri qual è la causa dell'eccezione e risolvila, in modo che non generi affatto l'eccezione.
Se 1. non è possibile, catturare l'eccezione specifica e gestirla.
- Non aggiungere mai solo un tentativo / cattura e quindi ignorare l'eccezione! Non farlo!
- Non usare mai
catch (Exception e)
, cattura sempre eccezioni specifiche. Questo ti farà risparmiare un sacco di mal di testa.
Per aggiungere a ciò che ha menzionato Rob. L'impostazione dei punti di interruzione nell'applicazione consente l'elaborazione graduale dello stack. Ciò consente allo sviluppatore di utilizzare il debugger per vedere in quale punto esatto il metodo sta facendo qualcosa che non era previsto.
Poiché Rob ha utilizzato NullPointerException
(NPE) per illustrare qualcosa di comune, possiamo aiutare a rimuovere questo problema nel modo seguente:
se abbiamo un metodo che accetta parametri come: void (String firstName)
Nel nostro codice vorremmo valutare che firstName
contiene un valore, lo faremmo in questo modo:if(firstName == null || firstName.equals("")) return;
Quanto sopra ci impedisce di utilizzare firstName
come parametro non sicuro. Pertanto, eseguendo controlli nulli prima dell'elaborazione possiamo aiutare a garantire che il nostro codice funzioni correttamente. Per espandere un esempio che utilizza un oggetto con metodi possiamo guardare qui:
if(dog == null || dog.firstName == null) return;
Quanto sopra è l'ordine corretto per verificare la presenza di null, iniziamo con l'oggetto di base, in questo caso cane, e poi iniziamo a camminare lungo l'albero delle possibilità per assicurarci che tutto sia valido prima dell'elaborazione. Se l'ordine fosse invertito, potrebbe essere lanciato un NPE e il nostro programma andrebbe in crash.
C'è un'altra funzionalità di stacktrace offerta dalla famiglia Throwable: la possibilità di manipolare le informazioni di stack trace.
Comportamento standard:
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();
}
}
Traccia dello stack:
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)
Traccia dello stack manipolata:
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();
}
}
Traccia dello stack:
Exception in thread "main" java.lang.RuntimeException
at OtherClass.methodX(String.java:99)
at OtherClass.methodY(String.java:55)
Per capire il nome : una traccia dello stack è un elenco di eccezioni (oppure si può dire un elenco di "Cause by"), dall'eccezione più superficiale (ad esempio, eccezione del livello di servizio) a quella più profonda (ad esempio, eccezione del database). Proprio come il motivo per cui lo chiamiamo 'stack' è perché lo stack è First in Last out (FILO), l'eccezione più profonda si è verificata all'inizio, poi è stata generata una catena di eccezioni una serie di conseguenze, l'eccezione di superficie è stata l'ultima uno è accaduto in tempo, ma lo vediamo in primo luogo.
Chiave 1 : Una cosa difficile e importante da capire qui è: la causa più profonda potrebbe non essere la "causa principale", perché se scrivi un "codice errato", potrebbe causare qualche eccezione al di sotto che è più profonda del suo livello. Ad esempio, una query SQL errata può causare la reimpostazione della connessione SQLServerException nel bottem invece dell'errore syndax, che potrebbe trovarsi solo nel mezzo dello stack.
-> Individuare la causa principale al centro è il tuo lavoro.
Chiave 2 : Un'altra cosa complicata ma importante è all'interno di ogni blocco "Causa per", la prima riga era lo strato più profondo e si trovava al primo posto per questo blocco. Per esempio,
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 è stato chiamato da Auther.java:25 che è stato chiamato da Bootstrap.java:14, Book.java:16 era la causa principale. Qui allega un diagramma che ordina lo stack delle tracce in ordine cronologico.
Solo per aggiungere agli altri esempi, ci sono classi interne (annidate) che appaiono con il $
segno. Per esempio:
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();
}
}
Risulterà in questa traccia dello stack:
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)
Gli altri post descrivono cos'è una traccia dello stack, ma può ancora essere difficile lavorarci.
Se si ottiene una traccia dello stack e si desidera risalire alla causa dell'eccezione, un buon punto di partenza per comprenderla è utilizzare Java Stack Trace Console in Eclipse . Se usi un altro IDE, potrebbe esserci una funzionalità simile, ma questa risposta riguarda Eclipse.
Innanzitutto, assicurati di avere tutte le tue fonti Java accessibili in un progetto Eclipse.
Quindi nella prospettiva Java , fare clic sulla scheda Console (di solito in basso). Se la vista Console non è visibile, vai all'opzione di menu Finestra -> Mostra vista e seleziona Console .
Quindi nella finestra della console, fare clic sul pulsante seguente (a destra)
e quindi selezionare Java Stack Trace Console dall'elenco a discesa.
Incolla la traccia dello stack nella console. Fornirà quindi un elenco di collegamenti al codice sorgente e a qualsiasi altro codice sorgente disponibile.
Questo è ciò che potresti vedere (immagine dalla documentazione di Eclipse):
La chiamata di metodo più recente effettuata sarà la parte superiore dello stack, che è la riga superiore (escluso il testo del messaggio). Scendendo lo stack si torna indietro nel tempo. La seconda riga è il metodo che chiama la prima riga, ecc.
Se stai usando software open source, potresti dover scaricare e allegare al tuo progetto i sorgenti se vuoi esaminarli. Scarica i jar sorgente, nel tuo progetto, apri la cartella Biblioteche referenziate per trovare il tuo jar per il tuo modulo open source (quello con i file di classe) quindi fai clic con il tasto destro, seleziona Proprietà e allega il jar sorgente.