В чём разница между Heap и Stack?
4. TLAB — аллокация в Heap может быть быстрее Stack! 5. Virtual Threads (Java 21+) для high-concurrency 6. StackWalker (Java 9+) для анализа стека 7. Monitorьте -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 для long-running серверов (предотвращает resizing overhead). Для CLI-утилит оставьте маленький -Xms для быстрого старта.- Избегайте > 32 ГБ без необходимости (Compressed OOPs)
- Escape Analysis — пишите так, чтобы объекты не “убегали”
- TLAB — аллокация в Heap может быть быстрее Stack!
- Virtual Threads (Java 21+) для high-concurrency
- StackWalker (Java 9+) для анализа стека
- Monitorьте
-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]]