В чому різниця між Heap та Stack?
4. TLAB — алокація в Heap може бути швидшою за Stack! 5. Virtual Threads (Java 21+) для high-concurrency 6. StackWalker (Java 9+) для аналізу стеку 7. Моніторте -Xss — глибока р...
🟢 Junior Level
Heap (Купа) та Stack (Стек) — дві окремі ділянки віртуальної пам’яті, які JVM запитує у ОС.
Навіщо дві ділянки: у них різні патерни доступу. Stack — послідовний (LIFO): дані додаються та видаляються у чіткому порядку викликів методів. Heap — довільний: об’єкти живуть довго і доступні звідусіль.
Проста аналогія:
- Stack — як ваш робочий стіл: швидкий доступ, але мало місця. Використовується для поточних завдань.
- Heap — як склад: величезний, але до предметів потрібно йти. Використовується для зберігання всіх речей.
Основна різниця:
| Stack | Heap |
|---|---|
| Зберігає локальні змінні та посилання | Зберігає об’єкти (new Object()) |
| Швидкий доступ | Повільніше |
| Маленький (1 МБ на потік) | Величезний (гігабайти) |
| Очищується автоматично | Очищується Garbage Collector |
| Приватний для потоку | Спільний для всіх потоків |
Приклад:
public void example() {
int x = 10; // Stack (примітив)
String name = "Ivan"; // Stack (посилання) → "Ivan" в Heap
User user = new User(); // Stack (посилання) → об'єкт User в Heap
}
Помилки:
- Stack переповнений →
StackOverflowError(нескінченна рекурсія) - Heap переповнений →
OutOfMemoryError: Java heap space
🟡 Middle Level
Stack: Пам’ять виконання
Структура стекового фрейму:
Stack Frame (при виклику методу):
├── Local Variable Table ← локальні змінні
├── Operand Stack ← стек операндів для обчислень
├── Dynamic Linking ← посилання на Constant Pool
└── Return Address ← куди повернутися після методу
Життєвий цикл:
- Створюється при виклику методу → видаляється при виході
- Детермінований — пам’ять звільняється миттєво
- Не потребує Garbage Collector
Параметр: -Xss (за замовчуванням 1 МБ)
Heap: Пам’ять стану
Що зберігається:
- Усі об’єкти (
new ...) - Масиви (навіть примітивів
int[]) - Статичні поля класів
- String Pool (пул рядків)
Алокація:
- TLAB (Thread Local Allocation Buffer) — приватний буфер потоку в Eden. Кожен потік алокує у своєму TLAB без синхронізації → дуже швидко.
- Pointer Bumping — алокація = просто зсув вказівника (швидко!)
Параметри: -Xms (початковий), -Xmx (максимальний)
Escape Analysis: JIT оптимізація
Терміни:
- TLAB (Thread Local Allocation Buffer) — приватний буфер потоку в Eden. Кожен потік алокує у своєму TLAB без синхронізації → дуже швидко.
- Compressed OOPs (Compressed Ordinary Object Pointers) — стиснення посилань з 8 до 4 байт при Heap < 32 ГБ. Економія ~30% пам’яті.
- Escape Analysis — аналіз JIT: чи «тікає» об’єкт з методу. Якщо ні — JIT може алокувати його на Stack або взагалі усунути (Scalar Replacement).
// JIT може вирішити, що об'єкт не "утече" з методу
// і створити його на стеку замість купи!
public Point createPoint() {
Point p = new Point(10, 20); // Може бути алокований на Stack!
return p; // Об'єкт "тікає" — JIT не може усунути алокацію.
// JIT аналізує всі шляхи виходу: якщо посилання повертається або
// зберігається в полі — об'єкт "тікає". Перевірити: -XX:+PrintEscapeAnalysis
}
// Scalar Replacement: об'єкт "розвалюється" на змінні
// Lock Elision: synchronized прибирається, якщо об'єкт тільки в одному потоці
Порівняльна таблиця
| Критерій | Stack | Heap |
|---|---|---|
| Видимість | Thread-local | Shared |
| Управління | Апаратне (Stack Pointer) | GC |
| Очищення | Миттєве (Stack Unwinding) | Фонове (STW паузи) |
| Оптимізації | Register Allocation | TLAB, Compressed OOPs |
| Помилки | StackOverflowError |
OutOfMemoryError |
Project Loom (Virtual Threads, Java 21+)
Платформні потоки: стек у нативній пам'яті (1 МБ фіксовано)
Віртуальні потоки: стек у Heap як об'єкт!
→ Mount: копіюється в платформний стек
→ Unmount: копіюється назад у Heap
→ Результат: мільйони потоків!
🔴 Senior Level
Stack Frame Internal
Stack Frame Layout:
┌─────────────────────────────────────┐
│ Local Variable Table │
│ [0] = this (для нестатичних) │
│ [1] = param1 │
│ [2] = param2 │
│ ... │
├─────────────────────────────────────┤
│ Operand Stack (для byte-code ops) │
│ push a, push b, iadd, store c │
├─────────────────────────────────────┤
│ Dynamic Linking → Constant Pool │
│ Return Address → next instruction │
│ Exception Table Reference │
└─────────────────────────────────────┘
Розмір фрейму обчислюється при компіляції!
TLAB (Thread Local Allocation Buffer)
Eden Space:
┌────────────────────────────────────┐
│ Thread 1: [TLAB_1] 64KB │
│ Thread 2: [TLAB_2] 64KB │
│ Thread 3: [TLAB_3] 64KB │
│ ... │
└────────────────────────────────────┘
Алокація в TLAB:
pointer += object_size // Bump-the-pointer
→ 0 синхронізації!
→ Часто швидше, ніж malloc() у багатопотоковому середовищі, бо malloc вимагає синхронізації, а TLAB — ні.
Якщо об'єкт > TLAB → алокація в Eden з синхронізацією
NUMA Awareness
Багатопроцесорний сервер:
CPU 1 ── Memory A (швидка)
CPU 2 ── Memory B (швидка для CPU 2, повільна для CPU 1)
-XX:+UseNUMA → JVM розподіляє Heap по NUMA вузлах
→ Потоки на CPU 1 алокують в Memory A
→ +10-20% продуктивності на Highload!
Object Layout in Memory
Object Header (12-16 байт):
├── Mark Word (8 байт)
│ ├── Hash Code (31 біт)
│ ├── Age (4 біти) → для GC
│ ├── Lock State (2 біти)
│ └── GC bits
├── Klass Pointer (4 байти з Compressed OOPs)
│ → Вказівник на метадані класу в Metaspace
├── Instance Data (поля об'єкта)
└── Padding (вирівнювання до 8 байт)
Compressed OOPs: 32 ГБ поріг
64-бітний вказівник = 8 байт
Compressed OOPs = 4 байти (зсув × 8)
Максимальна адреса: 2^32 × 8 = 32 ГБ
→ 31 ГБ Heap швидше, ніж 33 ГБ!
→ Переступання 32 ГБ = -10-15% продуктивності
→ Економія L1/L2 кешів процесора
Future: Project Valhalla (Value Types)
// Майбутнє: зберігання за значенням, а не за посиланням
public final class Point {
public final int x;
public final int y;
}
// Зараз:
Point[] arr = new Point[1000]; // 1000 об'єктів в Heap + 1000 посилань
// Майбутнє (Value Types):
Point[] arr = new Point[1000]; // Зберігаються прямо в масиві!
→ Без заголовків об'єктів
→ Без непрямості
→ Кеш-локальність × 10
Production Experience
Реальний сценарій: 32 ГБ поріг убив продуктивність
- Сервер: 64 ГБ RAM,
-Xmx48g - Переступили 32 ГБ → Compressed OOPs вимкнулись
- Результат: -15% throughput, +20% latency
- Рішення:
-Xmx30g→ Compressed OOPs увімкнено → все полагодилось
Best Practices
-Xms = -Xmxу production для довготривалих серверів (запобігає overhead зміни розміру). Для CLI-утиліт залиште маленький -Xms для швидкого старту.- Уникайте > 32 ГБ без необхідності (Compressed OOPs)
- Escape Analysis — пишіть так, щоб об’єкти не “утікали”
- TLAB — алокація в Heap може бути швидшою за Stack!
- Virtual Threads (Java 21+) для high-concurrency
- StackWalker (Java 9+) для аналізу стеку
- Моніторте
-Xss— глибока рекурсія = StackOverflowError
Резюме для Senior
- Stack = execution (швидкий, детермінований, thread-local)
- Heap = data (величезний, shared, потребує GC)
- TLAB = pointer bumping → алокація швидша malloc
- Escape Analysis = Stack Allocation + Lock Elision
- Compressed OOPs = 32 ГБ поріг → критично для performance
- NUMA = розподіляйте Heap по вузлах для багатопроцесорних серверів
- Object Layout = Mark Word + Klass Pointer + Data + Padding
- Virtual Threads = стек у Heap → мільйони потоків
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
- Stack — LIFO, thread-local, зберігає локальні змінні та посилання; Heap — shared, зберігає об’єкти та масиви
- Stack очищується автоматично при виході з методу, Heap — Garbage Collector
- Stack Overflow →
StackOverflowError, Heap Overflow →OutOfMemoryError - TLAB дозволяє алокувати об’єкти в Heap без синхронізації (pointer bumping)
- Escape Analysis (JIT) може алокувати об’єкт на Stack, якщо він не «тікає» з методу
- Compressed OOPs: при Heap < 32 ГБ посилання стискаються з 8 до 4 байт → 31 ГБ часто швидше 33 ГБ
- Object Layout: Mark Word (8 байт) + Klass Pointer (4 байти) + Data + Padding
- Virtual Threads (Java 21+): стек у Heap, ~2 КБ на потік замість 1 МБ
Часті уточнюючі запитання:
- Чому 31 ГБ Heap швидше 33 ГБ? — Compressed OOPs вимикаються вище 32 ГБ, вказівники подвоюються → більше cache miss
- Чи може об’єкт бути створений на Stack? — Так, через Escape Analysis JIT може зробити Stack Allocation або Scalar Replacement
- Що таке TLAB? — Thread Local Allocation Buffer, приватний буфер потоку в Eden для lock-free алокації
- Який параметр задає розмір Stack? —
-Xss(за замовчуванням 1 МБ)
Червоні прапорці (НЕ говорити):
- «Heap та Stack — це одне й те саме, просто різні назви» — це дві різні області пам’яті
- «GC чистить Stack» — Stack очищується автоматично при виході з методу
- «Об’єкти завжди створюються в Heap» — JIT може алокувати на Stack через Escape Analysis
Пов’язані теми:
- [[2. Що зберігається в Heap]]
- [[3. Що зберігається в Stack]]
- [[4. Що таке Garbage Collection]]
- [[8. Що таке покоління в GC (young, old, metaspace)]]
- [[18. Що таке параметри -Xms та -Xmx]]