Что такое трассировка стека, и как я могу использовать ее для отладки ошибок моего приложения?

Иногда, когда я запускаю свое приложение, он дает мне ошибку, которая выглядит так:

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

  • Определите причину исключения и исправьте его, чтобы он вообще не генерировал исключение.
  • Если 1. невозможно, поймайте конкретное исключение и обработайте его.

    • Никогда не добавляйте try/catch, а затем просто игнорируйте исключение! Не делай этого!
    • Никогда не используйте catch (Exception e), всегда ловите определенные исключения. Это избавит вас от многих головных болей.

Ответ 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 щелкните вкладку Консоль (обычно внизу). Если вид консоли не отображается, перейдите в пункт меню "Окно" → "Показать представление" и выберите "Консоль".

Затем в окне консоли нажмите следующую кнопку (справа)

Consoles button

а затем выберите Консоль трассировки стека Java из раскрывающегося списка.

Вставьте трассировку стека в консоль. Затем он предоставит список ссылок в исходный код и любой другой исходный код.

Это то, что вы можете увидеть (изображение из документации Eclipse):

Diagram from Eclipse documentation

Самый последний вызов метода - это верхняя часть стека, которая является верхней строкой (исключая текст сообщения). Спуск по стопе идет назад во времени. Вторая строка - это метод, который вызывает первую строку и т.д.

Если вы используете программное обеспечение с открытым исходным кодом, вам может потребоваться загрузить и прикрепить к проекту источники, если вы хотите их изучить. Загрузите исходные банки в своем проекте, откройте папку "Связанные библиотеки", чтобы найти свою банку для своего модуля с открытым исходным кодом (тот, который имеет файлы классов), затем щелкните правой кнопкой мыши, выберите "Свойства" и прикрепите исходную банку.