Published on

Java memory - Xms and Xmx

Whenever a Java program is run, some memory is allocated for the program to use. The -Xms and -Xmx JVM options can be used to configure the minimum and the maximum allocated memory for the program respectively.

When the program starts, only Xms amount of memory is allocated and is available for use. The allocated memory can grow up to Xmx only when the program needs it.

  • -Xms512m - Instantiates JVM with 512MB pre-allocated memory.
  • -Xmx1024 - Maximum memory of 1024MB can be allocated and used by the JVM for the current program. If the program tries to allocate more memory than this, it will result in OOM (OutOfMemoryError) error.

The Runtime library provides methods to measure the current memory usage by the program.

  1. Runtime.freeMemory - Free memory readily available to use. (Minimum -Xms)
  2. Runtime.totalMemory - Total allocated memory. Equals to freeMemory + usedMemory.
  3. Runtime.maxMemory - The -Xmx value.

The ThreadMXBean provides methods to get stats on the currently executing thread such as total allocated memory, cpu time etc.,.

ThreadMXBean.getThreadAllocatedBytes returns the aggregated memory allocated to that thread in the current context.

In the below java file, I'm trying to do the below steps and take allocated memory stats

Tap to see code
Main.java

public class Main {
private static final int MEGA_BYTES = 1024 \* 1024;
private static final java.util.Scanner scanner = new java.util.Scanner(System.in);

    public static void main(String[] $) throws Exception {
    	int bytes = Integer.parseInt($[0]);

    	printStats(); // ------------------------------------ 1
    	allocate(bytes); // --------------------------------- 2

    	printStats(); // ------------------------------------ 3

    	// scanner.nextLine(); // Uncomment to pause

    	System.gc(); // ------------------------------------- 4
    	System.out.println("Garbage collected");

    	printStats(); // ------------------------------------ 5

    	allocate(bytes); // --------------------------------- 6
    	Thread.sleep(1000);

    	printStats(); // ------------------------------------ 7
    	// scanner.nextLine(); // Uncomment to pause
    }

    private static void allocate(int bytes) {
    	System.out.println(String.format(
    				"Trying to allocate %d bytes (%d MB)", bytes, bytes / MEGA_BYTES));

    	byte[] allocating_here = new byte[bytes];

    	System.out.println(String.format(
    				"Allocated %d bytes (%d MB) successfully",
    				bytes, bytes / MEGA_BYTES
    				));

    }

    private static void printStats() {
    	line();
    	printMemoryStats();
    	printThreadMemoryStats();
    }

    private static void printMemoryStats() {
    	long freeMemory = Runtime.getRuntime().freeMemory() / MEGA_BYTES;
    	long totalMemory = Runtime.getRuntime().totalMemory() / MEGA_BYTES;
    	long maxMemory = Runtime.getRuntime().maxMemory() / MEGA_BYTES;

    	System.out.println(String.format(
    				"free: %d MB; total: %d MB; max: %d MB",
    				freeMemory, totalMemory, maxMemory
    				));
    }

    private static void printThreadMemoryStats() {
    	com.sun.management.ThreadMXBean threadMXBean =
    		(com.sun.management.ThreadMXBean)
    		java.lang.management.ManagementFactory.getThreadMXBean();

    	long threadBytes =
    				threadMXBean.getThreadAllocatedBytes(Thread.currentThread().getId());

    	System.out.println(String.format(
    				"Bytes allocated for current thread %d (%d MB)",
    				threadBytes, threadBytes / MEGA_BYTES
    				));

    	System.out.println(String.format(
    				"CPU time %d nanos",
    				threadMXBean.getCurrentThreadCpuTime()
    				));
    }

    private static void line() {
    	System.out.println("---------------------");
    }

}

javac Main.java && java -Xms256m -Xmx1024m Main 1000000000 # Allocate almost 1 GB
  1. Print initial stats
    • Initializes the JVM with 256 MB total memory.
  2. Allocate 953MB
    • Allocates 953 MB and also increases the JVMs total memory capacity.
  3. Print stats
  4. Garbage collect
    • The allocated bytes are now out of scope and can be garbage collected.
  5. Print stats
    • The total memory shrunk back to 256 MB (Xms value) since there is need for memory.
  6. Allocate 953MB
    • Will be able to allocate 953 MB since it is available.
  7. Print stats
    • Now, notice the total allocated bytes of the thread. It is 1908 MB which is greater than the max -Xmx. It is because it prints the aggregated memory requested by the current thread.
Memory

More examples

$ java -version
openjdk version "17" 2021-09-14
OpenJDK Runtime Environment (build 17+35-2724)
OpenJDK 64-Bit Server VM (build 17+35-2724, mixed mode, sharing)

# The G1GC became the default garbage collector since Java 9

$ java -XX:+PrintGCDetails
[0.001s][warning][gc] -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead.
[0.006s][info   ][gc] Using G1
[0.006s][info   ][gc,init] Version:
<truncated>


$ javac Main.java && java -Xms256m -Xmx400000000m Main 10000000 # Try to allocate 400 Terra bytes.
Error occurred during initialization of VM
Could not reserve enough space for 409600000000KB object heap


$ javac Main.java && java -Xms256m -Xmx512m Main 1000000000 # Out of memory when trying to allocate 953 MB with only 512 MB Xmx
---------------------
free: 256 MB; total: 258 MB; max: 512 MB
Bytes allocated for current thread 780208 (0 MB)
CPU time 28867000 nanos
Trying to allocate 1000000000 bytes (953 MB)
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at Main.allocate(Main.java:32)
        at Main.main(Main.java:10)