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

Що знаходиться на вершині ієрархії винятків?

На самому верху ієрархії всіх об'єктів, які можна викинути і перехопити, знаходиться клас java.lang.Throwable.

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

Junior Level

Корінь ієрархії — java.lang.Throwable

На самому верху ієрархії всіх об’єктів, які можна викинути і перехопити, знаходиться клас java.lang.Throwable.

Object
  └── Throwable
        ├── Error (фатальні помилки JVM)
        │     ├── OutOfMemoryError
        │     ├── StackOverflowError
        │     └── NoClassDefFoundError
        │
        └── Exception (помилки додатку)
              ├── RuntimeException (unchecked)
              │     ├── NullPointerException
              │     └── IllegalArgumentException
              │
              └── Checked Exceptions
                    ├── IOException
                    └── SQLException

Дві основні гілки

Error — фатальні проблеми JVM:

  • OutOfMemoryError — закінчилася пам’ять
  • StackOverflowError — нескінченна рекурсія
  • Ловити не потрібно — система у нестабільному стані

Exception — помилки додатку:

  • Можна і потрібно обробляти
  • Поділяються на checked та unchecked

Error – проблеми, які додаток не може виправити (пам’ять, стек). Exception – проблеми, які може. Розділення в ієрархії дозволяє ловити Exception, не ловлячи Error.

Приклад

// Throwable — батьківський клас всіх винятків
try {
    // якийсь код
} catch (Throwable t) {
    // Перехопить ВСЕ, включаючи Error (зазвичай так не роблять)
    System.err.println("Caught: " + t.getMessage());
}

Middle Level

Чому Throwable реалізує Serializable?

Серіалізація – перетворення об’єкта на байти для передачі по мережі або збереження. Це потрібно для RMI (віддалених викликів), JMX, кешування. Виняток повинен бути серіалізованим, щоб «подорожувати» між JVM.

Це критично для розподілених систем (RMI, JMX). Коли виняток відбувається на сервері, JVM серіалізує його разом зі стек-трейсом і передає клієнту.

Ключові підкласи Error

  • OutOfMemoryError — різні підтипи:
    • Java heap space — закінчилася купа
    • Metaspace — закінчилася пам’ять під метадані класів
    • Unable to create new native thread — ОС заборонила потоки
  • ThreadDeath — викидається при Thread.stop() (deprecated). Це Error, щоб звичайний catch (Exception e) не проковтнув його.

  • NoClassDefFoundError — клас був при компіляції, але зник у рантаймі. Порушено цілісність середовища.

Проблема пробросу через ExecutorService

Коли виняток відбувається всередині потоку в пулі, він перехоплюється ExecutorService і зберігається всередині Future:

Future<?> future = executor.submit(() -> {
    throw new RuntimeException("Task failed!");
});

// Виняток "схований" всередині Future
try {
    future.get(); // Тут вилетить ExecutionException
} catch (ExecutionException e) {
    System.err.println("Cause: " + e.getCause()); // RuntimeException
}
// ExecutorService перехоплює всі винятки із завдань і зберігає їх всередині Future.
// При виклику future.get() виняток загортається в ExecutionException.
// Щоб дістатися до причини: e.getCause().

fillInStackTrace()

fillInStackTrace() – нативний метод (написаний на C/C++, не на Java). Обходить стек викликів на рівні ОС, створюючи знімок усіх кадрів. Це дорого: потребує переходу між Java і нативним кодом + обхід всього стека.

При виклику getStackTrace() нативна структура перетворюється на масив StackTraceElement[] — це ледача оптимізація, але заповнення нативної структури все одно дороге.


Senior Level

Under the Hood: приховане поле backtrace

Всередині Throwable є поле private Object backtrace, заповнюване нативним методом fillInStackTrace(). Воно зберігає посилання на нативний стек викликів у пам’яті JVM.

Serialization Pitfall: при серіалізації нативний backtrace втрачається, і JVM конвертує його у масив Java-об’єктів. Це збільшує розмір пакета і навантаження на CPU.

Вимкнення стек-трейсу для продуктивності

public class FastException extends RuntimeException {
    public FastException(String message) {
        // enableSuppression = true, writableStackTrace = false
        super(message, null, true, false);
    }
}

Прискорює створення у десятки разів — нативний обхід стека не виконується.

Immutable Exceptions

У високонавантажених системах створюють статичні інстанси:

public static final MyException INVALID_DATA =
    new MyException("Invalid data", null, true, false);

Не створює нові об’єкти, але в логах буде однаковий стек-трейс.

Suppressed Exceptions

Введені в Java 7 для try-with-resources. Якщо виняток виник у try, а другий — при закритті ресурсу в finally, перший стає основним, другий додається в suppressed:

try {
    resource.doWork(); // IOException #1
} finally {
    resource.close();  // IOException #2
}
// IOException #1 — основний, #2 — suppressed

Діагностика

  • Ніколи не робіть catch (Throwable t) у бізнес-логіці — перехопить OutOfMemoryError
  • Root Cause Analysis — використовуйте ExceptionUtils.getRootCause() для пошуку першопричини
  • Розподілені системи — на межі сервісу вилучайте повідомлення та код помилки, передавайте лише DTO

🎯 Шпаргалка для співбесіди

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

  • Корінь ієрархії — java.lang.Throwable (не Exception)
  • Дві гілки: Error (фатальні проблеми JVM) та Exception (помилки додатку)
  • Error — OutOfMemoryError, StackOverflowError, NoClassDefFoundError — не ловити
  • Exception поділяється на RuntimeException (unchecked) та checked exceptions
  • fillInStackTrace() — нативний метод, обходить стек викликів, дорога операція
  • Suppressed exceptions — механізм Java 7 для try-with-resources (зберігає другий виняток)
  • Вимкнення стек-трейсу (writableStackTrace = false) прискорює створення у десятки разів
  • При серіалізації нативний backtrace втрачається — конвертується у Java-об’єкти

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

  • Чому Throwable реалізує Serializable? — Для RMI, JMX, кешування — винятки «подорожують» між JVM
  • Що буде, якщо перехопити OutOfMemoryError? — JVM може бути у нестабільному стані, наступна операція впаде
  • Що таке suppressed exceptions? — Коли в try та close() впали два винятки, другий додається у suppressed-список першого
  • Чому не можна створювати immutable винятки для всього? — Однаковий стек-трейс ускладнює дебаг

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

  • “Корінь ієрархії — Exception” — ні, Throwable
  • “Я ловлю Throwable у бізнес-логіці для надійності” — це перехопить OutOfMemoryError
  • “fillInStackTrace() — звичайний Java-метод” — ні, нативний, обходить стек на рівні ОС

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

  • [[Що таке Throwable]]
  • [[В чому різниця між Error та Exception]]
  • [[Що таке suppressed exceptions]]
  • [[Що таке stack trace]]
  • [[Що таке try-with-resources]]