30

When you add

 -Xmx????m

to the command line, the JVM gives you a heap which is close to this value but can be out by up to 14%. The JVM can give you a figure much closer to what you want, but only through trial and error.

 System.out.println(Runtime.getRuntime().maxMemory());

prints

-Xmx1000m ->  932184064
-Xmx1024m -Xmx1g ->  954728448
-Xmx1072m ->  999292928
-Xmx1073m -> 1001390080

I am running HotSpot Java 8 update 5.

Clearly, the heap can be something just above 1000000000 but why is this -Xmx1073m instead of say -Xmx1000m?

BTW 1g == 1024m which suggests that 1g should be 1024^3 which is 7% higher than 1000^3 but you get something 7% lower than 1000^3.


Being off by so much suggests that I am missing something fundamental about how the heap works. If I asked for -Xmx1000m and it was 1001390080 I wouldn't care, I would assume there is some allocation multiple it needs to adhere to, but to give you 932184064 suggests to me the heap is more complicated than I can imagine.


EDIT I have found that

-Xmx1152m gives 1073741824 which is exactly 1024^3

so it appears it is giving me exactly 128 MB less than I asked for in this case cf the maxMemory().


BTW 128 is my favourite number. I was in a conference today at street number 128 and the speaker quoted a book from page 128 ;)

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • 3
    Why do you care that it is exactly `1000000000`? – Absurd-Mind May 16 '14 at 17:28
  • 2
    @Absurd-Mind It is just one of those impresice things which has bugged me over the years. It is also different in different JVMs as well so you can't be sure what you will really get between systems. – Peter Lawrey May 16 '14 at 17:30
  • 3
    @Absurd-Mind It doesn't have to be exact, but does it have to be off by so much? It suggests that I am missing something fundamental about how the heap works. – Peter Lawrey May 16 '14 at 17:31
  • I guess that the missing bytes are things like PermGenSpace and such – Absurd-Mind May 16 '14 at 17:33
  • @Absurd-Mind right, I want to know what the `and such` is. BTW Java 8 doesn't have a PermGen and it never counted to the heap size. – Peter Lawrey May 16 '14 at 17:35
  • Do successive runs with the same `-Xmx` value give consistent results for `maxMemory()`? – Alex May 16 '14 at 17:38
  • 1
    1000 MB in RAM is 1,048,576,000 (1000 * 2**20) bytes. If anything your short 11% on that first figure. – Dev May 16 '14 at 17:40
  • 1
    @Alex Yes, I get the same numbers on the same machine. BTW `-Xmx1g` == `-Xmx1024m` which makes it even stranger. – Peter Lawrey May 16 '14 at 17:41
  • Getting the same `1073741824` with `-Xmx1152m` on Java 6.0_45, seems like it's been like that for a while. – Jonathan Drapeau May 16 '14 at 17:49
  • While others have mentioned permgen, there's also the code cache. I think it defaults to 32M, or so. – David Ehrmann May 16 '14 at 18:30
  • 1
    Java version "1.7.0_79" on Ubuntu 14.04 also yields 1073741824 with `-Xmx1152m`. I note that this is 128 MiB more than 1 GiB, not 128 MB more than 1 GB. 128 MB is 128,000,000. See https://en.wikipedia.org/wiki/Binary_prefix to learn about mebibytes and gibibytes :) – nealmcb Sep 30 '15 at 04:50
  • 1
    @nealmcb forgive us old-timers for using MB and GB to refer to base-2 numbers, that's how it always used to be. – Michael Warner Jun 15 '16 at 20:35
  • @MichaelWarner For memory, hardware vendors still say you have a machine with 8 GB / 32 GB not 8.58 MB or 8 MiB / 34.3 MB or 32 MiB. In Java, GB/GiB/Gb/gb (all appears in the source) always means 1024^3 – Peter Lawrey Jun 15 '16 at 22:10
  • @MichaelWarner and Peter: Yeah, I feel your pain. For main memory, as used here, it remains common to mis-use the real "old time" metric/SI prefixes. But for a confusing array of other byte prefixes, e.g. hard disk capacity, DVD (but not CD!) capacity, and bandwidth, SI usage is standard. The massive confusion that results is why we continue to see a shift to be clear and precise, by using SI or binary prefixes like MiB as appropriate, most everywhere. – nealmcb Jun 17 '16 at 14:44
  • @nealmcb The one which bugs me is people use `b` for bytes and `b` for bits. If you ask some which is faster; memory speed of 49 mb/s, disk write of 50 mb/s or a network of 100 mb/s ? The memory speed of course o_O it most likely 49 MiB/s – Peter Lawrey Jun 17 '16 at 17:55

2 Answers2

35

The difference appears to be accounted for by the size of the garbage collector's survivor space.

