Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save didibus/c1b847e8e606d826dc773a3e2a023749 to your computer and use it in GitHub Desktop.
Save didibus/c1b847e8e606d826dc773a3e2a023749 to your computer and use it in GitHub Desktop.
JVM clojure command line args to run with low memory footprint

If you want to minimimze the memory overhead of the running JVM, as well as make it so the consumed heap size stays as close to the actual ammount of consumed heap from your app, I've found running with the following args to work best:

clojure -J-XX:+UseSerialGC -J-Xmx256m -J-Xms1m -J-Xss256k -J-XX:MaxHeapFreeRatio=10 -J-XX:MinHeapFreeRatio=1 -J-XX:-ShrinkHeapInSteps -J-X
X:TieredStopAtLevel=1
  • SerialGC has the least memory overhead of all GCs, in that it itself doesn't require a lot of memory to run.
  • SerialGC is also good at quickly releasing Heap memory back to the OS if you configure it as such.
  • TieredStopAtLevel=1 will disable C2 compilation, this used to be called -client option in older JDKs, it won't run as fast due to less optimized compilation, but it needs a lot less memory.

Explanation:

  • -XX:+UseSerialGC : Tells JDK to use SerialGC as the GC. This GC uses the least memory, but will have big pause times that get longer as the heap size increases since it is single threaded and stop the world.
  • -Xmx256m : Sets the maximum heap size, you can actually make this really big if you want, the other settings will manage releasing what's unused, though having it be tighter on your true memory needs might trigger the GC to run more often helping to quickly release memory. If you get OutOfMemory errors, you might need to increase it, or find ways to use less memory in your app.
  • -Xms1m : Sets the minimum heap size, we don't want to over reserve, so we make it real small. This also might control the step incremenent, so by how much would heap grow when it needs, so keeping it small is good for not over-reserving.
  • -Xss256k : Sets the per-thread stack size. If you get StackOverFlows you might need to increase it, but keeping it tight means less memory overhead per-thread.
  • -XX:MaxHeapFreeRatio=10 : The JVM heap is split in sections called generations. This setting controls when the JVM will shrink each generation. So here it says when 10% of a generation in the heap is free, shrink the generation size, aka release memory back to the OS.
  • -XX:MinHeapFreeRatio=1 : This one controls when the JVM will grow each generation. It says if there's only 1% free memory left in the generation than grow it (up to MaxHeapFreeRatio), not sure by how much it grows it for though. Again here "growing" means reserving more memory for it from the OS.
  • -XX:-ShrinkHeapInSteps : Normally the SerialGC doesn't run to completion on each GC run, in that it doesn't loop over all objects to free all the ones that are garbage. It only does a little bit of it each time the GC runs, to minimize pause times. This disables that, and says each time the GC runs, loop over all objects and clean up all garbage. Again, this makes it that memory is reclaimed much faster.
  • -XX:TieredStopAtLevel=1 : The JVM JIT compiler is tiered, code starts interpreted, then gets profiled, eventually could get compiled with small optmization, that's called C1 which is also Level=1, and it can also be compiled with lots of optimization, which is Level=4 or called C2. Compiling with more optimization requires more profiling and more memory and thus will have more overhead. Do not using this means code will be less optmized and thus might run slower.

If -XX:TieredStopAtLevel=1 slows things down too much, you can also try instead to use -XX:-TieredCompilation:

  • -XX:-TieredCompilation : This will disable tiered compilation, but it won't disable or restrict compilation. What it does is simply have everything compile directly to C2. I've found this too uses less memory, to a bit more than Level=1, but it could result in faster code. I think it is still slower than tiered compilation though, since it won't perform as much profiling.

Here's the alternative with -TieredCompilation:

clojure -J-XX:+UseSerialGC -J-Xmx256m -J-Xms1m -J-Xss256k -J-XX:MaxHeapFreeRatio=10 -J-XX:MinHeapFreeRatio=1 -J-XX:-ShrinkHeapInSteps -J-XX:-TieredCompilation

You can also try with -Xint instead, this tells the JIT to turn itself off, and all code will run interpreted only. This makes code a lot slower, its no longer JIT compiled, but uses even less memory:

clojure -J-XX:+UseSerialGC -J-Xmx256m -J-Xms1m -J-Xss256k -J-XX:MaxHeapFreeRatio=10 -J-XX:MinHeapFreeRatio=1 -J-XX:-ShrinkHeapInSteps -J-Xint

And I've also noticed that -XX:+UseParallelGC seems to also not have a big overhead, very close to -XX:+UseSerialGC, so you can try the same command with that GC instead:

Note: I don't know if -XX:-ShrinkHeapInSteps does anything for ParallelGC.

clojure -J-XX:+UseParallelGC -J-Xmx256m -J-Xms1m -J-Xss256k -J-XX:MaxHeapFreeRatio=10 -J-XX:MinHeapFreeRatio=1 -J-XX:-ShrinkHeapInSteps -J-XX:TieredStopAtLevel=1

And this might help throughput since GC will be parallel now. I've also seen some people add -J-XX:GCTimeRatio=4 -J-XX:AdaptiveSizePolicyWeight=90 which seems to maybe help even more with memory when using ParallelGC, but I'm not sure what they do:

clojure -J-XX:+UseParallelGC -J-Xmx256m -J-Xms1m -J-Xss256k -J-XX:MaxHeapFreeRatio=10 -J-XX:MinHeapFreeRatio=1 -J-XX:-ShrinkHeapInSteps -J-XX:TieredStopAtLevel=1 -J-XX:GCTimeRatio=4 -J-XX:AdaptiveSizePolicyWeight=90
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment