Heap in Java

As someone unfamiliar with Java, I recently discovered the -Xms and -Xmx settings used to configure the Java Virtual Machine’s (JVM) initial and maximum heap sizes. This sparked my curiosity, particularly because Node.js has a similar --max-old-space-size setting, while .Net seemingly lacks any heap size control.

For context, 64-bit systems provide applications with a vast, isolated virtual address space (VAS) for memory allocation. While theoretically massive, the operating system often imposes limits. What truly matters is the physical address space, shared by all applications and limited by the actual RAM installed. Applications can reserve VAS memory without immediate physical usage, and only when utilized is it mapped to the physical address space. You can find a good example of this with a native Linux application here: this link. Therefore, virtual memory consumption by a 64-bit application is less critical than its physical memory footprint.

The Java heap, where objects are stored and managed by the garbage collector, represents a significant portion of an application’s memory usage. However, other areas like the stack, class definitions, code, and additional structures also reside in memory. A clear illustration of this memory layout can be found here: here. Similar memory structures exist in other virtual environments like .Net, Node.js, and Python.

The -Xmx setting is straightforward—defining the maximum heap size. This limit, applied to the reserved virtual memory, inherently restricts physical memory consumption. When the heap is full, either the garbage collector frees space, or the application crashes. This mechanism aims to prevent one application from monopolizing resources and potentially impacting other well-behaved applications.

The purpose of -Xms, setting the initial heap size, is less intuitive. Reserving this space within the application’s VAS doesn’t immediately consume physical memory. This initial reservation ensures a contiguous memory block for the heap, enhancing performance. More importantly, it appears garbage collection might be deferred until this minimum heap size is reached, potentially improving application speed. This explains why setting -Xms and -Xmx to the same value is sometimes recommended. It allows the application to run unimpeded by garbage collection until reaching the defined memory limit, while avoiding resizing costs.

Intrigued by the effects of the -Xms setting, I conducted tests to verify its impact on initial physical memory usage, especially given some conflicting information online. In Linux, I found the command top -p pid most effective for examining a process’s physical (resident) and virtual memory consumption.

My test case involved a simple “Hello World” Java application running on Ubuntu with OpenJDK 64-Bit Server VM (build 14.0.2+12-Ubuntu-120.04, mixed mode, sharing). This application barely uses the heap, as it only creates objects essential for console interaction.

Running the application without specifying -Xms or -Xmx resulted in:

While the JVM reserved a significant chunk of virtual memory (over 4 GB, with the heap likely around 1 GB), physical memory usage remained low at 33 MB.

Introducing a -Xms12g setting (12 GB) resulted in:

Here, the JVM reserved 15 GB of virtual memory (including the 12 GB heap and 3 GB for other areas), with physical memory usage at 87 MB. This highlights how reserving a larger heap leads to a slight increase in physical memory consumption.

Similar tests on Windows yielded 10 GB virtual memory / 20 MB working set for the first case, and 13 GB virtual memory / 60 MB working set for the second. However, due to my limited understanding of Windows memory metrics (Private Bytes, Working Set, etc.), I’ll refrain from further analysis.

Virtual Address Space

Licensed under CC BY-NC-SA 4.0
Last updated on Sep 09, 2022 16:02 +0100