← Back to blog

Runtime memory economics · July 18, 2026

Java memory in containers: why an idle application can still cost gigabytes

Heap used, heap committed, metaspace, direct buffers, thread stacks, page cache, and container RSS are different numbers. This guide explains what a memory graph means, why garbage collection may retain capacity, and how flexible RAM billing turns slack into cost.

14 minute readDocumentedModeled

Method

What this comparison measures

  1. Separate JVM heap used, committed and maximum from process RSS and cgroup memory.
  2. Model a 30-day continuously running service; published Railway memory pricing is used only as a transparent cost example.
  3. Treat memory timelines as illustrative workload shapes, not universal Java-versus-Node benchmarks.
  4. Evaluate performance and cost together because shrinking heap can trade RAM for GC CPU, latency, and OOM risk.

Heap used is not the container bill

Oracle's MemoryUsage model distinguishes init, used, committed, and max. Committed memory is guaranteed to the JVM and is always at least used. The JVM can request and release memory over time, but unused committed capacity is not the same as an unreachable object leak.[1]

The process also owns class metadata, JIT code, thread stacks, direct byte buffers, native libraries, allocator arenas, and mapped pages. Container and platform graphs commonly report a process or cgroup view closer to RSS, which includes more than live Java objects.[2][6]

The first chart is a modeled 2.8 GB resident process after a burst. The categories are deliberately visible so a reader can replace them with Native Memory Tracking, JMX, cgroup, and profiler evidence from a real application.

Modeled resident-memory components after loadIllustrative Java process totaling 2.8 GB RSS. Components are not additive in every monitoring tool, so real measurements must use one consistent accounting boundary.
Modeled resident-memory components after loadIllustrative Java process totaling 2.8 GB RSS. Components are not additive in every monitoring tool, so real measurements must use one consistent accounting boundary.Live heap800 MBObjects retained after GCFree committed heap1200 MBAvailable for fast reuse by the JVMDirect / native buffers300 MBNetworking, compression, librariesMetaspace and JIT220 MBClass metadata and compiled codeThread stacks160 MBDepends on thread count and stack sizeRuntime / allocator120 MBLibraries and allocator overheadMB

The JVM sizes itself against a boundary

Current Java documents that the available-memory basis is the smaller of physical memory and an environment constraint such as a container. MaxRAMPercentage controls how much of that basis can become heap; Oracle documents a default of 25 percent for the option in Java 25.[3]

A manually oversized -Xmx can leave too little headroom for native memory. A very small heap can increase collection frequency and tail latency. A container with no memory limit can consume node memory and become a stronger OOM-kill candidate; a container above its limit can be terminated reactively under memory pressure.[3][4][5]

The resource limit, JVM heap ceiling, thread count, direct-memory ceiling, and workload allocation rate therefore form one configuration. Copying a heap flag from a large bare-metal server into a 1 GB PaaS container is not neutral.

Memory signals and the question each one answers
SignalWhat it measuresUseful questionCommon mistake
Heap used after GCLive Java objectsIs retained application state growing?Calling every high RSS value a heap leak
Heap committedMemory guaranteed to JVM heapHow much capacity is retained for reuse?Assuming free committed heap costs nothing
Allocation rateBytes created per secondHow hard is GC working?Looking only at a static heap snapshot
Native memoryMetaspace, code, stacks, buffers, librariesWhat exists outside heap?Setting Xmx equal to container limit
Container RSS / working setKernel-accounted resident memoryWhat boundary is the platform billing or enforcing?Comparing it directly with heap used
GC pause and CPUCollection costDid the smaller heap damage latency?Optimizing RAM without an SLA

Memory retained after a burst is not automatically a leak

The JVM can keep committed heap after traffic falls because future allocation can reuse it cheaply. Oracle exposes MaxHeapFreeRatio, MinHeapFreeRatio, and incremental heap shrinking controls, while warning that lower values can trade footprint for performance and results vary by application.[3]

A leak has a different signature: the post-GC floor rises across comparable cycles, retained object paths grow, or native allocations never return. A single sawtooth that settles above startup proves only that the runtime warmed up.

The timeline below is an illustrative shape. Java and Node values are not vendor benchmarks; they show why startup, peak, immediate post-load, and settled measurements all belong in a capacity report.

Illustrative RSS before, during, and after a burstModeled resident memory for two runtimes running different implementations of the same service shape. Use this as a measurement checklist, not a language comparison.
Illustrative RSS before, during, and after a burstModeled resident memory for two runtimes running different implementations of the same service shape. Use this as a measurement checklist, not a language comparison.

Flexible memory pricing makes slack visible

Railway currently publishes RAM at $10 per GB per month of measured usage. A service that settles at 4 GB instead of 2 GB therefore carries roughly a $20 monthly memory difference before replicas, databases, and traffic. Other platforms bill provisioned tiers instead, but the economic question is the same.[7]

Do not tune from idle alone. Run startup, steady, burst, allocation-heavy, and recovery phases under the exact cgroup limit. Record OOM events, restarts, throughput, CPU, p95/p99 latency, GC pauses, and heap after GC.

Monthly memory line at Railway list rateSimple 30-day illustration using the published $10/GB/month memory rate. CPU, minimum plan, replicas, storage, egress, and taxes are excluded.
Monthly memory line at Railway list rateSimple 30-day illustration using the published $10/GB/month memory rate. CPU, minimum plan, replicas, storage, egress, and taxes are excluded.1 GB average$102 GB average$204 GB average$408 GB average$80USD/month

Source register

Specifications and prices change. The links make this snapshot auditable.

Sources and commercial facts were checked on 2026-07-18. Prices exclude tax unless the source says otherwise.

  1. MemoryUsageOracle · product
  2. On-heap and off-heap memoryOracle · product
  3. The java command and advanced runtime optionsOracle · product
  4. Assign memory resources to containersKubernetes · product
  5. Resource management for Pods and containersKubernetes · product
  6. process.memoryUsageNode.js · product
  7. Railway pricingRailway · pricing
  8. JVM metricsMicrometer · observability