posts
Guide to JVM memory configuration options

Guide to JVM memory configuration options

Apr 27, 2023
Dmitry Chuyko
13.0

So you have a goal: to reduce the memory footprint of the application. You roll up your sleeves and get straight to business. Unfortunately, there’s no one-size-fits-all answer to the question “How to reduce memory footprint” or enhance any other KPI. There are best practices to follow and common mistakes to avoid, but in reality, everything boils down to meticulous optimizations with due consideration of all variables. To assist you in this arduous task, we prepared a list of the most important JVM flags related to memory management.

Note that in most cases, switching to a smaller base image reduces the memory consumption significantly, so there's no need to tweak the memory configurations. For instance, Alpaquita Linux has a base image size of 3.26 MB (musl) and 8.77 MB (glibc), perfect for Java microcontainers.

Footprint (and startup) optimization is also possible by using Java with CRaC: give it a try!

Heap size options

JVM parameter

Description

-Xms

Sets the initial heap size 

-Xmx

Sets the maximum heap size

-XX:MinHeapFreeRatio

Sets the minimum percentage of free space after garbage collection

-XX:MaxHeapFreeRatio

Sets the maximum percentage of free space after garbage collection

-XX:MaxDirectMemorySize

Sets the limit for the memory allocated to direct byte buffers

In some cases, setting the maximum and minimum Java heap size is enough to optimize JVM memory footprint. The optimal heap size depends on your application, so you should experiment with the values before settling on a final number.

Setting min. and max. proportion of heap free after GC helps to avoid unnecessary expansion and shrinking of free space and release the unused memory without affecting the performance significantly.

For instance, if you set -XX:MinHeapFreeRatio=40 and -XX:MaxHeapFreeRatio=70, then the generation expands if the free space percentage goes below 40% and contracts if the free space exceeds 70%.

Direct byte buffers are used by the JVM to perform native I/O operations. As opposed to non-direct byte buffers stored in the heap, direct ones reside outside the heap and therefore are not affected by heap size parameters or garbage collection. By default, the JVM chooses the size of the direct size buffers automatically based on the available memory, so setting the -XX:MaxDirectMemorySize helps to prevent excessive resource consumption.

RAM consumption

JVM parameter

Description

-XX:MaxRAM

Sets the max. amount of total memory used by the JVM

-XX:MaxRAMFraction

Sets the RAM limit for JVM in fractions

-XX:MaxRAMPercentage

Sets the RAM limit for JVM per cent

The JVM flags adjusting the heap size do not affect the total memory consumption by the JVM. To limit the total RAM consumption, use MaxRam flags. The heap size will be adjusted accordingly. For instance, if you have 1 GB of memory, setting -XX:MaxRAMPercentage=50 (or -XX:MaxRAMFraction=2) will make the JVM allocate approx. 500 MB to heap.

These arguments are especially useful in the case of containerized applications, where they help to adjust the heap size based on the available container memory.

GC selection and logging

Selection

JVM parameter

Description

-XX:+UseSerialGC

Enables Serial Garbage Collector

-XX:+UseParallelGC

Enables Parallel Garbage Collector

-XX:+UseConcMarkSweepGC

Enables Concurrent Mark Sweep Garbage Collector (available up to Java 8 only)

-XX:+UseG1GC

Enables G1 Garbage Collector

-XX:+UseZGC (since Java 15)

Enables Z Garbage Collector (available since Java 11)

-XX:+UnlockExperimentalVMOptions -XX:+UseZGC (since Java 11 up to 15)

-XX:+UseShenandoahGC

Enables Shenandoah Garbage Collector (absent in Oracle Java, available in major OpenJDK distributions)

The default garbage collection settings are enough for many applications. If you would like to enhance some KPIs (in this case, memory footprint), try switching to another collector, whose defaults will be more beneficial to your app, without delving into the intricacies of GC tuning.

Java provides a set of GC implementations, each tailored to specific needs and use cases:

  • Serial GC works in one thread and freezes all app threads while performing collection;
  • Parallel GC also freezes all threads, but works in multiple threads itself;
  • CMS GC doesn’t freeze application threads, but instead, uses a few of them to perform its tasks. This collector was deprecated in Java 9 in favor of a more advanced G1 GC;
  • G1 GC utilizes the Garbage-First approach by dividing the heap in areas and collects the garbage in mostly free areas thus releasing lots of memory;
  • Z GC performs expensive work concurrently with the program and doesn’t freeze the app threads for more than 10 ms;
  • Shenandoah GC performs most of its work concurrently with the program, including the concurrent compaction, so the GC pause times are not directly proportional to the heap size. 

More detailed information on various collectors can be found in our Guide to Java GC

Logging

JVM parameter

Description

-Xlog:gc*:<gc.log file path>:time

Stores the GC logging data at the specified location 

-XX:PrintGC

Enables basic logging in Java 8

-XX:+PrintGCDetails

Activates detailed logging in Java 8+

-XX:NumberOfGCLogFiles

Sets the limit for the number of GC logs in Java 8

-XX:GCLogFileSize

