8

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)
Community
  • 1
  • 1
Marcus Junius Brutus
  • 26,087
  • 41
  • 189
  • 331
  • Maybe Garbage Collection kicked in in the meantime ... – Robert Niestroj Oct 16 '15 at 07:23
  • 1
    @RobertNiestroj it can't since all the references are kept – André Stannek Oct 16 '15 at 07:24
  • 1
    @RobertNiestroj why should anything be GCed? I am adding arrays in a `List`. No reference goes out of scope. – Marcus Junius Brutus Oct 16 '15 at 07:24
  • The total memory doesn't really say anything since it's all the memory used by the JVM while Xmx just declares the maximum for the heap space. Still I would agree that 5468 1000 byte arrays shouldn't fit into 5000000 bytes of heap. Very interesting... – André Stannek Oct 16 '15 at 07:30
  • I have run the code on Mac, it always output about 4700 – andy Oct 16 '15 at 07:52
  • I found a clue and was just doing a lot of math when I noticed that it shouldn't even fit in the overall memory. But still wan't the leave the clue: [The documentation](http://docs.oracle.com/javase/7/docs/technotes/tools/solaris/java.html) states "This value must a multiple of 1024" which 5000000 isn't – André Stannek Oct 16 '15 at 07:53
  • @AndréStannek so JVM auto change to 5242880, as it output. – andy Oct 16 '15 at 07:56
  • @andy that was what I suspected but as I said, 5242880 is the total memory, not just the heap space. I would suspect the heap space to be auto changed to a multiple of 1024 between 5000000 and 5242880. But still the arrays shouldn't even fit the total memory, so the mistery remains. – André Stannek Oct 16 '15 at 08:00
  • @AndréStannek yes, agree – andy Oct 16 '15 at 08:04
  • @MarcusJuniusBrutus Oh wait, I just noticed that you print the total memory before creating the arrays. Could you add it to `msg` to see what the total memory is at the end of the program? – André Stannek Oct 16 '15 at 08:05
  • @AndréStannek see updated code and trace. – Marcus Junius Brutus Oct 16 '15 at 10:53

3 Answers3

7

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.

Petter
  • 4,053
  • 27
  • 33
5

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.

Community
  • 1
  • 1
Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
1

Your question is pretty much answered by other people by now, but out of interest I've done the math :-)

It is stated here:

This value must a multiple of 1024 greater than 2 MB.

5000000 is not a multiple of 1024. My guess was that the argument is rounded up to a multiple of 1024, which the other answers confirm. Your total memory (which is more than just the declared heap space) is 5767168 which is 5632 * 1024. It was enlarged during runtime from the initial 5242880 to fit the growing heap space. Note that -Xmx declares the maximum, so it's not necessarily allocated right away. With the help of this source we can aproximate your memory usage (assuming 64bit):

  • 4878000 bytes for the bytes
  • 4878 * 24 = 117072 bytes array overhead
  • a few bytes for the other created objects and the strings
  • a little memory fluctuation because of garbage collection (at least the string you create each iteration could be thrown away)

So the arrays take up 4995072 bytes which (coincidentally?) already would be a multiple of 1024. But there is still the overhead for the other objects. So the heap space is some multiple of 1024 greater than 4995072 and below 5767168. Considering the free space in the end, this leaves us with 228808 bytes for the rest of the heap and non-heap memory usage, which sounds like a plausible number.

Finally a word on the free space left in the end. The JVM doesn't necessarily fill up all the memory before crashing. If the memory is close to running out, garbage collection runs more frequently. If this takes up a certain percentage of the overall runtime, the JVM exits, which happened in your case.

André Stannek
  • 7,773
  • 31
  • 52