A typical transaction-based Java™ application uses a request-response model and its load does not remain the same throughout its lifetime; it goes through peaks (high load) and troughs (idle period). Similarly, a big data application, which can require a huge amount of resources, still has variations in load. Periods of high load typically lead to a spurt in memory usage. However, when the load reduces there is no corresponding drop in memory usage. Why? Because most Java virtual machine (JVM) optimizations are targeted at improving the throughput or memory utilization of the application when it is active. Hardly any effort is spent on optimizing resource usage when it is idle.

But should we care about resource usage during idle periods?

In this era of cloud computing, different pricing models are used by cloud service providers. Consumption-based pricing models charge on resource usage, which generally includes the amount of memory, CPU cycles, disk space, network traffic, and so on. The most common pricing model is the memory based consumption model (Gigabytes/hour), as used by Bluemix. So if you are being charged for memory usage you probably want to know the memory usage pattern for your application.

So, does a JVM help us recognize these variations in load and optimize memory usage accordingly?

Memory consumption during idle periods

When an application is active over a period of time, the Java heap fills up with objects that are no longer being referenced, otherwise known as “garbage”. Garbage is collected and removed from the heap when space is needed by an application but is no longer available. When an application becomes idle, the JVM doesn’t bother collecting garbage because space isn’t being requested. So, the application ends up with a bloated heap that keeps memory footprint high, even during an idle period, which results in (you guessed it) bloated bills!

What can be done?

The Hotspot™ JVM has an option called –XX:-ShrinkHeapInSteps, which shrinks the heap to its actual usage after every full garbage collection (GC) cycle. But this has a drawback; the onus is on the application developer to figure out when the application is idle and then call System.gc().

There are some Java agents on the market that make use of the Hotspot option to automatically maintain the heap at the optimal size. But there is a drawback to these external agents too. They are not completely aware of the internal characteristics or state of the heap in order to make an informed decision about triggering a GC cycle. The agent makes decisions based on some heuristics, which might negatively impact application performance if the GC cycle is not timed correctly.

Wouldn’t it be better if your JVM, which does knows the exact state of the Java heap and the application, could recognize when your application is idle and take the opportunity to reduce the memory footprint?

Eclipse OpenJ9 to the rescue!

This is precisely what Eclipse OpenJ9 brings to the table. The OpenJ9 JVM has the ability to identify when an application is idle and reduce the reserved heap size down to its actual usage. This feature is enabled by specifying the –XX:+IdleTuningGcOnIdleoption option. OpenJ9 determines if an application is idle based on CPU utilization and other internal heuristics. When an idle state is established, a GC cycle runs if there is significant garbage in the heap and releases unused memory back to the operating system.

So, there is no need to worry about memory consumption and charges during idle periods. OpenJ9 is there to do the right thing, at the right time, in the right way!

How about some results?

We ran some tests to show how the use of the –XX:+IdleTuningGcOnIdle option reduced the memory footprint of the AcmeAir benchmark during idle periods. The AcmeAir application is designed with cloud platforms as the target and is available in both monolithic and microservices form.

For our test, we used the monolithic version. We ran the application, introducing an idle cycle between two active cycles. During active cycles the load increased in steps. We used the following Java heap settings:

  • –Xms8m
  • –Xmx512m

We did two runs of the application, one with the –XX:+IdleTuningGcOnIdle option set and one without.

Figure 1 shows the memory usage pattern that was recorded when the –XX:+IdleTuningGcOnIdle option was not set.
The time line on the X-axis shows the active and idle periods during the run. As expected, the Java heap grows during the first active period to reach a peak where it flattens. Memory usage remains the same during the idle period until the next active period, where usage increases by small amounts until the next idle period. The trend continues.

The overall memory usage (physical memory/RAM usage) of the JVM process shows the same pattern as the heap usage.

Chart depicting memory usage during active and idle periods of application activity without idle tuning enabled. Java heap memory and JVM process memory are both recorded. Further explanation is provided in the text.
Figure 1: AcmeAir benchmark results with idle tuning disabled.

Next, we enabled the idle tuning feature by specifying the –XX:+IdleTuningGcOnIdle option. The resulting memory usage pattern is shown in Figure 2.

Memory usage during an active period is similar to Figure 1 but when the application enters an idle cycle, OpenJ9 recognizes the opportunity for memory optimization. The graph shows how process memory is reduced during an idle cycle as a result of a GC cycle, where garbage is collected and unused memory is returned to the operating system. In our test, 20MB of memory was returned and while the application was idle, memory usage remained low.

