Что такое стектрейс java
Перейти к содержимому

Что такое стектрейс java

Что такое стек (stacktrace)?

Stacktrace — очень полезный инструмент для отладки. Это список вызовов метода, в которых приложение было посередине, когда было выбрано исключение. Это очень полезно, потому что оно не только показывает вам, где произошла ошибка, но также и то, как программа оказалась в этом месте кода. Точно так же, как причина, которую мы называем «стеком», состоит в том, что стек — это First in Last out (FILO), самое глубокое исключение произошло в самом начале, затем цепочка исключений была сгенерирована серией последствий, поверхность Exception была последней один случился вовремя, но мы видим это в первую очередь. Но в большинстве случаев вы даже можете получить причину исключения из первых нескольких строк.

Исключения в Java

Исключение — это событие, которое происходит во время выполнения программы, что нарушает нормальный поток инструкций программы. Он обеспечивает способ передачи контроля с одной части программы на другую.

Исключение — это событие, которое происходит во время выполнения программы, что нарушает нормальный поток инструкций программы. Он обеспечивает способ передачи контроля с одной части программы на другую.

Stack Trace и с чем его едят

Java-университет

В этой статье вы узнаете и поймете, как работает такое явление в Java, как StackTrace, так же известное как «Трассировка стека вызовов». Эта информация была структурирована для новичков, столкнувшихся с этим понятием в начале девятого уровня Java Syntax. Я думаю все из вас, хоть раз, но встречали похожие ошибки при работе в вашем IDE, независимо от того будь это Idea, Eclipse или что-то другое.

Exception in thread "main" java.lang.ArithmeticException at com.example.task01.Test.division(Test.java:10) at com.example.task01.Test.main(Test.java:6)

Принцип работы коллекции Stack

Это, как вы уже догадались и есть наша трассировка. Но не спешите паниковать, сейчас мы с вами разложим данный пример на пальцах. Для начала необходимо понять тот факт, что StackTrace работает как Стэк и это видно из его названия. На этом месте мы остановимся чуть поподробнее. На восьмом уровне вы уже познакомились с коллекциями и знаете что они делятся на три группы Set — множество, List — список, Map — словарь (или карта). По мнению JavaRush (c). Наш Stack входит в группу List . Принцип его работы можно описать как LIFO, что расшифровывается как Last In First Out(Последний пришел, первый ушел). А именно это такой список похожий на стопку книг, чтобы взять элемент который мы положили в Stack первым, нам необходимо сначала извлечь все элементы которые мы добавили в наш список после. Как это указано на картинке выше в отличии например от обычного списка ArrayList где мы можем получить любой элемент из списка по индексу. Еще раз для закрепления. Получение элемента из Стэка возможно только с конца! В то время как первый добавленный в него элемент находится в начале(или на дне как удобнее). Вот какие методы имеет наш Stack Object push() — Добавляет элемент в верх стека. Object pop() — Возвращает элемент, находящийся в верхней части стэка, удаляя его в процессе. Object peek() — Возвращает элемент, находящийся в верхней части стэка, но не удаляет его. int search() — Ищет элемент в стеке. Если найден, возвращается его смещение от вершины стека. В противном случае возвращается -1. boolean empty() — Проверяет, является ли стек пустым. Возвращает true, если стек пустой. Возвращает false, если стек содержит элементы. Так для чего же в Java нужен StackTrace построеный на принципах работы Stack ? Давайте разберем пример ошибки ниже, которая возникла в процессе выполнения такой вот простой программы.

public class Test  public static void main(String[] args)  System.out.println(convertStringToInt(null)); > public static int convertStringToInt(String s)  int x = Integer.parseInt(s); return x; > >

У нас есть класс Test с двумя методами. Всем привычный main и convertStringToInt логика которого заключается в конвертировании и возврате полученной извне(а именно из метода main ) строки в целочисленное число типа int . Как вы видите мы намеренно передали вместо строки с какой-нибудь цифрой, параметр null . Данный параметр наш метод не смог правильно обработать и вызвал ошибку NumberFormatException . Как вы знаете программа начинает отрабатывать свою работу из метода main и в этот момент она создает новый Стэк с названием StackTrace куда кладет текущее значение ее работы под номером 1, далее мы переходим в метод convertStringToInt и программа опять заносит параметры нашего нахождения в созданный ранее StackTrace под номером 2, далее вызывается не видимый нашему глазу метод parseInt находящийся в классе Integer и это уже будет элемент под номером 3 нашего StackTrace , в этом методе будет еще один внутренний вызов добавленный в StackTrace под номером 4 для проверки элемента на null который и приведет к ошибке. Программе необходимо вывести нашу ошибку с указанием всей цепочки наших переходов до момента возникновения ошибки. Тут то ей и приходит на помощь ранее созданный StackTrace с данными наших переходов.

