Java's RAM usage doesn't correspond to what the Task Manager says
Conclusion:
Use the garbage first (G1) GC (default GC in Java 9), this garbage collector also shrinks the heap size (which, in conclusion, will also shrink the overall "native memory" used) on garabage collections, compared to the ParallelOldGC (default GC in Java 7 and Java 8), which seldom to never shrinks the heap size!
Generally:
Your basic assumption is wrong.
You assume your code snippet shows the heap size. This is not correct. It shows the heap utilization. This means "How much space of my heap is used?". Runtime.getRuntime().totalMemory()
shows the heap size, Runtime.getRuntime().freeMemory()
shows the free heap size, their difference shows the heap utilization (used size).
Your heap starts with an initial size, with 0 bytes utilization because no object is yet created, and a max heap size. Max heap size describes the size to which the garbage collector is allowed to resize the heap (e.g. if there is not enough space for a very large object)
As next step after creating the empty heap, automatically, some objects are loaded (class objects, etc.), they generally should fit easily in the initial heap size.
Then, your code starts running and allocates objects. As soon as there is no more space in your Eden space (the heap is splitted up into the young generation (Eden, survivor-from and survivor-to space) and old generation, look up additional resources if you are interested in these details), a garbage collection is triggered.
During a garbage collection, the garbage collector may decide to resize the heap (as mentioned above when talking about max heap size). This happens in your case, because your initial heap size is too small to fit your 1GB object. Therefore the heap size is increased, somewhere between initial heap size and max heap size.
Then, after your big object died, the next GC could make the heap smaller again, but it does not have to. Why? It's below the max heap size, that's all the GC cares for. Some garbage collection algorithms shrink the heap again, some don't.
Espacially the ParallelOldGC, the default GC in Java 7 and Java 8, does seldom to never shrink the heap.
If you want a GC that also tries to keep heap size small by shrinking it during a garbage collection, try the garabage first (G1) GC by setting the -XX:+UseG1GC
Java flag.
Example:
This will print out all values in byte.
You will get an overview, how both GCs work and how many space is used when using either of them.
System.out.println(String.format("Init:\t%,d",ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getInit()));
System.out.println(String.format("Max:\t%,d%n", ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax()));
Thread outputThread = new Thread(() -> {
try {
int i = 0;
for(;;) {
System.out.println(String.format("%dms\t->\tUsed:\t\t%,d", i, ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed()));
System.out.println(String.format("%dms\t->\tCommited:\t%,d", i, ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getCommitted()));
Thread.sleep(100);
i += 100;
}
} catch (Exception e) { }
});
Thread allocThread = new Thread(() -> {
try {
int val = 0;
Thread.sleep(500); // Wait 1/2 second
createArray();
Thread.sleep(500); // Wait another 1/2 seconds
System.gc(); // Force a GC, array should be cleaned
return;
} catch (Exception e) { }
});
outputThread.start();
allocThread.start();
createArray()
is just the following small method:
private static void createArray() {
byte[] arr = new byte[1024 * 1024 * 1024];
}
--Result ParallelOldGC:
Init: 262,144,000
Max: 3,715,629,056
0ms -> Used: 6,606,272
0ms -> Commited: 251,658,240
100ms -> Used: 6,606,272
100ms -> Commited: 251,658,240
200ms -> Used: 6,606,272
200ms -> Commited: 251,658,240
300ms -> Used: 6,606,272
300ms -> Commited: 251,658,240
400ms -> Used: 6,606,272
400ms -> Commited: 251,658,240
500ms -> Used: 1,080,348,112
500ms -> Commited: 1,325,924,352
600ms -> Used: 1,080,348,112
600ms -> Commited: 1,325,924,352
700ms -> Used: 1,080,348,112
700ms -> Commited: 1,325,924,352
800ms -> Used: 1,080,348,112
800ms -> Commited: 1,325,924,352
900ms -> Used: 1,080,348,112
900ms -> Commited: 1,325,924,352
1000ms -> Used: 1,080,348,112
1000ms -> Commited: 1,325,924,352
1100ms -> Used: 1,080,348,112
1100ms -> Commited: 1,325,924,352
1200ms -> Used: 2,261,768
1200ms -> Commited: 1,325,924,352
1300ms -> Used: 2,261,768
1300ms -> Commited: 1,325,924,352
You can see, my heap starts with an initial size of about 260MB, with an allowed maximum size (size to which the GC may decide to resize you heap) of about 3,7 GB.
Before creating the array, about 6MB of my heap is used. Then the big array gets created, and my heap size (commited size) is increased to 1,3GB, with about 1GB (the array) used. Then I force a garbage collection, that collects the array. Yet, my heap size stays at 1,3GB, because the GC does not care about shrinking it again, just the utilization goes down on 2MB.
--Result G1:
Init: 262,144,000
Max: 4,179,623,936
0ms -> Used: 2,097,152
0ms -> Commited: 262,144,000
100ms -> Used: 2,097,152
100ms -> Commited: 262,144,000
200ms -> Used: 2,097,152
200ms -> Commited: 262,144,000
300ms -> Used: 2,097,152
300ms -> Commited: 262,144,000
400ms -> Used: 2,097,152
400ms -> Commited: 262,144,000
500ms -> Used: 1,074,364,464
500ms -> Commited: 1,336,934,400
600ms -> Used: 1,074,364,464
600ms -> Commited: 1,336,934,400
700ms -> Used: 1,074,364,464
700ms -> Commited: 1,336,934,400
800ms -> Used: 1,074,364,464
800ms -> Commited: 1,336,934,400
900ms -> Used: 1,074,364,464
900ms -> Commited: 1,336,934,400
1000ms -> Used: 492,520
1000ms -> Commited: 8,388,608
1100ms -> Used: 492,520
1100ms -> Commited: 8,388,608
1200ms -> Used: 492,520
1200ms -> Commited: 8,388,608
And here we go! The G1 GC cares about small heaps! After the the object is cleaned, not only the utilization goes down to about 0,5MB but also the heap size gets shrinked to 8MB (compared to 1,3GB in ParallelOldGC)
Further info:
But, keep in mind, that the heap size will still differ from what is shown in the task manager. The following image from Wikipedia - Java virtual machine illustrates that the heap is only a part of the full JVM memory:
The heap is just one region in the memory of a JVM. It is not unusual for a JVM to have an extra 200 - 400 MB over and above the maximum heap size for things like the shared libraries, code, thread stacks, direct memory and GUI components.
So while on 2 MB (MB = mega-byte, Mb = Mega-bit) of object might being used at that moment, the application can be reserving a lot more.
Is there a way to reduce the native memory used, so that it is close to the heap memory?
You can use an older version of Java which tended to use less memory, a smaller maximum heap and perm gen, use less additional resource.