JVM Memory Regions
Purpose
Budget a JVM process against a hard memory limit. A common failure is -Xmx set to the
container limit, with non-heap
regions pushing RSS past the cgroup, and the kernel killing the process — no
OutOfMemoryError, no shutdown hook, no heap dump, because the JVM never knew it was
dying.
Only the Java heap object graph is reclaimed directly as ordinary GC-managed objects, but
class unloading, code-cache sweeping, native cleaners/arenas and OS reclaim couple the
other domains to different lifecycles. Not every domain has a hard flag or a distinct
OutOfMemoryError; that is why accounting starts from evidence rather than six fixed boxes.
Inspect the target JDK/vendor/update, collector, effective flags, OS and cgroup hierarchy
before applying a budget. JDK 25 examples describe one HotSpot environment; neither a newer
feature nor a measurement here authorizes changing the project's runtime.
Workflow
- Read the full OOM message first. It suggests an allocation path or resource limit, not necessarily
the root cause; preserve causes and external process/container evidence.
Metaspace, Direct buffer memory and unable to create native thread require different evidence; raising -Xmx is not a general repair and can increase
native/cgroup pressure even when it also raises an implicit direct-buffer limit.
- Measure JVM-tracked non-heap with NMT under your own load, and model untracked domains:
-XX:NativeMemoryTracking=summary at start, then
jcmd <pid> VM.native_memory summary; baseline followed later by summary.diff
attributes tracked growth to a category. Periodic JFR NMT/RSS events can provide a
related time series when present/enabled, but do not assume identical semantics. NMT cannot be
enabled on a running process.
- Budget every region and peak overlap: candidate heap max = limit − measured/modelled
non-heap/cgroup peaks − uncertainty/recovery headroom. Then express it as
-Xmx or
MaxRAMPercentage against the memory value the JVM actually detected;
the arithmetic and the RSS-versus-NMT gap table are in
references/container-budget.md.
- Distinguish virtual reserved, NMT committed, resident and cgroup-charged.
ps exposes
both VSZ and RSS; neither is identical to NMT totals or memory.current. A residual
obtained by subtracting NMT committed from RSS is not a measured untracked-native total.
- Judge normalized trends, not instants — compare equivalent reclamation points,
native/RSS/cgroup peaks and the workload regime that produced them.
Rules
- Choose fixed versus variable initial heap from startup, RSS/density, idle-uncommit and SLO
measurements.
-Xms = -Xmx removes heap growth but is not a universal production rule.
- Set
MaxMetaspaceSize only as a deliberate fail-fast/capacity boundary. Too low causes
avoidable OOM during legitimate class loading; absent/unbounded shifts the boundary to
process/cgroup capacity. Alert on class-loader/class-space trends either way.
unable to create native thread can mean PID/rlimit exhaustion, native allocation
failure, cgroup pressure or virtual-address constraints. Changing -Xmx helps only when
measured heap commitment/residency is consuming the relevant resource; it can also hide
the real PID/thread-lifecycle defect.
- The compressed-oop cutoff for applicable HotSpot collectors is often near 32 GiB but
depends on alignment, heap base/reservation and build. Confirm effective flags/layout;
ZGC's colored-pointer scheme is not a compressed-oops workaround.
-Xss × platform-thread count is only an approximate Java-stack reservation model,
not a process-wide bound or resident bytes. JVM/native threads, main-thread policy,
rounding and guard regions can differ; inspect actual reservations. Reduce stacks only after testing Java/native call depth and guard-page behavior;
stack overflow is a correctness failure, not merely a tuning regression.
- Code-cache pressure can stop compilation and trigger flushing/sweeping/restart behavior
that varies by tier/segment and release.
jdk.CodeCacheFull is strong evidence of an
event, not proof of permanent interpretation or a universal 80% threshold. Correlate
compiler logs, segment occupancy, sweeper activity and throughput (code-cache-segments).
- Measure object layout with JOL rather than estimating headers. Compact object headers
(JEP 519, product in 25) are off by default through JDK 26 and on by default from
JDK 27 (JEP 534); disable with
-XX:-UseCompactObjectHeaders. Do not budget 8 bytes
per object: alignment makes savings class/layout-dependent, and some common small
objects can retain the same aligned size while their surrounding graph/arrays change.
For the rule that predicts which classes do save, the measured
per-class table and the per-object arithmetic, see object-layout-and-footprint.
- Unmounted virtual-thread continuation chunks live in the heap, while mounted execution
uses carrier/native stack state. Millions of tasks can therefore shift context/stack
retention into GC-visible objects; measure mounted/unmounted state and in-flight
concurrency before adjusting heap or
-Xss.
MaxDirectMemorySize is a separate ceiling. On the verified HotSpot implementation its
absent value resolves to Runtime.maxMemory(), so choosing a large -Xmx can implicitly
authorize a similarly large direct-buffer budget; it does not reserve that memory or make
the combined process fit.
Evidence and rollout safety
- Capture NMT, process maps, cgroup files and JFR at aligned timestamps with JDK vendor/
update, PID, container ID and load. “Committed,” RSS/PSS and
memory.current must never
be joined from different windows as if simultaneous.
- Process maps, hs_err, heap/core dumps and command lines can expose paths, credentials and
payloads. Restrict collection/transfer, redact only on a preserved copy, record hashes and
expire artifacts under incident policy.
- For any limit/heap/stack/metaspace change, canary with abort thresholds for OOM kills,
Java OOMs, startup time, RSS/cgroup high-water mark, faults, GC tails, throughput and
stack overflow. Retain the previous configuration for rollback.
References
- Container budget — the per-region budget with the
worked
MaxRAMPercentage arithmetic, reading VM.native_memory summary and diff,
the RSS-versus-NMT gap table, and the Kubernetes checklist. Read when sizing a JVM for
a memory limit or when RSS does not match the heap.
- OOM triage by region — which message means which region,
the
jcmd command that confirms each, what does not fix it, and the
ExitOnOutOfMemoryError / CrashOnOutOfMemoryError decision. Read when an
OutOfMemoryError or an OOMKill has already happened, or when configuring what the JVM
does on the next one.
Authoritative sources: Oracle Native Memory Tracking guide,
Oracle container support guide,
JEP 519, JEP 534, and
Linux cgroup v2 memory controller.
1---2name: jvm-memory-regions3description: The major memory-accounting domains of a JVM process — heap, Metaspace/class space, code cache, thread stacks, direct/native/JVM-internal memory and mapped/file-backed pages — and how to budget them against a container limit. Use when a pod is OOMKilled with no Java exception, when an OutOfMemoryError names something other than "Java heap space", when -Xmx is set equal to the container limit, when RSS exceeds the heap by more than expected, when a heap above 32 GB is proposed, or when sizing a JVM for Kubernetes. Does not cover collector choice and heap tuning (jvm-gc-tuning), classloader leaks (jvm-class-loading), or kernel-side memory behaviour such as page faults, swap and the OOM killer (linux-for-jvm). Metaspace internals are metaspace-internals, memory outside the heap is off-heap-memory, and heap contents are heap-dump-analysis.4---56# JVM Memory Regions78## Purpose910Budget a JVM process against a hard memory limit. A common failure is `-Xmx` set to the11container limit, with non-heap12regions pushing RSS past the cgroup, and the **kernel** killing the process — no13`OutOfMemoryError`, no shutdown hook, no heap dump, because the JVM never knew it was14dying.1516Only the Java heap object graph is reclaimed directly as ordinary GC-managed objects, but17class unloading, code-cache sweeping, native cleaners/arenas and OS reclaim couple the18other domains to different lifecycles. Not every domain has a hard flag or a distinct19`OutOfMemoryError`; that is why accounting starts from evidence rather than six fixed boxes.2021Inspect the target JDK/vendor/update, collector, effective flags, OS and cgroup hierarchy22before applying a budget. JDK 25 examples describe one HotSpot environment; neither a newer23feature nor a measurement here authorizes changing the project's runtime.2425## Workflow26271. **Read the full OOM message first.** It suggests an allocation path or resource limit, not necessarily28 the root cause; preserve causes and external process/container evidence. `Metaspace`, `Direct buffer memory` and `unable to create native29thread` require different evidence; raising `-Xmx` is not a general repair and can increase30 native/cgroup pressure even when it also raises an implicit direct-buffer limit.312. **Measure JVM-tracked non-heap with NMT under your own load**, and model untracked domains:32 `-XX:NativeMemoryTracking=summary` at start, then33 `jcmd <pid> VM.native_memory summary`; `baseline` followed later by `summary.diff`34 attributes tracked growth to a category. Periodic JFR NMT/RSS events can provide a35 related time series when present/enabled, but do not assume identical semantics. NMT cannot be36 enabled on a running process.373. **Budget every region and peak overlap**: candidate heap max = limit − measured/modelled38 non-heap/cgroup peaks − uncertainty/recovery headroom. Then express it as `-Xmx` or39 `MaxRAMPercentage` against the memory value the JVM actually detected;40 the arithmetic and the RSS-versus-NMT gap table are in41 `references/container-budget.md`.424. **Distinguish virtual reserved, NMT committed, resident and cgroup-charged.** `ps` exposes43 both VSZ and RSS; neither is identical to NMT totals or `memory.current`. A residual44 obtained by subtracting NMT committed from RSS is not a measured untracked-native total.455. **Judge normalized trends, not instants** — compare equivalent reclamation points,46 native/RSS/cgroup peaks and the workload regime that produced them.4748## Rules4950- Choose fixed versus variable initial heap from startup, RSS/density, idle-uncommit and SLO51 measurements. `-Xms = -Xmx` removes heap growth but is not a universal production rule.52- Set `MaxMetaspaceSize` only as a deliberate fail-fast/capacity boundary. Too low causes53 avoidable OOM during legitimate class loading; absent/unbounded shifts the boundary to54 process/cgroup capacity. Alert on class-loader/class-space trends either way.55- `unable to create native thread` can mean PID/rlimit exhaustion, native allocation56 failure, cgroup pressure or virtual-address constraints. Changing `-Xmx` helps only when57 measured heap commitment/residency is consuming the relevant resource; it can also hide58 the real PID/thread-lifecycle defect.59- The compressed-oop cutoff for applicable HotSpot collectors is often near 32 GiB but60 depends on alignment, heap base/reservation and build. Confirm effective flags/layout;61 ZGC's colored-pointer scheme is not a compressed-oops workaround.62- `-Xss × platform-thread count` is only an approximate Java-stack reservation model,63 not a process-wide bound or resident bytes. JVM/native threads, main-thread policy,64 rounding and guard regions can differ; inspect actual reservations. Reduce stacks only after testing Java/native call depth and guard-page behavior;65 stack overflow is a correctness failure, not merely a tuning regression.66- Code-cache pressure can stop compilation and trigger flushing/sweeping/restart behavior67 that varies by tier/segment and release. `jdk.CodeCacheFull` is strong evidence of an68 event, not proof of permanent interpretation or a universal 80% threshold. Correlate69 compiler logs, segment occupancy, sweeper activity and throughput (`code-cache-segments`).70- Measure object layout with JOL rather than estimating headers. Compact object headers71 (JEP 519, product in 25) are **off by default through JDK 26 and on by default from72 JDK 27** (JEP 534); disable with `-XX:-UseCompactObjectHeaders`. **Do not budget 8 bytes73 per object**: alignment makes savings class/layout-dependent, and some common small74 objects can retain the same aligned size while their surrounding graph/arrays change.75 For the rule that predicts which classes do save, the measured76 per-class table and the per-object arithmetic, see `object-layout-and-footprint`.77- Unmounted virtual-thread continuation chunks live in the heap, while mounted execution78 uses carrier/native stack state. Millions of tasks can therefore shift context/stack79 retention into GC-visible objects; measure mounted/unmounted state and in-flight80 concurrency before adjusting heap or `-Xss`.81- `MaxDirectMemorySize` is a separate ceiling. On the verified HotSpot implementation its82 absent value resolves to `Runtime.maxMemory()`, so choosing a large `-Xmx` can implicitly83 authorize a similarly large direct-buffer budget; it does not reserve that memory or make84 the combined process fit.8586## Evidence and rollout safety8788- Capture NMT, process maps, cgroup files and JFR at aligned timestamps with JDK vendor/89 update, PID, container ID and load. “Committed,” RSS/PSS and `memory.current` must never90 be joined from different windows as if simultaneous.91- Process maps, hs_err, heap/core dumps and command lines can expose paths, credentials and92 payloads. Restrict collection/transfer, redact only on a preserved copy, record hashes and93 expire artifacts under incident policy.94- For any limit/heap/stack/metaspace change, canary with abort thresholds for OOM kills,95 Java OOMs, startup time, RSS/cgroup high-water mark, faults, GC tails, throughput and96 stack overflow. Retain the previous configuration for rollback.9798## References99100- [Container budget](references/container-budget.md) — the per-region budget with the101 worked `MaxRAMPercentage` arithmetic, reading `VM.native_memory summary` and `diff`,102 the RSS-versus-NMT gap table, and the Kubernetes checklist. Read when sizing a JVM for103 a memory limit or when RSS does not match the heap.104- [OOM triage by region](references/oom-triage.md) — which message means which region,105 the `jcmd` command that confirms each, what does _not_ fix it, and the106 `ExitOnOutOfMemoryError` / `CrashOnOutOfMemoryError` decision. Read when an107 `OutOfMemoryError` or an OOMKill has already happened, or when configuring what the JVM108 does on the next one.109110Authoritative sources: [Oracle Native Memory Tracking guide](https://docs.oracle.com/en/java/javase/25/vm/native-memory-tracking.html),111[Oracle container support guide](https://docs.oracle.com/en/java/javase/25/docs/specs/man/java.html#java-options-for-linux),112[JEP 519](https://openjdk.org/jeps/519), [JEP 534](https://openjdk.org/jeps/534), and113[Linux cgroup v2 memory controller](https://docs.kernel.org/admin-guide/cgroup-v2.html#memory).