Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java's RAM usage doesn't correspond to what the Task Manager says

Tags:

java

memory

jvm

ram

I have been playing with Java's JVM by making a 1024^3 (basically 1Gb) length byte array. I measured the RAM usage before, after the array creation and after the array's destruction by the garbage collector, using both the Task Manager (looking at the process) and this little snippet:

public static void showMemory() {
    System.out.println("Memory used: "
            + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024.D * 1024.D) + "mB.");
}

The aforementioned code shows 2Mb, 1029Mb and 2Mb, respectively. -> It all seems normal. However, when looking at the TaskManager, the RAM usage by Java is 2mb at first, then goes to 1052Mb and stays there, even if the snippet shows 2Mb.

As I would like to have Java use the least resource, how can I solve this problem?

Edit:

I have done some research and have figured out the terms to use. In fact, the native memory's does not resemble the heap memory's value, and is often greater than the heap memory. Is there a way to reduce the native memory used, so that it is close to the heap memory?

like image 544
Alexandre De Angelis Avatar asked Feb 21 '16 17:02

Alexandre De Angelis


People also ask

Is Task Manager memory RAM accurate?

As for memory usage reporting, the Task Manager is accurate. If you want a second opinion, you can go to www.sysinternals.com and download Process Explorer which is quite useful for tracking system resources too.

Why does Task Manager show high memory usage?

Why is my memory usage so high in Windows 10? One reason could be a big program or game that takes high system RAM. The other reason could be malware that caused your device high memory usage.


2 Answers

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:

JVM memory

like image 110
Markus Weninger Avatar answered Sep 28 '22 06:09

Markus Weninger


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.

like image 31
Peter Lawrey Avatar answered Sep 28 '22 07:09

Peter Lawrey