1. Overview
In this article, we’ll discuss the APIs provided by Java that can help us understand the several aspects related to Java heap space.
This can be useful in understanding the current memory status of the JVM and outsourcing it to monitoring services such as StatsD and Datadog which can then be configured to take pre-emptive action and avoid application failures.
2. Accessing Memory Parameters
Every Java application has a single instance of java.lang.Runtime that can help us understand the current memory status of the application. The Runtime#getRuntime static method can be called to get the singleton Runtime instance.
2.1. Total Memory
The Runtime#getTotalMemory method returns the total heap space currently reserved by the JVM in bytes. It includes the memory reserved for current and future objects. Hence, it isn’t guaranteed to be constant during the program execution since the Java heap space can be expanded or reduced as more objects are allocated.
Also, this value isn’t necessarily what is in use or the maximum memory available.
2.2. Free Memory
The Runtime#freeMemory method returns the free heap space available for new object allocations in bytes. It may increase as a result of a garbage collection operation where more free memory is available after.
2.3. Maximum Memory
The Runtime#maxMemory method returns the maximum memory that the JVM will attempt to use. Once the JVM memory usage reaches this value, then it will not allocate more memory and instead, and it will garbage collect more frequently.
*If the JVM objects still need more memory even after the garbage collector is run then the JVM may throw a java.lang.OutOfMemoryError runtime exception.*
3. Example
In the below example, we initialize an ArrayList and add elements to it while keeping track of the JVM heap space using the above three methods:
ArrayList<Integer> arrayList = new ArrayList<>();
System.out.println("i \t Free Memory \t Total Memory \t Max Memory");
for (int i = 0; i < 1000000; i++) {
arrayList.add(i);
System.out.println(i + " \t " + Runtime.getRuntime().freeMemory() +
" \t \t " + Runtime.getRuntime().totalMemory() +
" \t \t " + Runtime.getRuntime().maxMemory());
}
// ...
Output:
i Free Memory Total Memory Max Memory
0 254741016 257425408 3817865216
1 254741016 257425408 3817865216
...
1498 254741016 257425408 3817865216
1499 253398840 257425408 3817865216
1500 253398840 257425408 3817865216
...
900079 179608120 260046848 3817865216
900080 302140152 324534272 3817865216
900081 302140152 324534272 3817865216
...
- Row 1498: The Runtime#freeMemory value decreases when enough objects are allocated space in the Java heap.
- Row 900080: At this point, the JVM has more space available as GC has run hence the values of Runtime#freeMemory and Runtime#totalMemory increase.
The values shown above are expected to be different on every run of a Java application.
4. Customizing Memory Parameters
We can override the default values for the JVM memory parameters by setting custom values to certain flags when running our Java program in order to achieve the required memory performance:
- -Xms: The value assigned to -Xms flag sets the initial and minimum value of the Java heap. It can be used in cases where our application requires more memory than the default minimum when starting the JVM
- -Xmx: Likewise, we can set the maximum value for the heap space by assigning it to -Xmx flag. It can be used when we want to limit the amount of memory that our application will use, on purpose.
Please also note that the -Xms value needs to be equal or smaller than the -Xmx value.
4.1. Usage
java -Xms32M -Xmx64M Main
Free Memory : 31792664 bytes
Total Memory : 32505856 bytes
Max Memory : 59768832 bytes
java -Xms64M -Xmx64M Main
Free Memory : 63480640 bytes
Total Memory : 64487424 bytes
Max Memory : 64487424 bytes
java -Xms64M -Xmx32M Main
Error occurred during initialization of VM
Initial heap size set to a larger value than the maximum heap size
5. Conclusion
In this article, we’ve seen how to retrieve JVM memory metrics via the Runtime class. These methods can be useful when investigating JVM memory leaks and other JVM memory performance-related issues.
We’ve also shown how to assign custom values for certain flags leading to different JVM memory behavior for different scenarios.