1

I created for myself a Java rendering app for Windows (Win7 x64) that uses really a lot of memory during its rendering process (literally gigs in some huge projects...I have 8GB RAM in my PC and 6-core CPU) so I have to allocate like 4GB or even 8GB of RAM for it in my .bat file that starts my java application like this:

@ECHO OFF
java -Xmx8G -server -jar myapp %*
@if %errorlevel% neq 0 pause

When the rendering process ends it should unload all that is not needed anymore from the physical memory, and according to my in-app calculation ((r.totalMemory() - r.freeMemory()) / (1024.0 * 1024) + "MB") it really does that (I call System.gc() after the rendering thread ends: without this it is reporting no drop of memory usage - it is around 4GB, with this it report about 80MB of used memory).

My app usage of RAM is basically like this:

  • about 30MB without anything loaded yet, just the main app
  • about 80MB when I load some project (it does not matter how big, it would differ just in a few MB cos more GUI elements would be created than with some simple one)
  • up to 4GB when rendering is active
  • falls back to about 80MB when rendering ends

I even used memory profiling app from JAVA bin folder called Java VisualVM to be sure everything works as it should without any memory leaks...and according to its heap dump it really is working like that:

  • memory used with loaded project after the end of rendering is about 80MB
  • no open processes found
  • the largest files are like 3 files each of 25MB +/-

