Search code examples
javaspringmemorymemory-leaksgarbage-collection

Memory / garbage collector issues with Java Spring service


I have a memory issue with a Java service (21) using the Spring framework (3.2.1).

The service is quite simple. It exposes a REST endpoint.

When this endpoint is called, the service makes an HTTP request to another service (using Spring's RESTCLIENT mechanism) to fetch data. Once the data is retrieved, it is returned.

There is a lot of data, so when I call my endpoint, there is a spike in memory usage, which does not surprise me.

However, once the processing is complete, the memory remains high and never goes down.

Here is a screenshot from VisualVM :

enter image description here

The memory remains high until I click "Perform GC" in VisualVM. I have tried, even after 24 hours, the memory still stays high, even if no one calls my endpoint.

Here is more information:

  • The service runs in a Docker container. The image used is 3.9.5-eclipse-temurin-21
  • I have tried tweaking the JVM parameters:
    • Without any parameters
    • -Xms128m -XX:+UseG1GC -Xmx3g
    • -Xms128m -XX:+UseG1GC -Xmx3g -XX:MaxGCPauseMillis=200 In all three cases, the problem is still present

Do you have any idea of the origin of the problem and possible solutions?


Solution

  • This is normal behavior. Java garbage collectors are designed to minimize two metrics:

    • CPU utilization by the garbage collector, and / or
    • "stop the world" pauses while the garbage collector is running.

    Minimizing overall memory utilization is not a primary goal.

    In your example, you are using G1GC with an (implicit or explicit) maximum pause time goal. The JVM sees that there is still ~1GB of free heap space, and estimates that it doesn't need to trigger the GC yet to still meet the pause time guarantees. So it doesn't ... because running the GC when there isn't enough garbage to collect is inefficient.

    Frankly, I don't think this is a problem at all. But if average memory utilization is really an issue for you, look at the G1PeriodicGC options. These tell the JVM to trigger the GC periodically provided that the JVM is not already (too) busy. See JEP 346 for details. This feature was implemented in Java 12, so it should be available in the Java 21 image you are (I think) using.

    Note:

    • This will increase your JVM's CPU utilization: TANSTAAFL.
    • If you have an underlying problem with peak heap usage, this won't resolve it. When the amount of reachable data peaks, the JVM will need enough RAM to hold it ... plus some more because of JVM heap sizing policy. The only way to avoiding the JVM from using too much RAM when usage peaks is to set a maximum heap size that will stop it doing that.