Що таке параметри -Xms та -Xmx
Прапорці -Xms та -Xmx — це базові параметри віртуальної машини Java (JVM), що керують розміром купи (Java Heap), у якій виділяється пам'ять під усі об'єкти:
🟢 Junior Level
Прапорці -Xms та -Xmx — це базові параметри віртуальної машини Java (JVM), що керують розміром купи (Java Heap), у якій виділяється пам’ять під усі об’єкти:
-Xms<size>(Memory Start): початковий (мінімальний) розмір купи при запуску JVM.-Xmx<size>(Memory Maximum): максимально дозволений розмір купи. При спробі застосунку перевищити цей ліміт (і якщо Garbage Collector не зміг звільнити пам’ять) викидається помилкаjava.lang.OutOfMemoryError: Java heap space.
# Запуск застосунку з початковою купою 1 ГБ та максимальною 4 ГБ
java -Xms1g -Xmx4g -jar app.jar
Суфікси одиниць виміру
kабоK— кілобайтиmабоM— мегабайтиgабоG— гігабайти
java -Xms512m -Xmx2g -jar app.jar
Значення за замовчуванням (Ergonomics)
Якщо прапорці не задані явно, JVM розраховує їх автоматично через механізм JVM Ergonomics, спираючись на доступну фізичну RAM хоста або ліміти cgroups (контейнера):
- За замовчуванням
-Xmsстановить приблизно $1/64$ від фізичної пам’яті. - За замовчуванням
-Xmxстановить приблизно $1/4$ від фізичної пам’яті (але зазвичай не більше 32 ГБ через обмеження Compressed OOPs).
🟡 Middle Level
1. Механіка пам’яті: Reserved vs Committed
При запуску JVM з параметрами -Xms та -Xmx відбуваються два фундаментальні етапи взаємодії з пам’яттю операційної системи:
Віртуальний адресний простір ОС:
[================== Reserved (-Xmx: 4 GB) ==================]
[=== Committed (-Xms: 1 GB) ===][........ Uncommitted ......]
↑ Реальні сторінки RAM ↑ Адреси зарезервовані,
але сторінки не виділені
- Reserved (Зарезервована пам’ять):
Під час старту JVM виконує системний виклик (
mmapу Linux/POSIX із прапорцемPROT_NONEабоVirtualAllocізMEM_RESERVEу Windows) на повний розмір-Xmx. Це неперервний діапазон адрес віртуальної пам’яті, що резервується за процесом, аби купа могла зростати неперервно. Фізична RAM при цьому ще не витрачається. - Committed (Зафіксована пам’ять):
Пам’ять в обсязі
-Xmsпозначається як доступна для читання та запису (PROT_READ | PROT_WRITEабоMEM_COMMIT). ОС виділяє реальні фізичні сторінки оперативної пам’яті в міру першого звернення до них (lazy allocation / demand paging).
2. Чому в production рекомендується -Xms = -Xmx?
Якщо -Xms < -Xmx, купа динамічно зростає та стискається залежно від навантаження, орієнтуючись на прапорці -XX:MinHeapFreeRatio (за замовчуванням 40%) та -XX:MaxHeapFreeRatio (за замовчуванням 70%):
- Коли частка вільної пам’яті після GC падає нижче
MinHeapFreeRatio, JVM намагається розширити купу до-Xmx. - Перед розширенням купи JVM часто ініціює додаткове збирання сміття (аж до Full GC у традиційних збирачах), щоб спробувати звільнити місце без звернення до ОС.
- Сам системний виклик
mmap/mprotect/VirtualAllocта виділення сторінок ОС породжують Stop-The-World паузи та латентність.
Переваги рівності -Xms = -Xmx:
- Передбачуваність: розмір купи статичний з першої секунди роботи;
- Відсутність пауз на ресайз: JVM не витрачає процесорний час і не гальмує потоки застосунку запитами на розширення купи до ядра ОС;
- Запобігання OOM Killer: якщо на хості закінчиться пам’ять через 5 годин під навантаженням, купа, що динамічно росте, призведе до аварійного завершення процесу операційною системою. Статичний розмір гарантує, що якщо пам’ять виділилася на старті, застосунок продовжить роботу.
Винятки: CLI-утиліти, локальна розробка, безсерверні функції (AWS Lambda) та мікросервіси в динамічних кластерах, де пріоритетом є мінімальне споживання RAM під час простою.
3. Налаштування пам’яті в контейнерах (Docker / Kubernetes)
Жорстко задавати -Xms та -Xmx у мегабайтах усередині Dockerfile або deployment-маніфестів — антипатерн, оскільки при зміні лімітів пода в Kubernetes (resources.limits.memory) параметри JVM доведеться змінювати вручну.
Починаючи з Java 8u191 та Java 10+, JVM повноцінно підтримує Container Awareness (cgroups v1/v2). Для налаштування пам’яті використовують процентні співвідношення:
# Рекомендований production-шаблон для мікросервісів у контейнері:
java -XX:InitialRAMPercentage=75.0 \
-XX:MaxRAMPercentage=75.0 \
-XX:MinRAMPercentage=75.0 \
-jar app.jar
-XX:MaxRAMPercentage=75.0: виділяє 75% від загального ліміту пам’яті контейнера під Java Heap.- Решта 25% життєво необхідні для Non-Heap пам’яті (Metaspace, Thread Stacks, Code Cache, GC-структури, Direct Memory) та ядра ОС.
🔴 Senior Level
1. Формула RSS: чому процес споживає більше за -Xmx
Одна з найчастіших аварій у Kubernetes — примусове завершення пода через OOMKilled (Exit Code 137), хоча метрики Heap показують, що купа заповнена лише наполовину.
Реальне споживання фізичної пам’яті процесом в ОС (Resident Set Size — RSS) розраховується за формулою:
\[\text{RSS} \approx \text{Heap (-Xmx)} + \text{Metaspace} + \text{CodeCache} + (\text{Threads} \times \text{-Xss}) + \text{DirectMemory} + \text{GC Overhead} + \text{Native Libraries}\]+---------------------------------------------------------------+
| Загальний процес JVM в ОС (RSS) |
| +-------------------------+ +-------------------------------+ |
| | Java Heap | | Non-Heap | |
| | (-Xms / -Xmx / | | - Metaspace | |
| | MaxRAMPercentage) | | - Thread Stacks (-Xss) | |
| | | | - Code Cache (JIT-код) | |
| | | | - Direct Byte Buffers | |
| | | | - GC Card Tables / Marks | |
| | | | - Native memory (glibc malloc)| |
| +-------------------------+ +-------------------------------+ |
+---------------------------------------------------------------+
- Thread Stacks: кожен потік за замовчуванням забирає 1 МБ пам’яті (
-Xss1m). При 1000 потоках це 1 ГБ Native RAM поза купою. - GC Overhead: збирачі сміття зберігають службові структури даних у native memory. Наприклад, для G1 GC таблиці запам’ятованих наборів (RSet) можуть споживати додатково до 10–15% від розміру купи.
- Direct Memory: буфери вводу-виводу (NIO
ByteBuffer.allocateDirect()), які активно використовуються в Netty, gRPC, Kafka. - Інструмент контролю:
-XX:NativeMemoryTracking=summaryта утилітаjcmd <pid> VM.native_memory summary.
2. Межа 32 ГБ та Compressed OOPs
У 64-бітних архітектурах покажчики на об’єкти займають 8 байтів (64 біти), що збільшує споживання купи на 30–40% порівняно з 32-бітними системами. Для оптимізації JVM використовує технологію Compressed OOPs (Ordinary Object Pointers) (-XX:+UseCompressedOops).
JVM зберігає покажчики не як байтові адреси, а зі зсувом на 3 біти вправо (оскільки всі об’єкти в Java вирівняні за межею 8 байтів). Це дозволяє адресувати $2^{32} \times 8 = 32\text{ ГБ}$ пам’яті за допомогою 32-бітних покажчиків:
\[\text{Поріг вимкнення Compressed OOPs} \approx 32\text{ ГБ}\][!WARNING] Ніколи не встановлюйте
-Xmx32g! Якщо задати купу рівно 32 ГБ (або 32768m), JVM виходить за діапазон адресації зі зсувом. Compressed OOPs вимикаються, і JVM переходить на повноцінні 64-бітні покажчики. У результаті купа-Xmx32gвміщує менше реальних об’єктів, ніж купа-Xmx31g, а навантаження на кеші процесора (L1/L2/L3) та пропускна здатність шини пам’яті різко деградують. Безпечний максимум для стиснутих покажчиків зазвичай становить близько31g(або31744m).
3. Оптимізація запуску: -XX:+AlwaysPreTouch
За замовчуванням ОС виділяє фізичну пам’ять відкладено (page fault on demand). Коли Java-застосунок запускається і починає інтенсивно обробляти запити, кожне перше звернення до сторінки пам’яті викликає переривання процесора (page fault), змушуючи ядро ОС шукати вільний фізичний фрейм і зануляти його. Це призводить до просідань латентності (Tail Latency spikes) у перші хвилини роботи застосунку.
Прапорець -XX:+AlwaysPreTouch:
- Змушує JVM під час старту та ініціалізації купи обійти всі сторінки пам’яті й записати в кожну нульовий байт.
- Виділення сторінок відбувається паралельно в кілька потоків (
-XX:PreTouchParallelChunkSize). - Ціна: час старту JVM збільшується (на купі у 64 ГБ старт може тривати 5–15 секунд).
- Вигода: повне усунення затримок через page fault під час бойової роботи сервісу.
4. Transparent Huge Pages (THP) та збирачі сміття
У Linux стандартний розмір сторінки віртуальної пам’яті дорівнює 4 КБ. Huge Pages збільшують цей розмір до 2 МБ або 1 ГБ, зменшуючи кількість записів у TLB-кеші процесора (Translation Lookaside Buffer).
Однак механізм Transparent Huge Pages (THP) ядра Linux намагається об’єднувати 4 КБ сторінки у 2 МБ на льоту за допомогою фонового демона khugepaged. Під час роботи із сучасними GC (особливо G1 GC, ZGC, Shenandoah) це викликає катастрофічні затримки:
- G1 оперує невеликими регіонами (1–32 МБ) і часто переміщує дані.
- Дефрагментація THP блокує системні виклики виділення пам’яті, викликаючи непередбачені паузи Stop-the-World на десятки й сотні мілісекунд.
- Рекомендація для High-Load: вимикати THP на рівні ОС (
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled) або використовувати статичні Huge Pages (-XX:+UseLargePages).
🎯 Шпаргалка для інтерв’ю
30-секундна відповідь для співбесіди
«Параметри
-Xmsта-Xmxзадають початковий та максимальний розмір купи JVM.-Xmxвизначає межу зарезервованого віртуального адресного простору (reserved), а-Xms— початково виділену фізичну пам’ять (committed). У production заведено задавати-Xms = -Xmx(або-XX:InitialRAMPercentage = -XX:MaxRAMPercentage), щоб усунути накладні витрати та GC-паузи на динамічний ресайз купи. Важливо пам’ятати, що загальний процес Java (RSS) завжди споживає більше пам’яті, ніж-Xmx, через Metaspace, Thread Stacks, Direct Buffers та службові структури GC, тому під Heap у контейнерах виділяють не більше 70–75% RAM».
Питання підвищеної складності з відповідями
- Що станеться, якщо задати
-Xms4g -Xmx4g, але на сервері доступно лише 2 ГБ вільної фізичної пам’яті?- Відповідь: Без прапорця
-XX:+AlwaysPreTouchзастосунок успішно запуститься, оскільки Linux виділить віртуальні адреси й зафіксує їх без миттєвої прив’язки до фізичних фреймів (overcommit). Проте щойно JVM спробує фактично записати дані в пам’ять понад 2 ГБ, ядро Linux завершить процес сигналом SIGKILL через OOM Killer. Із прапорцем-XX:+AlwaysPreTouchJVM впаде одразу в момент старту з помилкою нестачі пам’яті від ОС.
- Відповідь: Без прапорця
- Чому купа
-Xmx31gможе бути ефективнішою, ніж-Xmx33g?- Відповідь: До межі ~32 ГБ JVM використовує Compressed OOPs (стиснуті 32-бітні посилання на об’єкти). При перевищенні цієї межі (починаючи приблизно з 32 ГБ) стиснення вимикається, і всі посилання стають 64-бітними (8 байтів замість 4). Це збільшує розмір графу об’єктів у пам’яті на 20–30%, вимиває процесорні кеші та погіршує продуктивність. Щоб отримати реальний виграш у корисній місткості після вимкнення Compressed OOPs, купу потрібно збільшувати відразу мінімум до 40–48 ГБ.
- Чому контейнер у Kubernetes падає з Exit Code 137, якщо
-Xmxдорівнює ліміту пам’яті пода?- Відповідь: Exit Code 137 означає відправлення сигналу
SIGKILL(128 + 9) системним механізмом Linux OOM Killer при перевищенні cgroups memory limit. Пам’ять процесу Java (RSS) складається з Heap та Native Memory (Metaspace, стеки потоків-Xss, CodeCache, Direct Memory, таблиці GC). Якщо-Xmxдорівнює ліміту контейнера, будь-яка non-heap алокація миттєво викличе перевищення ліміту cgroup та аварійну смерть пода. Ліміт купи повинен становити 70–80% від ліміту контейнера.
- Відповідь: Exit Code 137 означає відправлення сигналу
Типові помилки та Red Flags
- 🚩 Вважати, що
-Xmx— це максимальний обсяг оперативної пам’яті, який може зайняти Java-застосунок. (Red Flag: нерозуміння архітектури віртуальної машини, незнання Native Memory, Metaspace та Thread Stacks). - 🚩 Виділяти 95–100% пам’яті контейнера під
-Xmxу Docker. (Red Flag: гарантований OOMKilled у продакшені). - 🚩 Стверджувати, що
-Xmsвиділяє фізичну пам’ять одразу за будь-яких умов. (Омана: без-XX:+AlwaysPreTouchфізичні сторінки виділяються ОС ліниво при першому читанні/записі). - 🚩 Задавати розмір купи рівно 32 ГБ. (Незнання механіки Compressed OOPs).