Exception in thread "main" java.lang.NumberFormatException: null at java.base/java.lang.Integer.parseInt(Integer.java:614) at java.base/java.lang.Integer.parseInt(Integer.java:770) at com.example.task01.Test.convertStringToInt(Solution.java:10) at com.example.task01.Test.main(Solution.java:6)

До возникновения ошибки, программа шла вглубь методов, но как только возникла ошибка, все начинает происходить в обратном порядке. Печатается строка с описанием проблемы(№1 на примере), далее берется последнее (и находящееся на вершине) добавленное значение в наш Стэк оно было под номером четыре и печатается в консоль(№2 на примере) и мы видим что проблема возникла в классе Integer на 614 строке кода и вызвала эту строку, строка 770 метода parseInt того же класса(№3 на примере) которая при добавлении в Стэк была под номером три и этот метод класса Integer все еще не видимый нам был вызван уже нашим методом convertStringToInt располагающемся на 10 строке нашей программы(№4 на примере, а при добавлении он был вторым), а его в свою очередь вызвал main на 6 строке(№5 на примере, а при добавлении соответственно первый). Вот так вот, складируя в Стек шаг за шагом наши вызываемые методы мы смогли вернуться обратно в main параллельно печатая информацию что именно привело нас к ошибке. Но StackTrace это не только работа с ошибками, он позволяет получить нам кучу интересной информации о процессе работы нашего приложения. Давайте разберем еще один популярный пример в комментариях к основной лекции 9го уровня. У нас есть код и к нему сразу прикреплю картинку визуализирующую процесс работы программы:

public class Test  public static void main(String[] args)  method1(); method2(); > public static void method1()  //не вызывает ничего > public static void method2()  method3(); method4(); > public static void method3()  //не вызывает ничего > public static void method4()  method5(); > public static void method5()  StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); for (StackTraceElement element:stackTraceElements)  System.out.println(element.getMethodName()); > > >

Stack Trace и с чем его едят - 2

Тут наша программа безошибочно выполняет свою работу и заканчивается. Вот что мы увидим в выводе консоли:

getStackTrace method5 method4 method2 main Process finished with exit code 0 

Как у нас получился такой вывод и что же произошло в пятом методе начиная с 20й строки? Боюсь самое лучше что я смогу сделать это добавить самое популярное объяснение(в сокращении) юзера Кирилла из комментариев к лекции. Обратимся к строчке по созданию StackTrace и разберем ее поэлементно:

StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();

StackTraceElement[] — указание на тип массива(На ранних уровнях вы уже проходили массивы типа int[], String[], вот тут тоже самое). stackTraceElements — имя массива, может быть любым с учетом общих правил наименования на работу эту не влияет. Thread.currentThread() — получение ссылки на текущий поток, в котором выполняются методы, которые мы хотим отследить(пока это не важно, подробнее потоки вы будете разбирать на 16 уровне в квесте Java Core) getStackTrace() — получаем весь Стэк вызываемых методов(Это обычный геттер для StackTrace ) Теперь посмотрим, чем нам может быть полезен созданный массив. Мы понимаем, что в массиве хранится инфа о выполненных методах.(с) И для этого в 21й строке мы запускаем модифицированный цикл for под названием forEach (кстати кто еще не изучил этот цикл, советую почитать о нём) и выводим данные из массива в консоль, а именно информацию какие методы выполнялись в процессе работы посредством конструкции element.getMethodName() . Внимание как мы видим нулевым элементом массива у нас оказался сам getStackTrace() соответственно так как в момент получения массива данных он был последним методом что выполнился и тем самым оказавшись на верхушке Стэка , а помня про нашу конструкцию «Последний пришел, первый ушел» сразу же первым добавляется в массив под нулевым элементом. Вот что еще мы можем получить из StackTraceElement : String getClassName() — Возвращает имя класса. String getMethodName() — Возвращает имя метода. String getFileName() — Возвращает имя файла (в одном файле может быть много классов). String getModuleName() — Возвращает имя модуля (может быть null). String getModuleVersion() — Возвращает версию модуля (может быть null). int getLineNumber() — Возвращает номер строки в файле, в которой был вызов метода. Теперь, когда вы поняли общий принцип работы, советую вам самим опробовать разные методы StackTrace в вашей Ide. Даже если вы не совсем всё усвоили, продолжайте обучение и мозаика сложится так же как сложилась у меня в данном вопросе. Желаю вам всем успехов! P.s. Если вам понравился данный материал, пожалуйста поддержите лайком. Вам не трудно, мне приятно. Спасибо и увидимся на 41 уровне 😉

