Экономика памяти runtime · 18 июля 2026 г.
Память Java в контейнерах: почему простаивающее приложение всё ещё стоит гигабайты
Использованная и committed heap, metaspace, direct buffers, стеки потоков, page cache и RSS контейнера — разные числа. Объясняем, что означает график памяти, почему сборщик мусора может сохранять ёмкость и как гибкий биллинг RAM превращает запас в расходы.
Методика
Что именно сравнивается
- Разделяем used, committed и maximum JVM heap, RSS процесса и память cgroup.
- Моделируем непрерывно работающий 30 дней сервис; опубликованная цена памяти Railway используется только как прозрачный пример стоимости.
- Считаем временные ряды памяти иллюстративными формами нагрузки, а не универсальным сравнением Java и Node.
- Оцениваем производительность вместе со стоимостью, потому что уменьшение heap может обменять RAM на CPU сборщика, latency и риск OOM.
Heap used — не счёт контейнера
Модель MemoryUsage Oracle различает init, used, committed и max. Committed memory гарантирована JVM и всегда не меньше used. JVM может запрашивать и освобождать память со временем, но неиспользованная committed-ёмкость — не то же самое, что утечка недостижимых объектов.[1]
Процесс также владеет метаданными классов, JIT-кодом, стеками потоков, direct byte buffers, нативными библиотеками, аренами allocator и отображёнными страницами. Графики контейнера и платформы обычно показывают процесс или cgroup ближе к RSS, куда входят не одни живые Java-объекты.[2][6]
Первый график моделирует резидентный процесс на 2,8 ГБ после burst. Категории намеренно видимы, чтобы читатель мог заменить их доказательствами Native Memory Tracking, JMX, cgroup и профилировщика реального приложения.
JVM выбирает размер относительно границы
Актуальная документация Java определяет доступную память как меньшее из физической памяти и ограничения окружения, например контейнера. MaxRAMPercentage задаёт долю этой основы для heap; Oracle документирует значение по умолчанию 25 процентов для этой опции в Java 25.[3]
Вручную завышенный -Xmx может оставить слишком мало места native memory. Очень маленький heap способен увеличить частоту сборки и хвостовую latency. Контейнер без лимита может поглощать память узла и стать более вероятной целью OOM kill; контейнер выше лимита может быть реактивно завершён при давлении памяти.[3][4][5]
Поэтому лимит ресурса, потолок JVM heap, число потоков, потолок direct memory и скорость аллокаций нагрузки образуют одну конфигурацию. Копирование heap-флага с большого bare-metal сервера в PaaS-контейнер на 1 ГБ не нейтрально.
| Сигнал | Что измеряет | Полезный вопрос | Частая ошибка |
|---|---|---|---|
| Heap used после GC | Живые Java-объекты | Растёт ли сохраняемое состояние приложения? | Называть любой высокий RSS утечкой heap |
| Heap committed | Память, гарантированная JVM heap | Сколько ёмкости удерживается для повторного использования? | Считать свободный committed heap бесплатным |
| Скорость аллокаций | Создаваемые байты в секунду | Насколько интенсивно работает GC? | Смотреть только на статичный снимок heap |
| Native memory | Metaspace, код, стеки, buffers, библиотеки | Что существует вне heap? | Ставить Xmx равным лимиту контейнера |
| RSS / working set контейнера | Резидентная память по учёту ядра | Какую границу тарифицирует или применяет платформа? | Сравнивать напрямую с heap used |
| Паузы GC и CPU | Стоимость сборки | Повредил ли меньший heap latency? | Оптимизировать RAM без SLA |
Память после всплеска — не обязательно утечка
JVM может сохранять committed heap после падения трафика, потому что будущие аллокации дёшево используют его повторно. Oracle предоставляет MaxHeapFreeRatio, MinHeapFreeRatio и управление постепенным сжатием heap, предупреждая, что меньшие значения могут обменять footprint на производительность, а результат зависит от приложения.[3]
У утечки другой рисунок: нижняя граница после GC растёт между сопоставимыми циклами, цепочки удержания объектов увеличиваются или native allocations не возвращаются. Один sawtooth, установившийся выше старта, доказывает лишь прогрев runtime.
Временная шкала ниже иллюстративна. Значения Java и Node — не vendor benchmark; они показывают, почему запуск, пик, момент сразу после нагрузки и установившееся состояние должны входить в capacity-отчёт.
Гибкая цена памяти делает запас видимым
Railway сейчас публикует цену RAM $10 за ГБ в месяц измеренного использования. Поэтому сервис, устанавливающийся на 4 ГБ вместо 2 ГБ, несёт примерно $20 дополнительной месячной стоимости памяти ещё до реплик, баз и трафика. Другие платформы тарифицируют выделенные уровни, но экономический вопрос тот же.[7]
Не настраивайте только по idle. Проведите фазы запуска, steady, burst, интенсивных аллокаций и восстановления под точным лимитом cgroup. Записывайте OOM, рестарты, throughput, CPU, p95/p99 latency, паузы GC и heap после GC.
Реестр источников
Спецификации и цены меняются. Ссылки делают снимок проверяемым.
Источники и коммерческие данные проверены 2026-07-18. Если источник не говорит обратного, цены указаны без налогов.
- MemoryUsage ↗Oracle · продукт
- On-heap and off-heap memory ↗Oracle · продукт
- The java command and advanced runtime options ↗Oracle · продукт
- Assign memory resources to containers ↗Kubernetes · продукт
- Resource management for Pods and containers ↗Kubernetes · продукт
- process.memoryUsage ↗Node.js · продукт
- Railway pricing ↗Railway · цены
- JVM metrics ↗Micrometer · наблюдаемость