Якщо ти натрапив на цю статтю — тобі вже точно 30+ (плак, плак 😭)

Це — перша стаття з циклу про багатопоточність, де я детально розповім про структуру пам’яті JVM
Java Memory Structure

Stack – пам’ять, що живе з кожним потоком
Стекова пам'ять використовується для зберігання даних, пов'язаних з виконанням потоку (thread). Кожен потік у Java має свій власний, незалежний стек.
Для чого потрібен стек?
Основне призначення — керування викликами методів. Коли запускається новий метод, JVM створює в стеку спеціальний блок — stack frame (фрейм стека). Цей фрейм тримає всі дані, необхідні для роботи методу, і існує лише на час його виконання.
Що зберігається у Stack Frame?
Локальні змінні (Local Variables): Це змінні, оголошені всередині методу:
Примітивні типи (Primitive types):
int
,double
,boolean
,char
і т.д. Їхні значення зберігаються безпосередньо у стеку.Типи-посилання (Reference types): Об'єкти, наприклад,
String
,ArrayList
або будь-який ваш клас. У стеку зберігається лише посилання (адреса) на об'єкт. Сам об'єкт фізично знаходиться в Купі (Heap).
Приклад у коді:
public void processData() {
int count = 10; // 1. The variable 'count' and its value 10 are stored on the stack.
String message = "Hello"; // 2. The reference 'message' is stored on the stack.
// The "Hello" object itself is created in the Heap.
calculateSum(count); // 3. A new frame for the calculateSum() method is created
// and pushed onto the top of the stack.
}
Стек операндів (Operand Stack)
Це проміжний робочий простір для методу. JVM використовує його для виконання операцій. Наприклад, у виразі 2 + 3
числа 2 і 3 будуть поміщені в стек операндів перед тим, як виконається операція додавання.
Посилання на дані класу (Reference to Class Data)
Кожен фрейм також містить посилання на метадані класу, які зберігаються в Metaspace — щоб JVM знала, який саме метод виконується та як ним керувати.
Як працює стек?
Стек — це структура LIFO (Last-In, First-Out), тобто «останнім прийшов — першим пішов». Коли метод завершує роботу, його фрейм просто вилучається зі стека, і керування повертається попередньому методу (фрейму).