Необычная Java: StackTrace Extends Throwable

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

Chronicle Software обычно использует ряд различных шаблонов в своих низкоуровневых библиотеках, с которыми большинство разработчиков вообще не сталкивается.

Один из них — это класс, который расширяет Throwable, но не является ошибкой или исключением.

StackTrace Extends Throwable

package net.openhft.chronicle.core; /** * Throwable created purely for the purposes of reporting a stack trace. * This is not an Error or an Exception and is not expected to be thrown or caught. */ public class StackTrace extends Throwable < public StackTrace() < this("stack trace"); >public StackTrace(String message) < this(message, null); >public StackTrace(String message, Throwable cause) < super(message + " on " + Thread.currentThread().getName(), cause); >public static StackTrace forThread(Thread t) < if (t == null) return null; StackTrace st = new StackTrace(t.toString()); StackTraceElement[] stackTrace = t.getStackTrace(); int start = 0; if (stackTrace.length >2) < if (stackTrace[0].isNativeMethod()) < start++; >> if (start > 0) < StackTraceElement[] ste2 = new StackTraceElement[stackTrace.length - start]; System.arraycopy(stackTrace, start, ste2, 0, ste2.length); stackTrace = ste2; >st.setStackTrace(stackTrace); return st; > >

Некоторые важные примечания, чтобы для начала:

  • Это не тот класс исключения, которое, как я рассчитываю, когда-либо может возникнуть. Классы, непосредственно расширяющие Throwable, проверяются, как и Exception, поэтому компилятор поможет вам обеспечить эту проверку.
  • Трассировка стека Throwable определяется при создании Throwable, а не там, где она возникает. Обычно это одна и та же строка, но это не обязательно. Чтобы получить трассировку стека, Throwable не должно вызвать исключение.
  • Объекты элементов трассировки стека не создаются до тех пор, пока они не потребуются. Вместо этого метаданные добавляются к самому объекту, чтобы уменьшить накладные расходы, а массив StackTraceElements заполняется при первом использовании.

Однако давайте рассмотрим этот класс более подробно. Класс будет протоколировать как трассировку стека, где он был создан, так и поток, который его создал. Позже вы увидите насколько это полезно.

Этот класс также можно использовать для хранения трассировки стека другого запущенного потока.

Трассировка стека другого потока берется только тогда, когда поток достигает безопасной точки. Это может произойти через некоторое время после того, как вы попытаетесь получить его.

Связано это с тем, что JVM останавливает поток, и обычно JVM ждут, чтобы остановить каждый поток, поэтому он может проверить стек потока, который вы пытаетесь захватить. Т.е. это имеет высокие накладные расходы, но может быть очень полезным.

StackTrace как отложенное исключение

Мы не предполагаем, что этот Throwable класс будет вызван, но он может записать причину исключения, которое может быть вызвано позже.

Почему был закрыт ресурс

public class EgMain < static class MyCloseable implements Closeable < protected transient volatile StackTrace closedHere; @Override public void close() < closedHere = new StackTrace("Closed here"); // line 13 >public void useThis() < if (closedHere != null) throw new IllegalStateException("Closed", closedHere); >> public static void main(String[] args) throws InterruptedException < MyCloseable mc = new MyCloseable(); // line 27 Thread t = new Thread(mc::close, "closer"); t.start(); t.join(); mc.useThis(); >>

При запуске код выдает следующее исключение:

Обычно вы увидите исключение IllegalStateException и место, где ваш код пытался использовать закрытый ресурс, но это не говорит вам, почему он был закрыт без дополнительной информации.