But I am really confused why when I look at the Windows graph of Memory Usage in Windows Task Manager there is almost absolutely no drop of the memoroy usage at all (maybe just a few MB's): it should drop like in hundreds of MB if not thousands (with said huge projects) - it unloads only after I close my app. And I really waited like 5 minutes, 10 minutes, 15 minutes...still no drop whatsoever in that graph window!

So I wonder: is there some specific switch I need to add to my .bat that would tell JVM "please, do unload all the unused memory my app used during render" or...?

qraqatit
  • 492
  • 4
  • 14

2 Answers2

4

Runtime.totalMemory() returns the amount of memory, the JVM has requested from the operating system. Within that memory, the notion of “used” and “unused” are only relevant to the Java application, not to the operating system. So Runtime.freeMemory() tells you how much memory within that total memory is available to new allocations, whereas the difference between these values tells you, how much memory is occupied by Java objects, either still in use or not collected yet.

From the operating system’s point of view, the value reported by totalMemory() is the amount of memory that has been requested by the application and would be considered “in use” in a simple system. So when the JVM gives memory back to the operating system, which happens at a much lower frequency than garbage collection or never at all, depending on the configuration, that number reported by totalMemory() would decrease.

Unfortunately, an operating system like Windows doesn’t work that simple. It has its own notion of “used memory” which differs from both, the requested memory and Java’s notion of “in use”. The memory is organized in pages and Windows will consider a page only being in use, when the application has actually written into it, so it contains something that need to be kept.

Further, everything discussed so far is “virtual memory” and the way, it maps to physical memory, is another complex matter. Windows will try to keep the pages of virtual memory mapped to pages in physical memory, but the memory demands of other processes can make it reduce the physical memory in use by a process. This fact has been used by hoax tools promising to “clean up memory” by simply requesting a lot of memory, causing Windows to reassign the physical memory, then releasing the memory, so the number of used physical memory looks impressively low, because it has been taken away from the running processes, but of course, they will get it back later when they proceed, so the action only reduced performance.

The takeaway is, the graph in the task manager can turn out to be pretty meaningless, without the understanding of what’s actually needed by the running applications. The relationships between “allocated (virtual) memory”, “actually used (virtual) memory”, and “currently used (physical) memory” can’t be expressed with a single simple graph.

Further, garbage collection within the virtual machine fulfills the primary task of making memory within the virtual machine available to new allocations within the same virtual machine, not to give memory back to the operating system. The latter may sometimes happen, depending on the configuration, but not in general.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • So if I understand it correctly there is no way the JVM would release that unused previously allocated memory to system, it basically tells how much memory was freed/used within those 8GB I allocated for my Java app thus there is no chance that Windows would recognized it as available as it considers those 8GB I allocated as always in used until I close my app - is this assumption of mine correct? – qraqatit Sep 13 '19 at 11:16
  • @qraqatit that release is possible and in java-12 there was a separate JEP, that IIRC, releases memory back on major collections, there are settings to control that. – Eugene Sep 13 '19 at 11:26
  • I said, this depends on the configuration. First, which garbage collection algorithm has been chosen, then, which thresholds for giving memory back to the system have been configured. And the runtime behavior E.g. in [JDK-6498735](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6498735) it is said: “*Both parallel collectors do require a number of GCs before shrinking the heap down to an "acceptable" size. This is per design.*” See also [Does the JVM give back free memory to the OS when no longer needed?](https://stackoverflow.com/q/4625103/2711488). – Holger Sep 13 '19 at 11:29
  • The JEP @Eugene mentioned, is [JEP-346](https://openjdk.java.net/jeps/346). It also states that G1 *does* return memory, but not immediately and the requested improvement is about returning it earlier (“promptly”). The other point, my answer tried to emphasize, is that this returned memory has less importance to your system, as Window’s own memory management will cut down the physical memory of an application anyway, if needed by another application. And when not needed, that graph is just a meaningless number. – Holger Sep 13 '19 at 11:35
  • @Eugene can you elaborate some more on how to do that? Maybe some real example that I could try? You see I am still Java newbie so some stuff that looks trivial to you guys are quite over my actual knowledge without some example... – qraqatit Sep 13 '19 at 12:03
  • @Holger thanx for the links, I will look into that – qraqatit Sep 13 '19 at 12:04
  • @Holger so the answer signed as correct in the link you provided states that using `-XX:MaxHeapFreeRatio` and `-XX:MinHeapFreeRatio` should do that for me - I think it is another switch for the bat but I have no clue how to use it: should I literally write it like that or should those values be replaced by something? – qraqatit Sep 13 '19 at 12:06
  • They have the form of, e.g. `-XX:MinHeapFreeRatio=40 -XX:MaxHeapFreeRatio=70`. The values are percentages of the total memory. They tell the JVM that the free memory should be within this range, otherwise, it will expand or shrink the heap. – Holger Sep 13 '19 at 12:25
  • @Holger so can I write it in my bat like `java -XX:MinHeapFreeRatio=2 -XX:MaxHeapFreeRatio=70 -Xmx8G -server -jar myapp %*` ? Does it matter in which position those switches're placed, like before or after `-Xmx8G`? Do I understand it correctly that the `-XX:MinHeapFreeRatio` sets the minimum memory it needs to stay at after `System.gc()`? I need to fall back to 100MB I'd set it to as `-XX:MinHeapFreeRatio=2` (Math.ceil(100MB / (8000MB/100)))? Would that unload the memory previously allocated by the render to those 100MB (app with loaded project after render ends)? – qraqatit Sep 13 '19 at 12:55
  • The only thing that matters, is that they have to be before the `-jar myapp`. For all these JVM options, the order doesn’t matter. With `-XX:MinHeapFreeRatio=2`, you make the JVM more reluctant regarding expanding the heap, but you have to reduce the `XX:MaxHeapFreeRatio`, to make it more aggressive regarding shrinking. Note that I just made a test with JDK 11 and it gave back the memory smoothly with the default settings already, when the free memory is in the order of magnitude, you described. – Holger Sep 13 '19 at 13:17
  • @Holger by default values do you mean `-XX:MinHeapFreeRatio=40 -XX:MaxHeapFreeRatio=70`? Cos if so, I did use exactly that like `java -XX:MinHeapFreeRatio=40 -XX:MaxHeapFreeRatio=70 -Xmx8G -server -jar myapp %*` and yet still no change in Windows Physical Memory Graph at all whatsoever...it shows memory still as full, no drop until I close the app – qraqatit Sep 13 '19 at 13:57
  • I meant, I didn’t specify anything to the JDK 11 JVM. As said, there can be a lot of environmental factors. Which precise Java version do you use? Further, The task manage of my Windows 10 might show different stats than Windows 7’s. – Holger Sep 13 '19 at 14:10
  • @Holger nevermind, it is already solved by using `-XX:+UseG1GC` switch, I did not even try that before as I read somewhere there is no need for that as it is made now the default garbage collector for JVM, but I did not check the part where it said FROM THE SPECIFIC VERSION OF JDK :-) Anyway thank you very much for your thought! – qraqatit Sep 13 '19 at 14:25
  • Yes, that’s why I referred to the Java version. With JDK 11, G1GC is the default… – Holger Sep 13 '19 at 14:45
1

SO THIS IS THE FINAL SOLUTION FOR JDK v1.8.0_181 (my case)

It turns out that all I needed to ad to my .bat was -XX:+UseG1GC switch, so my java.bat code now finally looks like this and it is doing exactly what I expect it to: releasing unused memory back to system:

@ECHO OFF
java -XX:+UseG1GC -Xmx8G -server -jar myapp %*
@if %errorlevel% neq 0 pause

The "funny" part is I knew about this switch but as I read somewhere that it is used as default GC collector by default - that is: no need to set it manually - so I ignored that option completely - I just did not check the part where it said FROM THE SPECIFIC VERSION OF JDK :-). Well...

Big thank you goes to @apangin for solution suggestion!

qraqatit
  • 492
  • 4
  • 14