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

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

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 ]

550 голосов
/ 21 октября 2010

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

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

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

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 ...», мы можем сказать, где произошла наша ошибка.То, что мы ищем - это вызов метода topmost , который является частью нашего приложения.В данном случае это:

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 on line 59, потому что именно здесь возникла эта ошибка (эта ошибка немного очевидна, что пошло не так, поскольку SQLException сообщает об ошибке, но мы ищем процедуру отладки).

73 голосов
/ 14 октября 2015

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

Что такое Stacktrace?

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

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

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

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

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

TLDR

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

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

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

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

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

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

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

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

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

15 голосов
/ 16 сентября 2014

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

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

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

-> Найдите основную причину в центре вашей работы. enter image description here

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

8 голосов
/ 19 апреля 2016

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

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)
5 голосов
/ 12 марта 2015

В других статьях описывается, что такое трассировка стека, но с ней все еще сложно работать.

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

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

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

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

Consoles button

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

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

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

Diagram from Eclipse documentation

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

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

...