Поскольку StackTrace является Throwable классом, вы можете указать его в качестве причины последующего исключения или ошибки.

Вы можете увидеть поток, который закрыл ресурс. Таким образом вы узнаете, что это исключение или ошибка произошло в другом потоке. И вы можете увидеть трассировку стека с причиной, по которой он был закрыт. Это может помочь очень быстро диагностировать трудно обнаруживаемые проблемы связанные с преждевременным закрытием ресурсов.

Какой ресурс был закрыт?

Долгоживущие объекты Closeable могут иметь сложный жизненный цикл, и обеспечение их закрытия, когда это необходимо, может быть трудно отследить. Что может привести к утечке ресурсов.

Некоторые ресурсы не удаляются, когда GC освобождает объект, например, объект RandomAccessFile освобождается в GC вместе с файлом, который он представляет, и не закрывается, пока вы его не закроете, что приводит к потенциальной утечке ресурсов файловых дескрипторов.

public class CreatedMain < static class MyResource implements Closeable < private final transient StackTrace createdHere = new StackTrace("Created here"); volatile transient boolean closed; @Override public void close() throws IOException < closed = true; >@Override protected void finalize() throws Throwable < super.finalize(); if (!closed) Logger.getAnonymousLogger().log(Level.WARNING, "Resource discarded but not closed", createdHere); >> public static void main(String[] args) throws InterruptedException < new MyResource(); // line 27 System.gc(); Thread.sleep(1000); >>

Код выводит следующее:

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

Мониторинг производительности критического потока в рабочей среде

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

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

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

public class JitteryMain implements Runnable < volatile long loopStartMS = Long.MIN_VALUE; volatile boolean running = true; @Override public void run() < while (running) < loopStartMS = System.currentTimeMillis(); doWork(); loopStartMS = Long.MIN_VALUE; >> private void doWork() < int loops = new Random().nextInt(100); for (int i = 0; i < loops; i++) pause(1); // line 24 >static void pause(int ms) < try < Thread.sleep(ms); // line 29 >catch (InterruptedException e) < throw new AssertionError(e); // shouldn't happen >> public static void main(String[] args) < final JitteryMain jittery = new JitteryMain(); Thread thread = new Thread(jittery, "jitter"); thread.setDaemon(true); thread.start(); // monitor loop long endMS = System.currentTimeMillis() + 1_000; while (endMS >System.currentTimeMillis()) < long busyMS = System.currentTimeMillis() - jittery.loopStartMS; if (busyMS >100) < Logger.getAnonymousLogger() .log(Level.INFO, "Thread spent longer than expected here, was " + busyMS + " ms.", StackTrace.forThread(thread)); >pause(50); > jittery.running = false; > >

Выводит следующее и опять же, как вы видите, можно легко перемещается по стеку в вашей IDE.

Вам может быть интересно, почему это происходит в данном случае. Наиболее вероятная причина заключается в том, что Thread.sleep(time) спит в течение минимального, а не максимального времени, а в Windows спящий режим 1 мс на самом деле довольно стабильно занимает около 1,9 мс.

Обнаружение одновременного доступа к однопоточному ресурсу разными потоками

package net.openhft.chronicle.core; public class ConcurrentUsageMain < static class SingleThreadedResource < private StackTrace usedHere; private Thread usedByThread; public void use() < checkMultithreadedAccess(); // BLAH >private void checkMultithreadedAccess() < if (usedHere == null || usedByThread == null) < usedHere = new StackTrace("First used here"); usedByThread = Thread.currentThread(); >else if (Thread.currentThread() != usedByThread) < throw new IllegalStateException("Used two threads " + Thread.currentThread() + " and " + usedByThread, usedHere); >> > public static void main(String[] args) throws InterruptedException < SingleThreadedResource str = new SingleThreadedResource(); final Thread thread = new Thread(() ->str.use(), "Resource user"); // line 25 thread.start(); thread.join(); str.use(); // line 29 > >

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

Отключение этой трассировки

Создание StackTrace оказывает значительное влияние на поток и, возможно, на JVM. Однако его легко отключить с помощью управляющего флага, такого как системное свойство, и заменить его нулевым значением.

createdHere = Jvm.isResourceTracing() ? new StackTrace(getClass().getName() + " created here") : null;

