Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

is -Xmx a hard limit?

Tags:

java

java-7

This SO answer clarifies a few things about the -Xmx JVM flag. Trying to experiment I did the following:

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

public class FooMain {

    private static String memoryMsg() {
        return String.format("%s. %s. %s"
                             , String.format("total memory is: [%d]",Runtime.getRuntime().totalMemory())
                             , String.format("free memory is: [%d]",Runtime.getRuntime().freeMemory())
                             , String.format("max memory is: [%d]",Runtime.getRuntime().maxMemory()));
    }

    public static void main(String args[]) {
        String msg = null;
        try {
            System.out.println(memoryMsg());
            List<Object> xs = new ArrayList<>();
            int i = 0 ;
            while (true) {
                xs.add(new byte[1000]);
                msg = String.format("%d 1k arrays added.\n%s.\n"
                                    ,  ++i
                                    , memoryMsg());
            }
        } finally {
            System.out.printf(msg);
        }
    }
}

Compile it with javac FooMain.java. When I run it with a maximum heap size of 5 million bytes I get:

java -Xmx5000000 FooMain
total memory is: [5242880]. free memory is: [4901096]. max memory is: [5767168]
4878 1k arrays added.
total memory is: [5767168]. free memory is: [543288]. max memory is: [5767168].
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
          at java.lang.String.toCharArray(String.java:2748)
          at java.util.Formatter$FormatSpecifier.print(Formatter.java:3048)
          at java.util.Formatter$FormatSpecifier.printInteger(Formatter.java:2744)
          at java.util.Formatter$FormatSpecifier.print(Formatter.java:2702)
          at java.util.Formatter.format(Formatter.java:2488)
          at java.util.Formatter.format(Formatter.java:2423)
          at java.lang.String.format(String.java:2792)
          at FooMain.memoryMsg(FooMain.java:7)
          at FooMain.main(FooMain.java:21)

While the numbers are close enough, they don't seem very exact (with the exception of the total memory at the end reaching exactly the max memory). In particular 5368 arrays of 1000 bytes each should take more than 5000000 or even 5242880 bytes. How should these numbers be understood?

This is the java I'm using:

java version "1.7.0_80"
Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
Java HotSpot(TM) Server VM (build 24.80-b11, mixed mode)
like image 701
Marcus Junius Brutus Avatar asked Oct 16 '15 07:10

Marcus Junius Brutus


2 Answers

Looking at the OpenJDK source code, the logic to determine the max heap size is quite complex, and is determined by a lot of variables. The actual heap size is set in hotspot/src/share/vm/memory/collectorPolicy.cpp, it uses the provided -Xmx value as input, and aligns it up, using the following code:

align_size_up(MaxHeapSize, max_alignment());

align_size_up is defined as:

#define align_size_up_(size, alignment) (((size) + ((alignment) - 1)) & ~((alignment) - 1))

And max_alignment is the product of the virtual memory page size and the JVM garbage collection card size. The VM page size is 4096 bytes and the card size is 512 bytes. Plugging those values in gives an actual MaxHeapSize of 6324224 bytes, which would correspond well with the numbers you are seeing.

Note: I only looked briefly in the JVM code, so it is possible I have missed something, but the answer seems to add up with what you are seeing.

like image 165
Petter Avatar answered Oct 21 '22 03:10

Petter


is -Xmx a hard limit?

It depends what you mean by "hard". If you mean "cannot be changed by the program" then Yes. If you mean "precise", then No. The sizes of the various spaces that the garbage collector uses is a multiple of some power of 2 of bytes. And apparently, the JVM rounds upwards rather than down.

Example program which allocates 1000 byte arrays

How should these numbers be understood?

An 1000 byte Java array does not occupy precisely 1000 bytes:

  • There is an object header, including space for a 32 bit length field. (Typically 3 x 32 bits total).

  • Heap nodes are allocated in multiples of 2^N bytes. (Typically 2^3 == 8)


Then you have the question of what is causing the OutOfMemoryError. You might think that it is because the heap is completely full. However, in this case the message says "GC overhead limit exceeded". That means that the JVM has detected that the JVM is spending too large a percentage of the overall CPU time running the garbage collector. That's what has killed the GC ... not running out of memory.

The rationale for the "GC overhead limit" is that when a heap gets closeto full, the GC runs frequently and manages to reclaim less and less memory each time. When you get into a GC "death spiral", it is better to pull the plug quickly rather than allowing the application to grind on to its ultimate point of failure.

Anyway ... what this means is that your heuristic for figuring out how much memory is allocated when the heap is full is probably incorrect.

like image 5
Stephen C Avatar answered Oct 21 '22 03:10

Stephen C