Обмеження
Розмір стека є фіксованим і значно меншим за розмір купи. Якщо кількість вкладених викликів методів (наприклад, при глибокій рекурсії) перевищує доступний розмір стека, виникає помилка java.lang.StackOverflowError
.
PC registers — твій внутрішній GPS по байт-коду
PC-регістр (Program Counter Register) — це маленький, але дуже важливий покажчик, який є у кожного потоку в JVM. Він зберігає адресу наступної байт-код інструкції, яку JVM має виконати.
Уяви собі, що PC-регістр — це твій навігатор у світі байт-коду, що точно каже, де ти зараз і куди рухатись далі.
Приклад
Нехай у нас є простий метод:
public int add(int a, int b) {
int result = a + b;
return result;
}
Для JVM цей код перетворюється на набір простих інструкцій (байт-код). Уявімо його у спрощеному вигляді, де кожна інструкція має свою адресу:
Адреса | Інструкція | Пояснення |
---|---|---|
|
| Завантажити першу змінну ( |
|
| Завантажити другу змінну ( |
|
| Додати два числа |
|
| Зберегти результат у змінну |
|
| Завантажити |
|
| Повернути значення з методу |
Тепер подивимося, як буде змінюватися значення PC-регістра під час виконання цього методу в одному потоці.
Крок за кроком:
Початок: Метод
add
викликається. PC-регістр вказує на першу інструкцію.Значення PC-регістра:
0
Крок 1: JVM виконує інструкцію за адресою
0
(iload_1
). Після цього PC-регістр оновлюється, щоб вказувати на наступну інструкцію.Значення PC-регістра:
1
Крок 2: JVM виконує інструкцію
iload_2
за адресою1
.Значення PC-регістра:
2
Крок 3: JVM виконує інструкцію
iadd
за адресою2
.Значення PC-регістра:
3
... і так далі до кінця методу.
Навіщо це потрібно для багатопоточності?
Тепер уявіть, що у вас два потоки.
Потік А виконує наш метод
add
і зупинився на кроці 3 (його PC-регістр =3
).Потік Б виконує якийсь інший код.
Коли операційна система вирішує повернути час процесора Потоку А, JVM дивиться на його PC-регістр. Вона бачить значення 3
і точно знає, що наступною потрібно виконати інструкцію istore_3
.
Native Method Stack - Міст до світу поза JVM
Native Method Stack — це спеціальна область памʼяті, створена окремо для кожного потоку, яка використовується при виконанні нативних методів — тобто таких, що написані не на Java, а на C, C++ або іншій системній мові.
Нативні методи в Java оголошуються з ключовим словом native
. Їхній код не існує у байт-коді — він лежить у зовнішній бібліотеці (наприклад, *.dll
на Windows або *.so
на Linux), яка підключається до JVM через JNI (Java Native Interface).
Приклад оголошення нативного методу: public native void performSystemTask();
Як працює Native Method Stack
У звичайному Java-методі JVM створює stack frame у Java Stack.
Але коли викликається native
-метод, все змінюється:
Передача керування поза JVM:
Потік "перестрибує" з Java-середовища в зовнішній (нативний) код.Створення фрейму в Native Stack:
Для виконання створюється новий фрейм, але вже у Native Method Stack. Він зберігає параметри, локальні змінні та інший стан — за правилами мови C/C++, а не JVM.Повернення до JVM:
Після завершення виклику фрейм видаляється, результат повертається в Java-код, і виконання продовжується там, де зупинилось.
Простими словами, це паралельний стек, який служить мостом між безпечним, керованим середовищем JVM та низькорівневим кодом операційної системи.
Для чого це потрібно?
Викликати системні API операційної системи
Доступ до апаратного забезпечення
Оптимізація продуктивності в критичних місцях
Підключення до бібліотек, написаних не на Java
HEAP – великий, спільний світ об'єктів
Heap (Купа) — це основна область пам'яті в JVM, яка використовується для динамічного розміщення всіх об'єктів та масивів Java під час виконання програми. Купа є спільною для всіх потоків (threads) програми. Це означає, що об'єкти, створені в одному потоці, можуть бути доступні з іншого, що робить необхідною синхронізацію при роботі з даними у багатопотоковому середовищі.
Що зберігається в Купі?
Об'єкти: Усі об'єкти, створені за допомогою оператора
new
(наприклад,new MyClass()
), розміщуються в Купі.Змінні екземпляра (Instance variables): Поля, що належать об'єкту, зберігаються разом із ним.
Масиви: Будь-які масиви, незалежно від типу даних, також створюються в Купі.
Статичні змінні зберігаються тут разом з їхнім Class-об'єктом. Під час завантаження JVM створює в пам'яті спеціальний об'єкт типу
java.lang.Class
. Цей об'єкт представляє сам клас, а не його екземпляр. Усі статичні змінні вашого класу є полями цьогоClass
об'єкта. Отже, вони живуть разом з ним у Купі.Thread pool
String pool: До Java 7 - Пул рядків знаходився в окремій області пам'яті під назвою
PermGen
. З Java 8:PermGen
був замінений наMetaspace
, але пул рядків не переїхав доMetaspace
. Він так і залишився в Купі.
Управління пам'яттю
Купа — це єдина область пам'яті, яка управляється автоматичним Збирачем сміття (Garbage Collector). Він періодично знаходить і видаляє об'єкти, на які більше не існує посилань, звільняючи таким чином місце. Це позбавляє програміста необхідності вручну керувати пам'яттю.
Структура Купи

Для оптимізації роботи збирача сміття Купа зазвичай поділяється на кілька логічних частин, які називаються поколіннями:
Young Generation (Молоде покоління): Тут "народжуються" всі нові об'єкти. Більшість об'єктів мають короткий час життя і видаляються звідси під час "малих" зборів сміття (Minor GC). Це покоління, у свою чергу, поділяється на:
Eden Space: Простір, куди спочатку потрапляють усі нові об'єкти.
Survivor Spaces (S0 та S1): Два простори для об'єктів, що "вижили" після одного чи кількох циклів збирання сміття в Eden.
Old Generation (Старе покоління): Сюди переміщуються об'єкти, які пережили достатню кількість циклів збирання сміття в Young Generation. Вважається, що ці об'єкти є "довгожителями" і перевіряються збирачем сміття значно рідше під час "повних" зборів сміття (Major/Full GC).
METASPACE — Архів метаданих про класи
Metaspace — це спеціальна область пам'яті в JVM, розташована в нативній пам'яті, яка зберігає метадані про ваші класи.
Що зберігає?
Metaspace містить "креслення" або метадані ваших класів: їхню структуру, інформацію про поля та методи, байт-код, константи часу виконання та анотації. Він не зберігає об'єкти (екземпляри) класів — вони знаходяться в Купі (Heap).
Де знаходиться?
Це його головна відмінність: Metaspace розташований у нативній пам'яті операційної системи, а не є частиною Купи (Heap).
Хто управляє?
Ним повністю управляє JVM. Віртуальна машина запитує пам'ять в операційної системи та звільняє її, коли класи вивантажуються (що трапляється рідко).
Який розмір?
За замовчуванням розмір Metaspace обмежений лише об'ємом доступної нативної пам'яті. Він може динамічно розширюватися за потреби, що вирішує проблему з переповненням, яка існувала раніше.
Підсумок
Stack — це швидка, обмежена пам’ять, унікальна для кожного потоку. Тут живуть локальні змінні, параметри методів і інформація про виклики. Працює за принципом LIFO — останній виклик обробляється першим. Помилка StackOverflow виникає, коли стек переповнюється, наприклад, через глибоку рекурсію.
PC-регістр — це маленький «указівник» у кожному потоці, який точно знає, яка байт-код інструкція має виконуватися наступною. Завдяки йому JVM контролює, де саме знаходиться потік у виконанні.
Native Method Stack — окремий стек для виконання «нативних» методів, написаних не на Java, а на мовах типу C/C++. Забезпечує зв’язок JVM з операційною системою та низькорівневим кодом.
Heap (Купа) — велика, спільна для всіх потоків пам’ять, де розміщуються всі об’єкти, масиви, а також статичні змінні через
Class
-об’єкти. Управляється автоматичним збирачем сміття (Garbage Collector), який звільняє пам’ять від непотрібних об’єктів.Metaspace — спеціальна область у нативній пам’яті, де JVM зберігає метадані класів: опис структури, методи, поля та анотації. Це «архіватор» класів, який динамічно розширюється і не є частиною Heap.