Що знаходиться на вершині ієрархії винятків
На самій вершині ієрархії всіх винятків та системних помилок у мові Java знаходиться клас java.lang.Throwable (який успадковується безпосередньо від java.lang.Object).
🟢 Junior Level
На самій вершині ієрархії всіх винятків та системних помилок у мові Java знаходиться клас java.lang.Throwable (який успадковується безпосередньо від java.lang.Object).
Тільки об’єкти класу Throwable або його нащадків можуть бути:
- Викинені за допомогою ключового слова
throw. - Оголошені в сигнатурі методу за допомогою ключового слова
throws. - Перехоплені в секції
catch (...)блокуtry-catch.
Спроба написати throw new String("Error"); або catch (Object obj) призведе до помилки компіляції.
Деревоподібна структура ієрархії
java.lang.Object
│
java.lang.Throwable
│
┌────────────────┴────────────────┐
▼ ▼
java.lang.Error java.lang.Exception
(Системні аварії JVM) (Помилки логіки та середовища)
│ │
┌───────────┴───────────┐ ┌───────────┴───────────┐
▼ ▼ ▼ ▼
OutOfMemoryError StackOverflowError Checked Exceptions RuntimeException
(IOException, (Unchecked: NPE,
SQLException) IllegalArgument)
Дві головні гілки
java.lang.Error: Сигналізує про критичні апаратні або системні збої віртуальної машини JVM (вичерпання фізичної пам’яті, переповнення стека потоку, пошкодження байткоду). Звичайний прикладний код не повинен намагатися перехоплюватиError.java.lang.Exception: Сигналізує про помилки рівня застосунку та інфраструктури (не знайдено файл, відпала мережа, передано невалідні дані). Має оброблятися програмно.
🟡 Middle Level
Внутрішня будова класу Throwable
Клас Throwable інкапсулює 5 ключових полів, що визначають стан будь-якої помилки:
public class Throwable implements Serializable {
private String detailMessage; // Текстовий опис помилки
private Throwable cause = this; // Першопричина (вкладений виняток)
private StackTraceElement[] stackTrace; // Масив фреймів стека викликів
private List<Throwable> suppressedExceptions; // Пригнічені винятки (Java 7+)
private transient Object backtrace; // Нативний вказівник на стек JVM
}
detailMessage: Повідомлення, що передається в конструктор і повертається методомgetMessage().cause(Chained Exceptions): Посилання на першопричину збою (ініціалізується в конструкторі або через методinitCause(Throwable)). Дозволяє досліджувати ланцюжок причин за допомогоюgetCause().backtrace: Прихованеtransientполе, що заповнюється нативним методомfillInStackTrace()під час створення об’єкта. Зберігає знімок кадрів виконання потоку на рівні ядра JVM.suppressedExceptions: Список винятків, пригнічених у конструкціїtry-with-resources(доступний черезgetSuppressed()).
Чому Throwable реалізує java.io.Serializable?
Історично за часів створення Java (1995–1997 рр.) ключовою розподіленою технологією був протокол RMI (Remote Method Invocation) та специфікація EJB. Якщо метод віддаленого сервера завершувався помилкою, JVM зобов’язана була серіалізувати об’єкт винятку мережею та передати його клієнту.
Сучасний підводний камінь: Під час серіалізації нативний вказівник backtrace недоступний. JVM змушена «матеріалізувати» нативну структуру у величезний масив важковагових Java-об’єктів StackTraceElement[], що призводить до значного роздування мережевого трафіку та витрат пам’яті.
🔴 Senior Level
StackWalker API (Java 9+): Сучасна альтернатива Throwable
До Java 9 єдиним способом програмного аналізу стека викликів було створення фіктивного винятку:
StackTraceElement[] frames = new Throwable().getStackTrace();
Цей підхід є вкрай неефективним: JVM робить повну зупинку потоку, збирає абсолютно всі фрейми до самої основи (до main або потоку Tomcat) та виділяє пам’ять під весь масив.
Починаючи з Java 9, стандартом аналізу стека став java.lang.StackWalker:
- Підтримує ліниве (lazy) вичитування: фрейми парсяться тільки доти, доки ви читаєте потік
Stream<StackFrame>. - Дозволяє відфільтровувати внутрішні класи рефлексії та Spring CGLIB проксі без виділення зайвої пам’яті в купі.
package com.example.diagnostic;
import java.lang.StackWalker.StackFrame;
import java.util.List;
public class StackInspector {
private static final StackWalker WALKER =
StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
// Витягує ім'я безпосереднього викликаючого методу за O(1) без створення Exception
public static String getImmediateCaller() {
return WALKER.walk(frames -> frames
.skip(1) // Пропускаємо сам метод getImmediateCaller
.findFirst()
.map(StackFrame::getMethodName)
.orElse("UNKNOWN"));
}
// Пошук першого класу поза поточним пакетом
public static List<String> getExternalCallers() {
return WALKER.walk(frames -> frames
.map(StackFrame::getClassName)
.filter(className -> !className.startsWith("com.example.diagnostic"))
.limit(5)
.toList());
}
}
Конструктор Java 7+ із прапорцями пригнічення та стека
У класі Throwable присутній захищений конструктор:
protected Throwable(String message, Throwable cause,
boolean enableSuppression,
boolean writableStackTrace)
enableSuppression = false: Забороняє накопичення пригнічених винятків (економить пам’ять,getSuppressed()поверне порожній масив).writableStackTrace = false: Повністю блокує викликfillInStackTrace(). Полеbacktraceзалишаєтьсяnull, стек-трейс не формується. Це прискорює створення екземпляра винятку в десятки разів.
🎯 Шпаргалка для інтерв’ю
30-секундна відповідь
У вершині ієрархії винятків Java знаходиться клас
java.lang.Throwable. Тільки його нащадки можуть передаватися в операториthrow,throwsтаcatch.
Throwableподіляється на дві фундаментальні гілки:
java.lang.Error— неусувні системні відмови віртуальної машини JVM (OutOfMemoryError,StackOverflowError), які прикладний код не повинен перехоплювати.java.lang.Exception— прикладні помилки (поділяються на checked та uncheckedRuntimeException).Всередині
Throwableміститьmessage, першопричинуcause, масивsuppressedExceptionsта приховане нативне полеbacktrace, що заповнюється методомfillInStackTrace(). Починаючи з Java 9, для високопродуктивного аналізу стека викликів замістьnew Throwable().getStackTrace()використовується потоковийStackWalker.
4 каверзних питання з відповідями
-
Чи можна викинути екземпляр класу
Object, написавшиthrow new Object();? Відповідь: Ні, код не скомпілюється. Специфікація Java (JLS §14.18) вимагає, щоб операнд оператораthrowмав тип, привідний доjava.lang.Throwable. -
Навіщо в Java 7 до класу Throwable додали механізм Suppressed Exceptions? Відповідь: Механізм додано для підтримки конструкції
try-with-resources. Якщо виняток викинуто з тіла блокуtry, а під час закриття ресурсу вclose()виникає другий виняток, перший виняток викидається як основний, а другий прикріплюється до нього викликомaddSuppressed(), запобігаючи втраті інформації про збій. -
Чому StackWalker працює швидше, ніж
Thread.currentThread().getStackTrace()абоnew Throwable().getStackTrace()? Відповідь: Старий підхід монолітно збирає повний стек викликів і матеріалізує всі елементи в масивStackTraceElement[].StackWalkerпрацює ліниво (lazily) черезStream<StackFrame>, дозволяючи перервати читання стека на потрібній глибині (наприклад, взяти тільки 1-й фрейм викликаючого) без розбору решти 50 фреймів. -
Чому перехоплення
catch (Throwable t)у сервісі призводить до зомбі-процесів під час витоку пам’яті? Відповідь: Тому що блок перехопитьjava.lang.OutOfMemoryError. Пам’ять купи не звільниться, але процес продовжить існувати з порушеними інваріантами та завислими блокуваннями, не відповідаючи на запити й не падаючи, що перешкоджає оркестратору (Kubernetes) перезапустити дефектний под.
Червоні прапорці (чого категорично не можна говорити)
- ❌ «У вершині ієрархії винятків знаходиться клас Exception» (у вершині знаходиться
Throwable, а над ним лишеObject). - ❌ «Error успадковується від Exception» (
ErrorтаException— паралельні незалежні гілки, що успадковуються відThrowable). - ❌ «Щоб дізнатися, хто викликав мій метод, треба створити new Throwable() і подивитися нульовий елемент масиву» (з Java 9 для цього існує легковажний
StackWalker). - ❌ «Throwable корисно перехоплювати в кожному методі, це гарантує 100% аптайм» (перехоплення
Errorламає стабільність JVM).