Статьи

Полный курс по языку Java — от установки JDK до многопоточности и HTTP-запросов

Отладка и чтение stack trace

Страница 25 из 30 · 2026-10-25 · 6 мин чтения

Виды ошибок

  • Ошибка компиляции — код не соответствует синтаксису или типам. javac не создает .class.
  • Ошибка выполнения — программа запустилась, но выбросила исключение.
  • Логическая ошибка — программа работает, но результат неверный. Сложнее всего находить.

Чтение stack trace

Пример программы:

public class Demo {    static int parse(String s) {        return Integer.parseInt(s);    }    static int process(String input) {        return parse(input) * 2;    }    public static void main(String[] args) {        System.out.println(process("12"));        System.out.println(process("abc"));    }}

Вывод:

24Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"    at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)    at java.base/java.lang.Integer.parseInt(Integer.java:661)    at java.base/java.lang.Integer.parseInt(Integer.java:777)    at Demo.parse(Demo.java:3)    at Demo.process(Demo.java:7)    at Demo.main(Demo.java:12)

Как читать:

  1. Первая строка — тип исключения и сообщение: NumberFormatException: For input string: "abc".
  2. Строки at ... — стек вызовов, сверху вниз от места ошибки к точке входа.
  3. Строки из java.base/... — внутренности JDK. Искать причину нужно в первой строке со своим классом: Demo.parse(Demo.java:3).
  4. Дальше видно, кто вызвал parse: process на 7 строке, а того вызвал main на 12 строке.

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

Блок Caused by

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

Exception in thread "main" java.lang.IllegalStateException: Не удалось загрузить настройки    at Config.load(Config.java:15)    at Main.main(Main.java:5)Caused by: java.io.FileNotFoundException: settings.json (No such file or directory)    at java.base/java.io.FileInputStream.open0(Native Method)    at Config.load(Config.java:12)    ... 1 more

Настоящая причина обычно в последнем блоке Caused by.

Частые исключения и их причины

ИсключениеТипичная причина
NullPointerExceptionвызов метода у null
ArrayIndexOutOfBoundsExceptionиндекс вне границ массива
IndexOutOfBoundsExceptionиндекс вне границ списка
ClassCastExceptionневерное приведение типа
NumberFormatExceptionстрока не является числом
ConcurrentModificationExceptionизменение коллекции во время перебора
StackOverflowErrorбесконечная рекурсия
OutOfMemoryErrorутечка памяти или слишком большие данные
NoClassDefFoundErrorне хватает класса или библиотеки в classpath
UnsupportedOperationExceptionизменение неизменяемой коллекции

Современные версии Java выдают подробные сообщения для NullPointerException:

Cannot invoke "String.length()" because "<local1>" is null

Отладка выводом

Самый простой способ — напечатать промежуточные значения:

public class PrintDebug {    static int sumEven(int[] data) {        int sum = 0;        for (int i = 0; i < data.length; i++) {            System.out.println("i=" + i + ", value=" + data[i] + ", sum=" + sum);            if (data[i] % 2 == 0) {                sum += data[i];            }        }        return sum;    }    public static void main(String[] args) {        System.out.println(sumEven(new int[]{1, 2, 3, 4}));    }}

Метод быстрый, но не оставляйте такой вывод в готовом коде. Для сообщений об ошибках используйте System.err.println.

Отладчик

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

Основные понятия

  • Breakpoint — точка останова. Выполнение приостанавливается перед этой строкой.
  • Step Over — выполнить текущую строку целиком, не заходя в методы.
  • Step Into — зайти внутрь вызываемого метода.
  • Step Out — выполнить до конца текущего метода и вернуться.
  • Resume — продолжить до следующей точки останова.
  • Watch — выражение, значение которого отслеживается при каждом шаге.
  • Call stack — текущая цепочка вызовов.

IntelliJ IDEA

  1. Кликните слева от номера строки, чтобы поставить точку останова (красный кружок).
  2. Запустите программу в режиме Debug: значок жука или Shift+F9 (macOS: Ctrl+D).
  3. На панели Debug смотрите переменные, стек и добавляйте Watch.
  4. Шаги: F8 — Step Over, F7 — Step Into, Shift+F8 — Step Out, F9 — Resume.

