Питання 16 · Розділ 7

Що таке stack trace?

Stack trace:

Мовні версії: English Russian Ukrainian

Junior Level

Визначення

Stack trace (стек викликів) — це список методів, які були викликані до виникнення винятку. Він показує шлях виконання коду від точки помилки до початку програми.

Stack frame (кадр стека) — це один елемент у ланцюжку викликів. Кожного разу, коли програма викликає метод, JVM створює «кадр» у пам’яті, який зберігає: локальні змінні методу, параметри, адресу повернення. Stack trace — це просто список таких кадрів, від поточного методу до main().

Приклад

public class Main {
    public static void main(String[] args) {
        method1();
    }

    static void method1() {
        method2();
    }

    static void method2() {
        throw new RuntimeException("Error!");
    }
}

Stack trace:

java.lang.RuntimeException: Error!
    at Main.method2(Main.java:12)
    at Main.method1(Main.java:8)
    at Main.main(Main.java:4)

Як читати

Читайте зверху вниз:

java.lang.RuntimeException: Error!
    at Main.method2(Main.java:12)
    at Main.method1(Main.java:8)
    at Main.main(Main.java:4)

Розбір построчно:

  1. java.lang.RuntimeException: Error! — тип винятку (RuntimeException) та повідомлення (Error!). Це найважливіший рядок: він каже ЩО сталося
  2. at Main.method2(Main.java:12) — помилка сталася в методі method2 класу Main, файл Main.java, рядок 12. Це той самий stack frame (кадр стека): метод, який був активний у момент помилки
  3. at Main.method1(Main.java:8)method2 був викликаний з method1 на рядку 8. Це попередній кадр стека
  4. at Main.main(Main.java:4)method1 був викликаний з main на рядку 4. Це корінь стека (точка входу)

Аналогія: уявіть слід із крихтів хліба. main() поклав крихту, викликав method1(), той поклав крихту і викликав method2(), де все зламалося. Stack trace — це шлях назад до початку.

Де побачити

  • У консолі під час запуску програми
  • У логах сервера
  • При виклику exception.printStackTrace()

Коли НЕ збирати stack trace (Performance Caveat)

Створення stack trace — дорога операція (~1-5 мікросекунд). JVM обходить усі кадри стека і копіює їх у масив.

Не збирайте stack trace, якщо:

  • Винятки використовуються для потоку управління (антипатерн) — наприклад, кидати NotFoundException замість повернення Optional.empty(). При 1000 запитах на секунду це мілісекунди, які складаються
  • Високонавантажені системи — у hot-path (код, який викликається тисячі разів на секунду) створення винятку з повним стеком створює помітне навантаження на CPU та GC (garbage collector)
  • Expected/normal flow — якщо NumberFormatException при парсингу користувачського вводу — це очікувана ситуація, не логуйте повний стек, достатньо повідомлення

Альтернатива для легких винятків: у Java є трюк з fillInStackTrace() — можна перевизначити його у своєму винятку як return this;, щоб не збирати стек. Але тоді ви втратите інформацію про джерело помилки.


Middle Level

Внутрішній устрій

Stack trace — це масив об’єктів java.lang.StackTraceElement:

StackTraceElement[] elements = Thread.currentThread().getStackTrace();
for (StackTraceElement el : elements) {
    System.out.println(el.getClassName());   // "com.example.MyClass"
    System.out.println(el.getMethodName());  // "myMethod"
    System.out.println(el.getLineNumber());  // 42
    System.out.println(el.getFileName());    // "MyClass.java"
    System.out.println(el.isNativeMethod()); // false
}

Кожен елемент містить:

  • Ім’я файлу
  • Ім’я класу
  • Ім’я методу
  • Номер рядка (якщо компіляція з -g)
  • Флаг нативного методу

StackWalker API (Java 9+)

До Java 9 getStackTrace() завжди копіював весь стек у масив — дорого.

StackWalker читає стек ліниво:

