В чому різниця між Error та Exception
Обидва класи — java.lang.Error та java.lang.Exception — є прямими нащадками суперкласу java.lang.Throwable, проте вони репрезентують два принципово різних типи проблем у системі.
🟢 Junior Level
Обидва класи — java.lang.Error та java.lang.Exception — є прямими нащадками суперкласу java.lang.Throwable, проте вони репрезентують два принципово різних типи проблем у системі.
Головні відмінності
| Критерій | java.lang.Error |
java.lang.Exception |
|---|---|---|
| Природа проблеми | Катастрофічний збій віртуальної машини JVM або вичерпання системних ресурсів ОС. | Нештатна ситуація в логіці застосунку або тимчасовий збій оточення. |
| Можливість відновлення | Невідновлювано. Застосунок не здатний програмно усунути причину збою. | Відновлювано. Програма може перехопити помилку, застосувати fallback і продовжити роботу. |
| Контроль компілятора | Неперевірюване (Unchecked). | Поділяється на Checked (IOException) та Unchecked (RuntimeException). |
| Правило обробки | Не перехоплювати. Процес повинен бути аварійно завершений. | Обробляти локально в try-catch або централізовано. |
| Типові приклади | OutOfMemoryError, StackOverflowError, NoClassDefFoundError. |
NullPointerException, IllegalArgumentException, IOException, SQLException. |
Наочні приклади
// 1. Приклад Error: вичерпання пам'яті JVM (OutOfMemoryError)
// Застосунок безсилий: вільної пам'яті в Heap фізично більше немає
List<byte[]> memoryLeak = new ArrayList<>();
while (true) {
memoryLeak.add(new byte[1024 * 1024]); // Виділення по 1 МБ
}
// 2. Приклад Exception: відсутність файлу (IOException)
// Застосунок може легко відновитися, прочитавши дефолтну конфігурацію
try {
return Files.readString(Path.of("custom-config.json"));
} catch (IOException ex) {
return Files.readString(Path.of("default-config.json"));
}
🟡 Middle Level
Розбір класичної пари: NoClassDefFoundError vs ClassNotFoundException
Одне з найпопулярніших запитань на технічних інтерв’ю з Java Core:
java.lang.Throwable
│
┌────────────────┴────────────────┐
▼ ▼
java.lang.Error java.lang.Exception
│ │
LinkageError ClassNotFoundException
│ (Checked Exception)
NoClassDefFoundError
ClassNotFoundException(Checked Exception):- Виникає при динамічному завантаженні класу за рядковим ім’ям у runtime:
Class.forName("com.mysql.cj.jdbc.Driver"),ClassLoader.loadClass(). - Означає: у поточному ClassLoader клас із такою назвою фізично відсутній у classpath.
- Метод зобов’язаний оголосити
throws ClassNotFoundException, а викликаючий код може обробити цю ситуацію (наприклад, спробувати альтернативний драйвер або плагін).
- Виникає при динамічному завантаженні класу за рядковим ім’ям у runtime:
NoClassDefFoundError(Error):- Виникає, коли клас був присутній на етапі компіляції, але зник із classpath під час виконання.
- Інший частий сценарій: під час першої спроби завантаження класу впав його статичний блок ініціалізації (
static { ... }), викинувшиExceptionInInitializerError. При всіх наступних зверненнях до цього класу в межах того самого ClassLoader JVM викидатимеNoClassDefFoundError, оскільки клас помічений як непридатний до використання.
Різновиди OutOfMemoryError (OOM)
Помилка OutOfMemoryError не монолітна й поділяється на ключові підвиди:
OutOfMemoryError: Java heap space: Переповнена основна купа (Eden + Survivor + Old Gen). Збирач сміття (GC) не зміг вивільнити пам’ять. Причина: витік посилань у колекціях, важкі статичні кеші.OutOfMemoryError: Metaspace: Вичерпана нативна пам’ять під метадані класів (Class metadata). Причина: нескінченна генерація динамічних проксі (CGLIB, Spring AOP, Hibernate) без вивантаження завантажувача класів (ClassLoader leak).OutOfMemoryError: GC overhead limit exceeded: GC витрачає понад 98% процесорного часу на збирання сміття, але вивільняє менше 2% пам’яті купи.OutOfMemoryError: Unable to create new native thread: Досягнуто ліміту операційної системи на кількість потоків для процесу (ulimit -uв Linux).
StackOverflowError vs OutOfMemoryError
StackOverflowError: Вичерпано пам’ять стека викликів конкретного потоку (розмір задається прапорцем-Xss, за замовчуванням 1 МБ). Причина: нескінченна або надмірно глибока рекурсія, циклічні залежності у сутностях.OutOfMemoryError: Вичерпано спільну пам’ять купи (-Xmx) або нативну пам’ять процесу.
🔴 Senior Level
Чому перехоплювати Error — це груба архітектурна помилка?
Коли в потоці виникає java.lang.Error (зокрема підкласи VirtualMachineError):
- Порушення інваріантів пам’яті: Потік міг бути перерваний посеред модифікації спільної структури даних (наприклад, балансування
ConcurrentHashMap). - Руйнування моніторів: Блокування
synchronizedзнімаються автоматично під час розмотування стека, залишаючи об’єкти в напівзруйнованому неузгодженому стані. - Ефект «Зомбі-процесу»: Якщо перехопити
OutOfMemoryErrorчерезcatch (Throwable t)абоcatch (Error e), застосунок продовжить висіти в пам’яті. Він перестане відповідати на вхідні запити або зависне на першому ж логуванні, але процес не впаде, через що Kubernetes не зможе вчасно перезапустити несправний под.
Патерн “Crash-Only Software” та прапорці JVM
У сучасних хмарних архітектурах (Cloud-Native / Kubernetes) діє непохитний принцип: при критичній помилці процес повинен впасти негайно (Fail-Fast & Crash), щоб оркестратор підняв чистий і стабільний інстанс.
Рекомендовані налаштування JVM у production для контейнерів:
# 1. Миттєво аварійно завершити JVM при OOM (повернення ненульового exit code)
-XX:+CrashOnOutOfMemoryError
# 2. Або завершити процес через миттєвий вихід
-XX:+ExitOnOutOfMemoryError
# 3. Зберегти дамп купи для подальшого розслідування витоку пам'яті
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/dumps/oom.hprof
Демонстрація: як падіння у static-блоці породжує NoClassDefFoundError
package com.example.error;
public class ErrorDemo {
// Клас із дефектним статичним блоком ініціалізації
static class BrokenClass {
static {
// Викличе ArithmeticException всередині static ініціалізатора
int badDivision = 10 / 0;
}
public static void test() {
System.out.println("Hello");
}
}
public static void main(String[] args) {
// 1-ша спроба: викине ExceptionInInitializerError
try {
BrokenClass.test();
} catch (Throwable t) {
System.out.println("First call failed: " + t.getClass().getSimpleName());
// Виведе: ExceptionInInitializerError
}
// 2-га спроба: клас уже позначений JVM як дефектний -> викине NoClassDefFoundError!
try {
BrokenClass.test();
} catch (Throwable t) {
System.out.println("Second call failed: " + t.getClass().getSimpleName());
// Виведе: NoClassDefFoundError!
}
}
}
🎯 Шпаргалка для інтерв’ю
30-секундна відповідь
Errorпозначає критичні, невідновлювані системні збої JVM або вичерпання ресурсів операційної системи (OutOfMemoryError,StackOverflowError,NoClassDefFoundError). Прикладний код не повинен перехоплюватиError, оскільки середовище виконання переходить у нестабільний стан; процес має негайно завершитися, щоб оркестратор (Kubernetes) перезапустив под.Exceptionпозначає передбачувані помилки рівня застосунку (IOException,NullPointerException, валідація, бізнес-збої), які програма може і зобов’язана обробити.ClassNotFoundException— це перевірюваний виняток при явному динамічному пошуку класу за ім’ям уClass.forName(), тоді якNoClassDefFoundError— це фатальнийErrorкомпонування (лінковки), що виникає, коли клас був під час компіляції, але відсутній у рантаймі, або коли впав його статичний блок ініціалізаціїstatic {}.
4 каверзних питання з відповідями
-
Що станеться, якщо блок
catch (OutOfMemoryError e)успішно перехопить вичерпання пам’яті? Відповідь: Застосунок продовжить працювати у пошкодженому стані: блокування синхронізації зняті у випадкових місцях, пули потоків і транзакції розірвані, а будь-яка наступна операція (навіть виділення пам’яті під рядок логу) знову викинеOutOfMemoryError. Сервіс перетворюється на «зомбі». Правильне рішення — аварійно завершити процес за допомогою прапорця-XX:+CrashOnOutOfMemoryError. -
У чому різниця між
NoClassDefFoundErrorтаClassNotFoundException? Відповідь:ClassNotFoundException— це перевірюваний виняток, який виникає при динамічному рефлексивному завантаженні (Class.forName(),ClassLoader.loadClass()), коли файл класу фізично не знайдено.NoClassDefFoundError— це системна помилка (Error), що виникає при спробі використати клас, який успішно скомпілювався, але в рантаймі відсутній у classpath або впав під час виконання статичного блока ініціалізації<clinit>. -
До якої категорії належить
AssertionErrorі чому? Відповідь:AssertionErrorє прямим нащадкомError. Він викидається операторомassertпри порушенні внутрішніх інваріантів алгоритму. Розробники Java зробили йогоError, щоб він навмисно не перехоплювався стандартними блокамиcatch (Exception e), гарантуючи аварійну зупинку програми при порушенні фундаментальних інваріантів. -
У чому різниця між
OutOfMemoryError: Java heap spaceтаOutOfMemoryError: Metaspace? Відповідь:Java heap spaceозначає вичерпання пам’яті в купі для створення нових об’єктів бізнес-логіки.Metaspaceозначає вичерпання нативної пам’яті операційної системи для збереження метаданих класів (пул констант, байткод методів, vtable), що найчастіше викликано витоком завантажувачів класів (ClassLoader leak) або неконтрольованим створенням CGLIB/Spring-проксі.
Червоні прапорці (чого категорично не можна говорити)
- ❌ «Error — це перевірюваний виняток, який потребує обов’язкового блоку try-catch» (Error є неперевірюваним / unchecked).
- ❌ «Якщо перехопити StackOverflowError, можна спокійно продовжити виконання методу» (стек потоку зруйнований, потік необхідно негайно завершити).
- ❌ «NoClassDefFoundError і ClassNotFoundException — це одна й та сама помилка, просто з різними назвами» (один —
Errorлінковки, другий — checkedExceptionдинамічного завантаження). - ❌ «У надійних корпоративних системах усі помилки потрібно лікувати через catch (Error e)» (грубий антипатерн, що призводить до зависання систем у продакшені).