Дополнительные возможности:

  • Условная точка останова: правая кнопка по красному кружку → Condition, например i == 5.
  • Evaluate Expression: Alt+F8 — вычислить выражение на лету.
  • Изменение значения: правая кнопка по переменной → Set Value.
  • Точка останова на исключении: диалог Breakpoints → Java Exception Breakpoints.

Eclipse

  • Точка останова: двойной клик на левом поле редактора.
  • Запуск: Debug As → Java Application, или F11.
  • Шаги: F6 — Step Over, F5 — Step Into, F7 — Step Return, F8 — Resume.
  • Перспектива Debug показывает переменные, стек и точки останова.

VS Code

  • Точка останова: клик слева от номера строки.
  • Запуск: F5 или кнопка Debug над методом main.
  • Шаги: F10 — Step Over, F11 — Step Into, Shift+F11 — Step Out, F5 — Continue.
  • Панель Run and Debug показывает Variables, Watch, Call Stack.

Пример отладки

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

public class Buggy {    static int sumEven(int[] data) {        int sum = 0;        for (int i = 1; i < data.length; i++) {            if (data[i] % 2 == 0) {                sum += data[i];            }        }        return sum;    }    public static void main(String[] args) {        System.out.println(sumEven(new int[]{2, 4, 5, 6}));    }}

Ожидается 12, выводится 10. Поставьте точку останова на строку if и пройдите цикл по шагам. Станет видно, что i начинается с 1 и элемент data[0] пропускается. Исправление: int i = 0.

Стратегия поиска ошибки:

  1. Воспроизведите ошибку стабильно.
  2. Сузьте область: на каком участке данные еще верные, а где уже нет.
  3. Сформулируйте гипотезу и проверьте ее отладчиком.
  4. После исправления напишите тест, который ловит эту ошибку.

Логирование

Для настоящих приложений вместо System.out используют логгер. В стандартной библиотеке есть java.util.logging:

import java.util.logging.Level;import java.util.logging.Logger;public class LogDemo {    private static final Logger LOG = Logger.getLogger(LogDemo.class.getName());    public static void main(String[] args) {        LOG.info("Приложение запущено");        LOG.warning("Мало свободного места");        try {            Integer.parseInt("xyz");        } catch (NumberFormatException e) {            LOG.log(Level.SEVERE, "Ошибка разбора числа", e);        }    }}

Уровни по возрастанию важности: FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE. В проектах чаще применяют SLF4J с Logback, подключаемые через Maven или Gradle.

Утверждения assert

public class AssertDemo {    static double average(int[] data) {        assert data.length > 0 : "Массив не должен быть пустым";        double sum = 0;        for (int n : data) {            sum += n;        }        return sum / data.length;    }    public static void main(String[] args) {        System.out.println(average(new int[]{1, 2, 3}));    }}

Проверки assert по умолчанию выключены. Включение: java -ea AssertDemo. Не используйте assert для проверки пользовательского ввода.

Консольный отладчик jdb

javac -g Buggy.javajdb Buggy

Флаг -g добавляет отладочную информацию. Основные команды jdb:

stop at Buggy:5runlocalsprint inextstepcontquit

Диагностика работающего процесса

Инструменты JDK:

jpsjstack <pid>jcmd <pid> Thread.printjcmd <pid> GC.heap_infojmap -histo <pid>
  • jps — список запущенных Java-процессов;
  • jstack и jcmd Thread.print — снимок стеков всех потоков, помогает при зависаниях и взаимоблокировках;
  • jmap -histo — гистограмма объектов в памяти, помогает искать утечки.

Для профилирования есть JDK Flight Recorder и VisualVM.

Советы

  • Читайте сообщение об ошибке полностью, ответ часто в нем.
  • Ищите первую строку стека, относящуюся к вашему коду.
  • Меняйте одно за раз.
  • Не угадывайте: проверяйте гипотезы отладчиком или логами.
  • Объясните проблему вслух или на бумаге: этот прием часто сам подсказывает решение.