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!
Table of Contents
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.