I wanted to experiment with the -Xmx option in Java and created a simple test program that allocates 1 Mib at the time until it runs out of memory.
import java.util.Vector;
public class MemoryMuncher {
static final int MiB = 1048576;
static final int accellerator = 1;
public static void main(String[] args) {
Vector victor = new Vector();
int i = 1;
try {
while (true) {
byte roger[] = new byte[MiB * accellerator];
victor.add(roger);
Runtime rt = Runtime.getRuntime();
System.out.printf("free: %,6d\t\ttotal: %,6d\t\tallocated: %,6d \n", rt.freeMemory()/MiB, rt.totalMemory()/MiB, i++*accellerator);
}
} catch (OutOfMemoryError e) {
System.out.println(e);
}
}
}
When I run the program with the -Xmx option, I'm noticing a difference in behavior between Java 8 (1.8.0.131) and Java 9 (9.0.1).
Java 8 works close to what I would expect. When I run the program with -Xmx256M, it allocates 233MiB before throwing an OutOfMemoryError.
free: 361 total: 368 allocated: 1
free: 360 total: 368 allocated: 2
...
free: 12 total: 245 allocated: 232
free: 11 total: 245 allocated: 233
java.lang.OutOfMemoryError: Java heap space
However, when I run the same program with Java 9, it only gets about halfway (127 MiB) before I get the exception. This is consistent. If I change the -Xmx to 512M or 1024M, it only gets to half of that amount (255 MiB and 510 MiB respectively). I have no explanation for this behavior.
free: 250 total: 256 allocated: 1
free: 247 total: 256 allocated: 2
...
free: 2 total: 256 allocated: 126
free: 1 total: 256 allocated: 127
java.lang.OutOfMemoryError: Java heap space
I have searched the documentation for any changes in memory management between Java 8 and Java 9, but did not find anything.
Does anyone have any insight why Java 9 would manage/allocate memory differently in some cases from previous Java versions?
I found this very interesting article about how Java 9 planned to garbage collect differently.
More specifically, Java 9 plans to use the G1 Garbage collector which divides memories into fixed sizes. What your code may be doing is triggering the memory to split into two fixed size blocks for Java 9. The reason it would do this is that it would save the other half of memory for moving everything and 'compacting' the memory still in use. However, because you just keep using memory, it would just break immediately when ~1/2 the memory was used. I'm not sure how rigorous of an CS you may have, but at my school we studied simple garbage techniques, and one such simple one that G1GC reminds me of is the stop-and-copy technique which switches the allocated memory between halves of memory while it 'compacts' the memory as it does this.
With Java 8, the Parallel Collector is used, which does is different from the G1GC as all it cares about is throughput. Thus, closer to 100% of the true heap will be used, at the cost of longer GC times. Of course, this is a tradeoff between the two versions, but you can get around this be explicitly specifying the kind of GC to use. For example, if on Java 9 you use the option:
-XX:UseParallelGC
you should see the same behavior as you do with Java 8. The article I linked has all the different options, but I think you get the idea. Hope this helps/answer your question, I actually didn't realize this until you brought it up, so thank you for making me aware of this.
EDIT: According to Oracle themselves, they say that the new G1 garbage collector is intended for high memory machines, again giving reason why they would not utilize the full heap space to spend less time GC-ing.