Вопрос 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 для часто повторяющихся исключений

Связанные темы:

  • [[16. Что делает метод printStackTrace()]]
  • [[17. Как правильно логировать исключения]]
  • [[19. Почему не стоит глотать исключения (catch empty)]]
  • [[13. Можно ли создавать кастомные исключения]]
  • [[28. Что такое exception chaining]]