Sets the max. size of a GC log file in Java 8 

Before adjusting garbage collector settings, learn to understand its behavior. GC logs are text files that provide exhaustive information about GC work: total GC time, memory reclamation and allocation, etc.

Note that GC logging parameters vary between Java 8 and Java 9+:

  • -XX:+PrintGCDetails and -Xlog:gc in Java 9+ substitute -XX:PrintGC in Java 8;
  • Java 8 includes the -XX:+UseGCLogFileRotation parameter that enables the rotation of GC logs. It is used together with the -XX:NumberOfGCLogFiles and -XX:GCLogFileSize flags. However, these functions were deprecated in newer Java versions.

A new unified GC logging system is implemented with JEP 271. To learn more about the new logging syntax, run

-Xlog:help

GC management

JVM parameter

Description

-XX:GCTimeRatio

Sets the the limit for GC execution time

-XX:AdaptiveSizePolicyWeight

Specifies how much previous GC times are taken into consideration when calculating current timing goals

-XX:+UseCGroupMemoryLimitForHeap

Sets the heap size based on the available container memory

-XX:ParallelGCThreads

Sets the number of Parallel GC threads

-XX:G1HeapRegionSize

Sets the size of a G1 region

-XX:InitiatingHeapOccupancyPercent

Sets the heap occupancy threshold triggering a marking cycle

Each GC comes with numerous settings that enable the developers to adjust latency, throughput, or memory. The table above provides memory related settings.

It should be noted that by default, -XX:GCTimeRatio is set to 99, which means that the application gets 99% of total execution time, and the collector can run for not more than 1% of the time. The -XX:GCTimeRatio and -XX:AdaptiveSizePolicyWeight parameters are helpful when using -XX:MinHeapFreeRatio and -XX:MaxHeapFreeRatio with Parallel GC.

How to handle OutOfMemoryError

JVM parameter

Description

-XX:+HeapDumpOnOutOfMemoryError

Dumps heap into a file in the case of OutOfMemoryError

-XX:HeapDumpPath

Specifies the path for the file with heap data

-XX:OnOutOfMemoryError="< cmd args >;< cmd args >"

Specifies actions to be performed in the case of OutOfMemoryError

OutOfMemoryError leads to the application crash and is hard to troubleshoot. The above parameters provide the developers with a lot of information related to the error, so it is easier to detect memory leaks.

Working with Strings

JVM parameter

Description

-XX:+UseStringDeduplication

Removes duplicate strings during GC (with G1 GC only) 

-XX:+UseStringCache

Caches commonly allocated strings in the String pool 

-XX:+UseCompressedStrings

Uses a byte[] for Strings that can be represented as pure ASCII

-XX:+OptimizeStringConcat

Optimizes String concatenation operations when possible

java.lang.String is the most commonly used Java class. No wonder that Strings take up a significant part of the application memory. We can release the resources by removing duplicate strings and optimizing the String operations with the above parameters.

Other useful parameters

JVM parameter

Description

-XX:LargePageHeapSizeThreshold

Uses large pages if max. heap is at least as big as the specified value

-XX:LargePageSizeInBytes

Sets the large page size for the heap

XX:+UseCompressedOops

Enables the use of compressed pointers (32-bit instead of 64-bit) for heaps less than 32 GB

-XX:+TieredCompilation

Disables intermediate compilation tiers

-XX:TieredStopAtLevel=1

Uses only the C1 compiler

-XX:ThreadStackSize

Sets the size of thread stack space

The -XX:LargePageHeapSizeThreshold and -XX:LargePageSizeInBytes flags enable the developers to operate with large pages (a technique to reduce the pressure on the processors Translation-Lookaside Buffer caches) and make better use of virtual hardware resources.

The -XX:+TieredCompilation and -XX:TieredStopAtLevel=1 can be used with Serial GC to turn off the optimizing compiler and reduce memory footprint in some cases. Use them when memory consumption is the only important KPI.

Memory to thread stacks is allocated outside of the heap, so it is not affected by heap size parameters. The -XX:ThreadStackSize flag enables the developers to reduce the size of thread stacks.

Conclusion

There are a few more JVM options left unmentioned in this article, such as the ones adjusting the size of different heap spaces (permanent generation, young generation, Eden, survivor). The reason is that these parameters require extremely fine tuning without significant overall improvement of memory consumption. In addition, recommendations on tuning the JDK for resource constrained containers can be found in a dedicated guide.

The list of all options can be downloaded in PDF format under the button below.

Get JVM parameters cheat sheet

And remember: sometimes it is much cheaper and faster to migrate to a smaller base OS image than spend hours on twisting the JVM parameters, where one imprudent step may lead to degradation of other performance indicators.

Discover Alpaquita Linux — we made it for Java developers seeking excellent performance and minimal footprint. Check out the numbers for base Docker images with and without a JDK:

Docker images comparison

Have we piqued your interest? Click on the banner below to find out more about Alpaquita. 

 

Subcribe to our newsletter

figure

Read the industry news, receive solutions to your problems, and find the ways to save money.

Further reading