← Назад к вопросам

Как работает трассировка?

1.7 Middle🔥 211 комментариев
#REST API и микросервисы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает трассировка

Трассировка (stack trace) в Java — это представление содержимого стека вызовов в момент возникновения исключения. Это критически важный инструмент для отладки, так как показывает точный путь выполнения программы, который привёл к ошибке.

Что такое стек вызовов?

Когда один метод вызывает другой, вызывающий метод помещается в стек вызовов (call stack). Стек растёт по мере вложенности вызовов и сокращается по мере возврата из методов:

public class Main {
    public static void main(String[] args) {
        methodA();  // main вызывает A
        // Стек: [main]
    }
}

public static void methodA() {
    methodB();  // A вызывает B
    // Стек: [main -> A]
}

public static void methodB() {
    methodC();  // B вызывает C
    // Стек: [main -> A -> B]
}

public static void methodC() {
    int result = 10 / 0;  // Ошибка здесь!
    // Стек: [main -> A -> B -> C]
}

Когда возникает исключение, Java создаёт трассировку в порядке от последнего вызова к первому.

Пример stack trace

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at Main.methodC(Main.java:25)      <-- Самый глубокий вызов
    at Main.methodB(Main.java:20)      <-- Средний вызов
    at Main.methodA(Main.java:15)      <-- Первый вызов
    at Main.main(Main.java:10)         <-- Entry point

Каждая строка показывает:

  • Класс и метод: Main.methodC
  • Файл и номер строки: (Main.java:25)
  • Тип ошибки: ArithmeticException: / by zero

Как Java создаёт stack trace

Когда выбрасывается исключение, Java запускает процесс:

public class Exception {
    public Exception(String message) {
        this.detailMessage = message;
        // Захватить текущий стек
        this.stackTrace = Thread.currentThread().getStackTrace();
    }
}

// Внутренний процесс:
public static void throwException() {
    try {
        throw new Exception("Something went wrong");
    } catch (Exception e) {
        // Java уже создала stack trace в момент throw
        e.printStackTrace();  // Выводит трассировку
    }
}

Получение и вывод stack trace

Способ 1: printStackTrace()

try {
    int x = 10 / 0;
} catch (ArithmeticException e) {
    e.printStackTrace();  // Выводит в System.err
}

Вывод:

java.lang.ArithmeticException: / by zero
    at Main.main(Main.java:10)

Способ 2: getStackTrace()

try {
    int x = 10 / 0;
} catch (ArithmeticException e) {
    StackTraceElement[] elements = e.getStackTrace();
    
    for (StackTraceElement element : elements) {
        System.out.println(element.getClassName());     // "Main"
        System.out.println(element.getMethodName());    // "main"
        System.out.println(element.getFileName());      // "Main.java"
        System.out.println(element.getLineNumber());    // 10
    }
}

Способ 3: Логирование

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Service {
    private static final Logger logger = LoggerFactory.getLogger(Service.class);
    
    public void process() {
        try {
            riskyOperation();
        } catch (Exception e) {
            // Логирует stack trace автоматически
            logger.error("Processing failed", e);
        }
    }
}

// Лог будет содержать полный stack trace

Понимание stack trace

public class Database {
    public User getUser(String id) {
        return executeQuery("SELECT * FROM users WHERE id = " + id);
    }
    
    private User executeQuery(String sql) {
        return parseResult(connection.query(sql));
    }
    
    private User parseResult(ResultSet rs) {
        return new User(rs.getString("name"));  // Может быть null!
    }
}

// Использование
public class Main {
    public static void main(String[] args) {
        Database db = new Database();
        User user = db.getUser("invalid_id");  // Вернёт null
        System.out.println(user.getName());    // NullPointerException здесь!
    }
}

// Stack trace:
// Exception in thread "main" java.lang.NullPointerException
//     at Main.main(Main.java:15)          <-- Где произошла ошибка
//     at java.base/java.lang.Thread.run(Thread.java:842)

Из этого stack trace видно:

  • Ошибка в main методе на строке 15
  • Это NullPointerException, значит кто-то вызвал метод на null объекте
  • Посмотрев код на строке 15, видим user.getName() — значит user был null

Stack trace и производительность

Создание stack trace требует ресурсов:

// Плохо: создаёт stack trace на каждый вызов
public static void logEventParsed(String event) throws Exception {
    // Исключение создаёт stack trace
    if (!isValid(event)) {
        throw new Exception("Invalid event");  // ДОРОГО!
    }
}

// Хорошо: просто логирование без исключения
public static void logEventParsed(String event) {
    if (!isValid(event)) {
        logger.warn("Invalid event: {}", event);  // Дешевле
    }
}

// Очень плохо: исключения в цикле
for (int i = 0; i < 1000000; i++) {
    try {
        Object obj = null;
        obj.toString();  // Создаёт 1 млн stack traces!
    } catch (NullPointerException e) {
        // Обработка
    }
}

Фильтрация stack trace

Для больших приложений stack trace может быть очень длинным (сотни строк). Можно фильтровать:

try {
    riskyOperation();
} catch (Exception e) {
    // Вывести только релевантные части
    StackTraceElement[] trace = e.getStackTrace();
    for (StackTraceElement element : trace) {
        // Пропустить классы из framework
        if (element.getClassName().startsWith("com.myapp")) {
            System.out.println(element);
        }
    }
}

Причины цепочки исключений

Одно исключение может вызвать другое:

public void saveUser(User user) {
    try {
        database.save(user);
    } catch (SQLException e) {
        // Оборачиваем в свой тип исключения
        throw new UserServiceException("Failed to save user", e);
    }
}

// Stack trace покажет обе ошибки:
// UserServiceException: Failed to save user
//     at Service.saveUser(Service.java:10)
// Caused by: SQLException: Duplicate key value
//     at Database.save(Database.java:50)
//     at sun.jdbc.odbc.JdbcOdbc.executeUpdate(JdbcOdbc.java:200)

Отключение stack trace

В production иногда отключают создание stack trace для экономии памяти:

public class FastException extends Exception {
    @Override
    public synchronized Throwable fillInStackTrace() {
        return this;  // Не создавать stack trace
    }
}

Лучшие практики

  1. Всегда логируйте исключения с stack trace

    logger.error("Operation failed", exception);  // Хорошо
    
  2. Не игнорируйте исключения

    try {
        operation();
    } catch (Exception e) {
        // Плохо - нет логирования
    }
    
  3. Не используйте исключения для контроля потока

    // Плохо
    try {
        for (int i = 0; i < 1000000; i++) {
            array[i].process();
        }
    } catch (NullPointerException e) { }
    
  4. Оборачивайте исключения с контекстом

    try {
        processData(data);
    } catch (IOException e) {
        throw new DataProcessingException(
            "Failed to process file: " + filename, e
        );
    }
    

Резюме

Stack trace — это снимок стека вызовов в момент исключения. Он показывает точный путь выполнения программы, что критично для отладки. Java автоматически создаёт stack trace при выброске исключения, но нужно правильно логировать и анализировать эту информацию.