Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Will OpenJDK JVM ever give heap memory back to Linux?

We have a long-lived server process, that infrequently needs a lot of RAM for a short while. We see that once the JVM has gotten the memory from the OS, it never returns it back to the OS. How do we ask the JVM to return heap memory back to the OS?

Typically, the accepted answer to such questions is to use -XX:MaxHeapFreeRatio and -XX:MinHeapFreeRatio. (See e.g. 1,2,3,4). But we're running java like this:

java -Xmx4G -XX:MaxHeapFreeRatio=50 -XX:MinHeapFreeRatio=30 MemoryUsage

and still see this in VisualVM:

Visual VM memory usage

Clearly, the JVM is not honoring -XX:MaxHeapFreeRatio=50 as the heapFreeRatio is very close to 100% and nowhere near 50%. No amount of clicking on "Perform GC" returns memory to the OS.

MemoryUsage.java:

import java.util.ArrayList;
import java.util.List;

public class MemoryUsage {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("Sleeping before allocating memory");
        Thread.sleep(10*1000);

        System.out.println("Allocating/growing memory");
        List<Long> list = new ArrayList<>();
        // Experimentally determined factor. This gives approximately 1750 MB
        // memory in our installation.
        long realGrowN = 166608000; //
        for (int i = 0 ; i < realGrowN ; i++) {
            list.add(23L);
        }

        System.out.println("Memory allocated/grown - sleeping before Garbage collecting");
        Thread.sleep(10*1000);

        list = null;
        System.gc();

        System.out.println("Garbage collected - sleeping forever");
        while (true) {
            Thread.sleep(1*1000);
        }
    }
}

Versions:

> java -version
openjdk version "1.8.0_66-internal"
OpenJDK Runtime Environment (build 1.8.0_66-internal-b01)
OpenJDK 64-Bit Server VM (build 25.66-b01, mixed mode)

> uname -a
Linux londo 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt11-1+deb8u5 (2015-10-09) x86_64 GNU/Linux

> lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux 8.2 (jessie)
Release:    8.2
Codename:   jessie

I've also tried OpenJDK 1.7 and Sun Java's 1.8. All behave similarly and none give memory back to the OS.

I do think I need this and that swap and paging won't "solve" this, because spending disk IO on paging close to 2GB garbage in and out is just a waste of resources. If you disagree, please enlighten me.

I've also written a little memoryUsage.c with malloc()/free(), and it does return memory to the OS. So it is possible in C. Perhaps not with Java?

Edit: Augusto pointed out that searching would've led me to -XX:MaxHeapFreeRatio and -XX:MinHeapFreeRatio only worked with -XX:+UseSerialGC. I was ecstatic and tried it, puzzled that I hadn't found this myself. Yes, it did work with my MemoryUsage.java:

-XX:+UseSerialGC working with simple app

However, when I tried -XX:+UseSerialGC with our real app, not so much:

-XX:+UseSerialGC not working with real app

I discovered that gc() after a while did help, so I made a thread that did more or less:

while (idle() && memoryTooLarge() && ! tooManyAttemptsYet()) {
    Thread.sleep(10*1000);
    System.gc();
}

and that did the trick:

GC thread working

I had actually previously seen the behavior with -XX:+UseSerialGC and multiple System.gc() calls in some of my many experiments but didn't like the need for a GC thread. And who knows if that'll continue to work as both our app and java evolves. There must be a better way.

What is the logic that forces me to call System.gc() four times (but not immediately), and where is this stuff documented?

In search of documentation for -XX:MaxHeapFreeRatio and -XX:MinHeapFreeRatio only working with -XX:+UseSerialGC, I read the documentation for the java tool/executable and it isn't mentioned anywhere that -XX:MaxHeapFreeRatio and -XX:MinHeapFreeRatio only works with -XX:+UseSerialGC. In fact, the fixed issue [JDK-8028391] Make the Min/MaxHeapFreeRatio flags manageable says:

To enable applications to control how and when to allow for more or less GC, the flags -XX:MinHeapFreeRatio and -XX:MaxHeapFreeRatio should be made manageable. Support for these flags should also be implemented in the default parallel collector.

A comment for the fixed issue says:

Support for these flags have also been added to the ParallelGC as part of the adaptive size policy.

I've checked, and the patch referenced in the fixed issue backported to openjdk-8 is indeed contained in the source package tarball for the openjdk-8 version I'm using. So it should apparently work in "the default parallel collector", but doesn't as I've demonstrated in this post. I haven't yet found any documentation that says it should only work with -XX:+UseSerialGC. And as I've documented here, even this is unreliable/dicey.

Can't I just get -XX:MaxHeapFreeRatio and -XX:MinHeapFreeRatio to do what they promise without having to go through all these hoops?

like image 593
Peter V. Mørch Avatar asked Oct 24 '15 13:10

Peter V. Mørch


People also ask

Does JVM give memory back to OS?

The HotSpot JVM does release memory back to the OS, but does so reluctantly since resizing the heap is expensive and it is assumed that if you needed that heap once you'll need it again.

How do I permanently increase Java heap size in Linux?

In order to increase that permanently you need to change /etc/profile file. (If you want to increase it to some other value, replace 2048 with the appropriate value in MB). Now log out from your account and log in again. Run the previous command again to check whether it has changed.

Is heap permanent?

The permanent generation is the region of the heap where class definitions are stored.

What happens when JVM runs out of memory?

OutOfMemoryError is a runtime error in Java which occurs when the Java Virtual Machine (JVM) is unable to allocate an object due to insufficient space in the Java heap. The Java Garbage Collector (GC) cannot free up the space required for a new object, which causes a java. lang.


1 Answers

G1 (-XX:+UseG1GC), Parallel scavenge (-XX:+UseParallelGC) and ParallelOld (-XX:+UseParallelOldGC) do return memory when the heap shrinks. I'm not so sure about Serial and CMS, they didn't shrink their heap in my experiments.

Both parallel collectors do require a number of GCs before shrinking the heap down to an "acceptable" size. This is per design. They are deliberately holding on to the heap assuming that it will be needed in the future. Setting the flag -XX:GCTimeRatio=1 will improve the situation somewhat but it will still take several GCs to shrink a lot.

G1 is remarkably good at shrinking the heap fast, so for the usecase described above I would say it's solvable by using G1 and running System.gc() after having released all caches and class loaders etc.

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6498735

like image 198
M.Z Avatar answered Oct 14 '22 04:10

M.Z