StackWalker.getInstance().forEach(frame ->
    System.out.println(frame.getClassName() + " @ " + frame.getLineNumber())
);

Переваги:

  • Ліниве читання (стримом)
  • Пропуск непотрібних кадрів (проксі Spring, Hibernate)
  • Доступ до об’єктів Class, а не тільки рядкових імен

Реактивні стек-трейси

В асинхронному коді (Project Reactor / WebFlux) класичний стек-трейс марний — показує тільки нутрощі Netty threads.

Рішення: Hooks.onOperatorDebug() — «склеює» логічні шматки стека, але б’є по продуктивності.


Senior Level

FillInStackTrace Cost

Основний час — нативний обхід кадрів стека (fillInStackTrace). Чим глибше вкладеність методів (Spring!), тим дорожче створення винятку.

Stack Tracing у логах

Друк повного стека при кожному 404 — заб’є диск і задушить I/O.

Best Practice: повний стек тільки для ERROR, для очікуваних бізнес-винятків — тільки повідомлення та cause.

OmitStackTraceInFastThrow

При екстремально частих повтореннях одного винятку JVM (HotSpot) оптимізує його і перестає заповнювати стек-трейс:

java.lang.NullPointerException
// Без стека!

Флаг -XX:-OmitStackTraceInFastThrow вимикає цю оптимізацію для налагодження.

Obfuscation

При обфускації (Proguard/R8) стек-трейс перетворюється на a.b.c(SourceFile:1). Для розшифровки потрібні mapping-файли.

Діагностика

  • Thread.getAllStackTraces() — знімок усіх потоків. «Бідний» аналог jstack для діагностики deadlock-ів.
  • Async Trace ID — у розподілених системах використовуйте Span ID / Trace ID (OpenTelemetry) для зв’язку стеків між мікросервісами.
  • jstack <pid> — повний дамп потоків з моніторами
  • JSON структуровані логи — стек-трейс як окреме індексоване поле в ELK

🎯 Шпаргалка для інтерв’ю

Обов’язково знати:

  • Stack trace — список кадрів стека (stack frames) від точки помилки до main()
  • Кожен кадр зберігає: ім’я класу, методу, номер рядка, ім’я файлу
  • Створення stack trace — дорога операція (~1-5 мкс); JVM обходить усі кадри
  • StackWalker (Java 9+) читає стек ліниво, ефективніше за getStackTrace()
  • Флаг -XX:-OmitStackTraceInFastThrow вимикає оптимізацію JVM, яка прибирає стек при частих повтореннях
  • В асинхронному коді (WebFlux) класичний стек-трейс марний — показує тільки internals фреймворку

Часті уточнюючі запитання:

  • Як читати stack trace? — Зверху вниз: перший рядок — тип та повідомлення винятку, далі — шлях від місця помилки до точки входу
  • Коли НЕ збирати stack trace? — У hot-path (тисячі викликів/сек), при expected flow (валідація вводу), коли винятки використовуються для управління потоком
  • Що таке fillInStackTrace()? — Нативний метод, який обходить кадри стека; можна перевизначити як return this для пропуску (але втрачається інформація)
  • Як працює StackWalker? — Ліниве читання стека стримом, можна пропускати кадри, доступ до Class-об’єктів а не тільки рядків

Червоні прапорці (НЕ говорити):

  • “Stack trace безкоштовний — друкую завжди” — Створення стека навантажує CPU та GC, особливо в highload
  • “В асинхронному коді стек-трейс показує повну картину” — У реактивному коді показує тільки internals Netty/EventLoop
  • “OmitStackTraceInFastThrow — це баг” — Це оптимізація HotSpot для частих повторюваних винятків

Пов’язані теми:

  • [[17. Що робить метод printStackTrace()]]
  • [[18. Як правильно логовати винятки]]
  • [[20. Чому не варто ковтати винятки (порожній catch)]]
  • [[13. Чи можна створювати кастомні винятки]]
  • [[29. Що таке chaining винятків]]