You can see that the amount of memory recorded for the Java heap does not drop during idle periods, which is not a mistake or a bug! OpenJ9 does not resize the heap, it only decommits the heap pages and returns the memory back to the operating system. Therefore, heap usage reported by the JVM does not change.

Chart depicting memory usage during active and idle periods of application activity with idle tuning enabled. Java heap memory and JVM process memory are both recorded. Further explanation is provided in the text.
Figure 2 AcmeAir benchmark results with idle tuning enabled.

Icing on the cake

An additional advantage of running a GC cycle during an idle period is that it prepares the heap for the next active cycle. In our scenario, consider what might have happened if we did not run a GC cycle during the idle period. Most likely, the JVM would be forced to run a GC cycle very early during the next active cycle of the application when new objects are created.

By enabling the idle tuning feature, a GC cycle occurred during the idle period. Garbage was cleared up, making room for new objects that would be allocated during the next active cycle. This would definitely delay the need for a GC cycle during the active period. In a best case scenario, a GC cycle might not be needed at all during an active period if the space created is sufficient to accommodate new objects.

A few facts

  • The –XX:+IdleTuningGcOnIdle option is currently available only on Linux x86-64 bit platforms.
  • This feature is available only with the gencon GC policy, which is the default GC policy.
  • By default, OpenJ9 decommits the unused pages in the Java heap without resizing it. Therefore, the Java heap usage reported by the verbose:gc option doesn’t change. The memory savings, however, should be apparent in the physical memory usage of the JVM process.
  • For OpenJ9 to recognize that the application is in an idle state, the application must be idle for a minimum period of time, around 3-4 minutes. During this period, if OpenJ9 detects any activity, the counter gets reset and it might take longer to recognize a true idle state. If you want the JVM to be more aggressive in determining when your application is idle, you can override the default time period by using the -XX:IdleTuningMinIdleWaitTime option.
  • The memory saving that can be realized is dependent on the memory usage pattern of the application and its interaction with the Garbage Collector. To be more specific, the timing of a GC cycle during an active period and the garbage generated thereafter are the main factors that affect the memory saving. This feature is particularly useful for big data applications that generate a large amount of garbage. For example, reading image, video, audio files, or any large data sets that are no longer required once processed by the application. Enabling idle tuning can free up a substantial portion of the tenured space once this garbage is collected.

For more information about the feature and a comprehensive list of tuning options, start here.

Conclusion

The industry is quickly moving towards cloud where more and more apps are being developed and deployed. This era of cloud computing is not just impacting dev-ops or application developers, but also poses a challenge to managed runtimes to optimize themselves and become a better citizen in the cloud.

Optimizing resource usage during idle periods is one step in the right direction with benefits for developers as well as cloud vendors.

There is further scope for optimizing resource consumption in the cloud. With new cloud technologies such as serverless and containers, and with micro-services becoming increasingly mainstream, it is vital for managed runtimes to focus on optimizations that benefit these scenarios.

Eclipse OpenJ9 leads the way! Read more about it here



This article was authored by Ashutosh Mehra, Parameswaran Selvam, and Dinakar Guniguntala from the IBM Runtimes Cloud Squad.

3 comments on"Are you still paying for unused memory when your Java app is idle?"

  1. […] 本文翻译自:Measuring Java performance (or Jack starts a taxi business): Part 2(2017-09-26) […]

  2. Good night!
    I’m using the doptopenjdk/openjdk11-openj9 docker image in a springboot microservice, but when the load reduces (the application doesn’t receive request), i look that the Used Memory is reduce, but the Heap continue reserved.

  3. mehra.ashutosh February 28, 2019

    @Juan Gabriel Gomez
    When you refer to “Used Memory”, is it the physical memory usage of the process?
    With this feature, it is expected that the physical memory usage of the process (as reported by RES field in top) would come down during idle period, but the heap usage reported by OpenJ9 would not change.
    It is also mentioned the in the article – “You can see that the amount of memory recorded for the Java heap does not drop during idle periods, which is not a mistake or a bug! OpenJ9 does not resize the heap, it only decommits the heap pages and returns the memory back to the operating system. Therefore, heap usage reported by the JVM does not change.”
    If you need any more help, feel free to open an issue at https://github.com/eclipse/openj9 or you can also join OpenJ9 slack and ask your queries there: https://openj9.slack.com/join/shared_invite/enQtNDU4MDI4Mjk0MTk2LWM2MjliMTQ4NWM5YjMwNTViOTgzMzM2ZDhlOWJmZTc1MjhmYmRjMDg2NDljNGM0YTAwOWRiZDE0YzI0NjgyOWI
    Thanks!

Join The Discussion

Your email address will not be published. Required fields are marked *