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

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

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)

Люди называют это "следом стека". Что такое трассировка стека? Что он может сказать мне об ошибке, которая происходит в моей программе?


Об этом вопросе - довольно часто я вижу вопрос, когда начинающий программист "получает ошибку", и они просто вставляют свою трассировку стека и некоторый случайный блок кода, не понимая, что такое трассировка стека или как они могут ее использовать. Этот вопрос предназначен для начинающих программистов, которым может понадобиться помощь в понимании значения трассировки стека.

7 ответов

Решение

Проще говоря, трассировка стека - это список вызовов методов, которые приложение находилось в середине, когда было сгенерировано исключение.

Простой пример

С помощью примера, приведенного в вопросе, мы можем точно определить, где было выброшено исключение в приложении. Давайте посмотрим на трассировку стека:

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

То, что отличается от этого, это "Причины". Иногда исключения будут иметь несколько разделов "Причины". Для них обычно требуется найти "основную причину", которая будет одной из самых низких "причинных" секций в трассировке стека. В нашем случае это:

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 сообщает об ошибке, но мы ищем процедуру отладки).

Я публикую этот ответ, поэтому самый верхний ответ (при сортировке по активности) не является просто неправильным.

Что такое Stacktrace?

Stacktrace - очень полезный инструмент отладки. Он показывает стек вызовов (т. Е. Стек функций, которые были вызваны до этой точки) в момент возникновения неперехваченного исключения (или в момент, когда трассировка стека была сгенерирована вручную). Это очень полезно, поскольку показывает не только, где произошла ошибка, но и то, как программа оказалась в этом месте кода. Это приводит к следующему вопросу:

Что такое исключение?

Исключением является то, что среда выполнения использует, чтобы сообщить вам, что произошла ошибка. Популярные примеры: NullPointerException, IndexOutOfBoundsException или ArithmeticException. Каждый из них вызывается, когда вы пытаетесь сделать что-то, что невозможно. Например, исключение NullPointerException будет выдано при попытке разыменования объекта 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.

Как я должен иметь дело со стеками / исключениями?

Сначала выясните, что вызывает исключение. Попробуйте поискать название исключения, чтобы узнать причину этого исключения. В большинстве случаев это будет вызвано неправильным кодом. В приведенных выше примерах все исключения вызваны неправильным кодом. Так что для примера NullPointerException вы можете убедиться, что a никогда не будет нулевым в то время. Вы могли бы, например, инициализировать a или включите проверку как этот:

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

Таким образом, оскорбительная строка не выполняется, если a==null, То же самое касается других примеров.

Иногда вы не можете быть уверены, что не получите исключения. Например, если вы используете сетевое соединение в своей программе, вы не можете остановить компьютер от потери его интернет-соединения (например, вы не можете запретить пользователю отключать сетевое соединение компьютера). В этом случае сетевая библиотека, вероятно, выдаст исключение. Теперь вы должны поймать исключение и обработать его. Это означает, что в примере с сетевым подключением вы должны попытаться повторно открыть подключение или уведомить пользователя или что-то в этом роде. Кроме того, всякий раз, когда вы используете catch, всегда перехватываете только исключение, которое вы хотите перехватить, не используйте такие широкие операторы 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. Подобные вещи усложняют отладку, так что не делайте этого.

TLDR

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

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

Чтобы добавить к тому, что Роб упомянул. Установка точек останова в вашем приложении позволяет выполнять пошаговую обработку стека. Это позволяет разработчику использовать отладчик, чтобы увидеть, в какой момент метод делает что-то непредвиденное.

Так как Роб использовал NullPointerException (NPE), чтобы проиллюстрировать что-то общее, мы можем помочь устранить эту проблему следующим образом:

если у нас есть метод, который принимает параметры, такие как: void (String firstName)

В нашем коде мы хотели бы оценить это firstName содержит значение, мы бы сделали это так: if(firstName == null || firstName.equals("")) return;

Вышесказанное мешает нам использовать firstName как небезопасный параметр. Поэтому, делая нулевые проверки перед обработкой, мы можем гарантировать, что наш код будет работать правильно. Чтобы расширить пример, который использует объект с методами, мы можем посмотреть здесь:

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

Выше приведен правильный порядок проверки на нулевые значения, мы начинаем с базового объекта, в данном случае с собакой, а затем начинаем идти по дереву возможностей, чтобы убедиться, что все верно перед обработкой. Если бы порядок был отменен, NPE потенциально мог бы быть брошен, и наша программа потерпела бы крах.

Чтобы понять имя: трассировка стека - это список Исключений (или вы можете сказать список "Причины"), от самого поверхностного Исключения (например, Исключение сервисного уровня) до самого глубокого (например, Исключение базы данных). Точно так же, как причина, по которой мы называем это "стеком", заключается в том, что стек является первым в последнем выходе (FILO), самое глубокое исключение произошло в самом начале, затем была создана цепочка исключений с серией последствий, исключение поверхности было последним одно произошло во времени, но мы видим это в первую очередь.

Ключ 1: хитрая и важная вещь, которую необходимо здесь понять: самая глубокая причина не может быть "первопричиной", потому что, если вы пишете какой-то "плохой код", это может вызвать некое исключение, которое ниже, чем его уровень. Например, неверный SQL-запрос может привести к сбросу соединения SQLServerException в нижней части окна, а не к ошибке sinax, которая может быть только в середине стека.

-> Найти основную причину в середине ваша работа.

Ключ 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 был основной причиной. Здесь приложите диаграмму сортировки стека трасс в хронологическом порядке.

Существует еще одна функция трассировки стека, предлагаемая семейством 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)

Другие посты описывают, что такое трассировка стека, но с ней все еще сложно работать.

Если вы получаете трассировку стека и хотите отследить причину исключения, хорошей отправной точкой для понимания этого является использование консоли Java Stack Trace Console в Eclipse. Если вы используете другую IDE, может быть похожая функция, но этот ответ касается Eclipse.

Во-первых, убедитесь, что все ваши исходные коды Java доступны в проекте Eclipse.

Затем в перспективе Java нажмите на вкладку Консоль (обычно внизу). Если вид консоли не отображается, перейдите к пункту меню " Окно" -> "Показать вид" и выберите " Консоль".

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

Кнопка консоли

и затем выберите Java Stack Trace Console из выпадающего списка.

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

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

Диаграмма из документации по Eclipse

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

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

Просто чтобы добавить к другим примерам, есть внутренние (вложенные) классы, которые появляются с $ знак. Например:

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)
Другие вопросы по тегам