The -Xmx flag, as described in the docs, controls maximum size of the memory allocation pool. The heap portion of the memory allocation pool is divided into Eden, Survivor, and Tenured spaces. As described in this answer, there are two survivor regions, only one of which is available to hold live objects at any given point in time. So the total apparent space available for allocating objects, as reported by Runtime.maxMemory(), must subtract the size of one of the survivor spaces from the total heap memory pool.

You can use the MemoryMXBean and MemoryPoolMXBean classes to get a little more information about your memory allocation. Here's a simple program I wrote:

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryPoolMXBean;

public class MemTest {
  static String mb (long s) {
    return String.format("%d (%.2f M)", s, (double)s / (1024 * 1024));
  }

  public static void main(String[] args) {
    System.out.println("Runtime max: " + mb(Runtime.getRuntime().maxMemory()));
    MemoryMXBean m = ManagementFactory.getMemoryMXBean();

    System.out.println("Non-heap: " + mb(m.getNonHeapMemoryUsage().getMax()));
    System.out.println("Heap: " + mb(m.getHeapMemoryUsage().getMax()));

    for (MemoryPoolMXBean mp : ManagementFactory.getMemoryPoolMXBeans()) {
      System.out.println("Pool: " + mp.getName() + 
                         " (type " + mp.getType() + ")" +
                         " = " + mb(mp.getUsage().getMax()));
    }
  }
}

The output of this on OpenJDK 7 for java -Xmx1024m MemTest is:

Runtime max: 1037959168 (989.88 M)
Non-heap: 224395264 (214.00 M)
Heap: 1037959168 (989.88 M)
Pool: Code Cache (type Non-heap memory) = 50331648 (48.00 M)
Pool: Eden Space (type Heap memory) = 286326784 (273.06 M)
Pool: Survivor Space (type Heap memory) = 35782656 (34.13 M)
Pool: Tenured Gen (type Heap memory) = 715849728 (682.69 M)
Pool: Perm Gen (type Non-heap memory) = 174063616 (166.00 M)

Note that Eden + 2*Survivor + Tenured = 1024M, which is exactly the amount of heap space requested on the command line. Much thanks to @Absurd-Mind for pointing this out.

The differences you observe between different JVMs are likely due to differing heuristics for selecting the default relative sizes of the various generations. As described in this article (applies to Java 6, wasn't able to find a more recent one), you can use the -XX:NewRatio and -XX:SurvivorRatio flags to explicitly control these settings. So, running the command:

java -Xmx1024m -XX:NewRatio=3 -XX:SurvivorRatio=6

You're telling the JVM that:

Young:Tenured = (Eden + 2*Survivor):Tenured = 1:3 = 256m:768m
Survivor:Eden = 1:6 = 32m:192m

So, with these parameters, the difference between the requested -Xmx value and the available memory reported by Runtime.maxMemory() should be 32m, which is verified using the above program. And now you should be able to accurately predict the available memory reported by Runtime for a given set of command-line arguments, which is all you ever really wanted, right?

Community
  • 1
  • 1
Alex
  • 13,811
  • 1
  • 37
  • 50
  • 5
    If you sum up all Heap space and take the Surviver two times you get exactly `1024m`. Eden + 2*Survivor + Tenured: `715849728 + 2 * 35782656 + 286326784 = 1073741824 = 1024M`. Maybe this space gets duplicated in some way? – Absurd-Mind May 16 '14 at 18:50
  • Wow, nice call. I was looking for a way to make the numbers add up but couldn't. http://stackoverflow.com/questions/10695298/java-gc-why-two-survivor-regions – Alex May 16 '14 at 18:55
  • i had a look into the JVM source, heap sizes will be aligned on page sizes, so there still may be some difference. On my machine (windows7, x64, oracle jdk 1.7) the heap size can only be set in 512k steps. – Absurd-Mind May 16 '14 at 20:31
  • Yeah, I'm not surprised. I've only seen people setting -Xmx in multiples of 1m so in most cases I wouldn't expect it to be an issue. – Alex May 16 '14 at 20:35
  • BTW, doing the math on the program output without setting the ratios indicates the default ratios on my platform (Ubuntu x64, OpenJDK 7 u51) are `NewRatio=2` and `SurvivorRatio=8`. – Alex May 16 '14 at 20:41
  • Awesome! Very very clear in almost all respects, but confusing because you mix up decimal and and binary powers. Since you're calculating mebibytes rather than megabytes, you should change `"%.2f M"` to `"%.2f MiB"` as discussed at https://en.wikipedia.org/wiki/Binary_prefix – nealmcb Sep 30 '15 at 04:56
0

The following graph shows the Runtime.maxMemory as a percentage of Xmx (on the y-axis) for different Xmx values (on the x-axis).

We see that for the most part only ~85% of the Xmx setting is available for the heap to grow. This analysis is conducted using java version "1.8.0_212" Java(TM) SE Runtime Environment (build 1.8.0_212-b10) Java HotSpot(TM) 64-Bit Server VM (build 25.212-b10, mixed mode)

Graph of (maxMem/Xmx) for varying Xmx sizes (in MB)