Что такое трассировка стека, и как я могу использовать ее для отладки ошибок моего приложения?
Иногда, когда я запускаю свое приложение, он дает мне ошибку, которая выглядит так:
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)
Люди называют это "трассировкой стека". Что такое трассировка стека? Что это может сказать мне об ошибке, которая происходит в моей программе?
Об этом вопросе - довольно часто я вижу, что вопрос возникает, когда начинающий программист "получает ошибку", и они просто вставляют трассировку стека и некоторый случайный блок кода, не понимая, что такое трассировка стека и как они могут используй это. Этот вопрос предназначен как справочник для начинающих программистов, которым может понадобиться помощь в понимании ценности трассировки стека.
Ответы
Ответ 1
Говоря простыми словами, трассировка стека представляет собой список вызовов методов, которые приложение находилось в середине, когда было выбрано исключение.
Простой пример
В примере, приведенном в вопросе, мы можем точно определить, где исключение было брошено в приложении. Посмотрим на трассировку стека:
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)
Это очень простая трассировка стека. Если мы начнем с начала списка "at...", мы можем определить, где произошла наша ошибка. Нам нужен метод самый верхний метод, который является частью нашего приложения. В этом случае это:
at com.example.myproject.Book.getTitle(Book.java:16)
Чтобы отладить это, мы можем открыть Book.java
и посмотреть строку 16
, которая:
15 public String getTitle() {
16 System.out.println(title.toString());
17 return title;
18 }
Это указывает на то, что в приведенном выше коде что-то (возможно, title
) null
.
Пример с цепочкой исключений
Иногда приложения захватывают Исключение и повторно бросают его как причину другого Исключения. Обычно это выглядит так:
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 }
Это может дать вам трассировку стека, которая выглядит так:
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
В чем отличие от этого - "Причиненный". Иногда исключения будут иметь несколько разделов "Causeed by". Для этого вам обычно требуется найти "первопричину", которая будет одной из самых низких разделов "Причиненные" в трассировке стека. В нашем случае это:
Caused by: java.lang.NullPointerException <-- root cause
at com.example.myproject.Book.getId(Book.java:22) <-- important line
Опять же, с этим исключением мы хотели бы посмотреть строку 22
Book.java
, чтобы увидеть, что может вызвать NullPointerException
здесь.
Более сложный пример с библиотечным кодом
Обычно трассировки стека намного сложнее, чем два приведенных выше примера. Вот пример (он длинный, но демонстрирует несколько уровней цепочки исключений):
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
В этом примере есть намного больше. В основном нас беспокоит поиск методов, которые из нашего кода, которые будут в пакете com.example.myproject
. Из второго примера (см. Выше) мы сначала хотели бы посмотреть на основную причину, которая:
Caused by: java.sql.SQLException
Однако все вызовы метода под этим являются библиотечным кодом. Поэтому мы перейдем к "Причиненному" над ним и ищем первый вызов метода, исходящий из нашего кода, который:
at com.example.myproject.MyEntityService.save(MyEntityService.java:59)
Как и в предыдущих примерах, мы должны посмотреть на MyEntityService.java
на строке 59
, потому что там, где возникла эта ошибка (это немного очевидно, что пошло не так, поскольку SQLException указывает на ошибку, но процедура отладки - это то, что мы после).
Ответ 2
Я отправляю этот ответ, поэтому самый верхний ответ (при сортировке по активности) не является тем, что просто неправильно.
Что такое Stacktrace?
Элемент stacktrace - очень полезный инструмент для отладки. Он показывает стек вызовов (значение, стек функций, которые были вызваны до этой точки) в то время, когда было выбрано неперехваченное исключение (или время, когда стек была создана вручную). Это очень полезно, потому что оно не только показывает вам, где произошла ошибка, но также и то, как программа оказалась в этом месте кода.
Это приводит к следующему вопросу:
Что такое исключение?
Исключение - это то, что среда выполнения использует, чтобы сообщить вам, что произошла ошибка. Популярные примеры: NullPointerException, IndexOutOfBoundsException или ArithmeticException. Каждый из них вызывается, когда вы пытаетесь сделать что-то, что невозможно. Например, при попытке разыменовать Null-объект будет выбрано исключение NullPointerException:
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.
Как мне использовать Stacktraces/Exceptions?
Сначала выясните, что вызывает исключение. Попробуйте googleing имя исключения, чтобы узнать, в чем причина этого исключения. В большинстве случаев это будет вызвано неправильным кодом. В приведенных выше примерах все исключения вызваны неправильным кодом. Таким образом, для примера NullPointerException вы можете убедиться, что a
никогда не является нулевым в то время. Например, вы можете инициализировать a
или включить проверку, подобную этой:
if (a!=null) {
a.toString();
}
Таким образом, строка нарушения не выполняется, если a==null
. То же самое касается других примеров.
Иногда вы не можете убедиться, что вы не получите исключения. Например, если вы используете сетевое соединение в своей программе, вы не можете остановить компьютер от потери его интернет-соединения (например, вы не можете отключить пользователя от отключения сетевого подключения к компьютеру). В этом случае сетевая библиотека, вероятно, выбросит исключение. Теперь вы должны поймать исключение и дескриптор. Это означает, что в примере с сетевым соединением вы должны попытаться снова открыть соединение или уведомить пользователя или что-то в этом роде. Кроме того, всякий раз, когда вы используете catch, всегда вылавливаете только исключение, которое вы хотите поймать, не используют широко распространенные выписки, такие как catch (Exception e)
, которые поймают все исключения. Это очень важно, потому что иначе вы случайно поймаете неправильное исключение и отреагируете не так.
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!")
}
Почему я не должен использовать catch (Exception e)
?
Используйте небольшой пример, чтобы показать, почему вы не должны просто перехватывать все исключения:
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;
}
}
Что этот код пытается сделать, так это поймать ArithmeticException
, вызванное возможным делением на 0. Но он также улавливает возможный NullPointerException
, который вызывается, если a
или b
являются null
. Это означает, что вы можете получить NullPointerException
, но вы будете рассматривать его как исключение из ArithmeticException и, вероятно, ошибаетесь. В лучшем случае вы все еще пропустите, что было исключение NullPointerException. Подобные вещи делают отладку намного сложнее, поэтому не делайте этого.
TL;DR
Ответ 3
Чтобы добавить к тому, что упомянул Роб. Настройка точек останова в вашем приложении позволяет поэтапно обрабатывать стек. Это позволяет разработчику использовать отладчик, чтобы увидеть, в какой точечной точке метод делает что-то непредвиденное.
Так как Rob использовал NullPointerException
(NPE), чтобы проиллюстрировать что-то общее, мы можем помочь устранить эту проблему следующим образом:
если у нас есть метод, который принимает такие параметры, как: void (String firstName)
В нашем коде мы хотели бы оценить, что firstName
содержит значение, мы сделали бы это следующим образом: if(firstName == null || firstName.equals("")) return;
Вышеупомянутое не позволяет использовать firstName
как небезопасный параметр. Поэтому, выполняя нулевые проверки перед обработкой, мы можем помочь обеспечить правильность работы нашего кода. Чтобы расширить пример, который использует объект с методами, мы можем посмотреть здесь:
if(dog == null || dog.firstName == null) return;
Вышеописанный порядок проверки нулей, в этом случае мы начинаем с базового объекта, собаки, а затем начинаем спускаться по дереву возможностей, чтобы убедиться, что все все правильно. Если бы порядок был отменен, NPE потенциально может быть выброшен, и наша программа потерпит крах.
Ответ 4
Есть еще одна функция stacktrace, предлагаемая семейством Throwable - возможность манипулировать информацией о трассировке стека.
Стандартное поведение:
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();
}
}
Трассировка стека:
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)
Манипулированная трассировка стека:
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();
}
}
Трассировка стека:
Exception in thread "main" java.lang.RuntimeException
at OtherClass.methodX(String.java:99)
at OtherClass.methodY(String.java:55)
Ответ 5
Чтобы понять имя: трассировка стека представляет собой список исключений (или вы можете сказать список "Причина из" ), начиная с самого поверхностного исключения (например, из-за исключения уровня обслуживания) до самый глубокий (например, исключение базы данных). Точно так же, как причина, которую мы называем "стеком", состоит в том, что стек - это First in Last out (FILO), самое глубокое исключение произошло в самом начале, затем цепочка исключений была сгенерирована серией последствий, поверхность Exception была последней один из них произошел вовремя, но мы видим это в первую очередь.
Ключ 1: сложная и важная вещь здесь должна быть понята: самая глубокая причина не может быть "первопричиной", потому что, если вы напишете "плохой код", это может привести к какое-то исключение, под которым глубже его слоя. Например, плохой sql-запрос может вызвать соединение SQLServerException reset в узле вместо синтаксовой ошибки, которая может находиться только в середине стека.
- > Найдите основную причину в середине - это ваша работа.
Ключ 2. Еще одна сложная, но важная вещь находится внутри каждого блока "Причина", первая строка - самый глубокий слой и занимает первое место для этого блока. Например,
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 был вызван Auther.java:25, который был вызван Bootstrap.java:14, Book.java:16 был основной причиной.
Здесь прикрепите диаграмму сортировки стека следов в хронологическом порядке.
Ответ 6
Чтобы добавить к другим примерам, есть внутренние (вложенные) классы, которые появляются с знаком $
. Например:
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();
}
}
Результат этой трассировки стека:
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)
Ответ 7
Другие сообщения описывают, что такое трассировка стека, но с ней все еще сложно работать.
Если вы получаете трассировку стека и хотите отслеживать причину исключения, хорошей отправной точкой в понимании этого является использование Консоли трассировки Java Stack в Eclipse. Если вы используете другую среду IDE, может быть аналогичная функция, но этот ответ касается Eclipse.
Во-первых, убедитесь, что все ваши источники Java доступны в проекте Eclipse.
Затем в перспективе Java щелкните вкладку Консоль (обычно внизу). Если вид консоли не отображается, перейдите в пункт меню "Окно" → "Показать представление" и выберите "Консоль".
Затем в окне консоли нажмите следующую кнопку (справа)
а затем выберите Консоль трассировки стека Java из раскрывающегося списка.
Вставьте трассировку стека в консоль. Затем он предоставит список ссылок в исходный код и любой другой исходный код.
Это то, что вы можете увидеть (изображение из документации Eclipse):
Самый последний вызов метода - это верхняя часть стека, которая является верхней строкой (исключая текст сообщения). Спуск по стопе идет назад во времени. Вторая строка - это метод, который вызывает первую строку и т.д.
Если вы используете программное обеспечение с открытым исходным кодом, вам может потребоваться загрузить и прикрепить к проекту источники, если вы хотите их изучить. Загрузите исходные банки в своем проекте, откройте папку "Связанные библиотеки", чтобы найти свою банку для своего модуля с открытым исходным кодом (тот, который имеет файлы классов), затем щелкните правой кнопкой мыши, выберите "Свойства" и прикрепите исходную банку.