Это использование значения null не требует специальной обработки, поскольку logger будет игнорировать Throwable, который имеет значение null, и вы можете указать null как причину для исключения, и это то же самое, что и не указывать ее.

Заключение

Хотя класс, который напрямую расширяет Throwable, выглядит странно, он разрешен.

Более того он удивительно полезен для предоставления дополнительной информации о жизненном цикле ресурса или добавления простого мониторинга, который вы можете запустить в рабочей среде.

Stack trace

Получение стек-трейса

В языке программирования Java у программиста есть очень много способов получить информацию о том, что сейчас происходит в программе. И это не просто слова.

Например, программы на языке C++ после компиляции превращаются в один большой файл машинного кода и все, что во время выполнения доступно программисту, — это адрес куска памяти, который содержит машинный код, который сейчас выполняется. Не густо, скажем так.

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

Стек-трейс

Например, в любой момент работы программы можно узнать класс и имя метода, который сейчас выполняется. И даже не одного метода, а получить информацию о всей цепочке вызовов методов от текущего метода до метода main() .

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

StackTraceElement[] methods = Thread.currentThread().getStackTrace();

Можно записать ее и в две строки:

Thread current = Thread.currentThread(); StackTraceElement[] methods = current.getStackTrace();

Статический метод currentThread() класса Thread возвращает ссылку на объект типа Thread , который содержит информацию о текущей нити (о текущем потоке выполнения). Подробнее о нитях вы узнаете в квесте Java Core .

У этого объекта Thread есть метод getStackTrace() , который возвращает массив элементов StackTraceElement , каждый из которых содержит информацию об одном методе. Все элементы вместе и образуют stack trace .

public class Main < public static void main(String[] args) < test(); > public static void test() < Thread current = Thread.currentThread(); StackTraceElement[] methods = current.getStackTrace(); for(var info: methods) System.out.println(info); > >
java.base/java.lang.Thread.getStackTrace(Thread.java:1606) Main.test(Main.java:11) Main.main(Main.java:5) 

Как мы видим по выводу на экран, в приведенном примере метод getStackTrace() вернул массив из трех элементов:

  • Метод getStackTrace () класса Thread
  • Метод test () класса Main
  • Метод main () класса Main

Из этого стек-трейса можно сделать вывод, что:

  • Метод Thread.getStackTrace() был вызван методом Main.test() в строке 11 файла Main.java
  • Метод Main.test() был вызван методом Main.main() в строке 5 файла Main.java
  • Метод Main.main() никто не вызывал — это первый метод в цепочке вызовов.

Кстати, на экране отобразилась только часть всей имеющийся информации. Все остальное можно получить прямо из объекта StackTraceElement

2. StackTraceElement

Класс StackTraceElement , как следует из его названия, создан для того, чтобы хранить информацию по одному элементу stack trace — т.е. по одному методу из StackTrace .

У объектов этого класса есть такие методы:

String getClassName()
String getMethodName()
String getFileName()
int getLineNumber()
String getModuleName()
String getModuleVersion()

С их помощью можно получить более полную информацию о текущем стеке вызовов:

public class Main < public static void main(String[] args) < test(); > public static void test() < Thread current = Thread.currentThread(); StackTraceElement[] methods = current.getStackTrace(); for(StackTraceElement info: methods) < System.out.println(info.getClassName()); System.out.println(info.getMethodName()); System.out.println(info.getFileName()); System.out.println(info.getLineNumber()); System.out.println(info.getModuleName()); System.out.println(info.getModuleVersion()); System.out.println(); > > >
java.lang.Thread getStackTrace Thread.java 1606 java.base 11.0.2 Main test Main.java 11 null null Main main Main.java 5 null null

имя класса
имя метода
имя файла
номер строки
имя модуля
версия модуля

3. Стек

Что такое Stack Trace вы уже знаете, а что же такое сам Stack (Стек)?

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

Само название Stack переводится с английского как «стопка» и очень похоже на стопку бумаги. Если вы положите на стопку бумаги листы 1, 2 и 3, взять вы их сможете только в обратном порядке: сначала третий, затем второй, а только затем первый.

В Java даже есть специальная коллекция с таким поведением и таким же названием — Stack. Этот класс в своем поведении очень похож на ArrayList и LinkedList . Однако у него есть еще методы, которые реализуют поведение стека:

T push(T obj)
T pop()
T peek()
boolean empty()

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *