Earlier quoted context omitted.
That's not always true. Most of the time when Java uses a ton of memory it's because people use the default memory settings. If you tell Java to use up to 90% of system RAM, it will. Garbage collection is expensive so it will delay until memory is depleted. This is a different GC design than V8 and Go, which use older collector designs with high overhead. They need to collect very frequently because their stop the wo…
I'll shamefully admit that I have been running and writing JVM based services for years and I didn't know this. I thought that the fixed memory overhead for a simple JVM service was simply higher than with CPython as a fact of life. There are times when I'd happily trade more frequent GC pauses for a smaller per-process memory footprint. How do you find a reasonably small Xmx that doesn't lead to OutOfMemoryError exc…
That's tough to figure out. In new versions of Java, I think 14+, if you use ZGC collector it will return unused memory to the OS. Memory options vary depending on collector, but new versions of ZGC support "soft max" heap size and uncommit. Together it might be close to what you're looking for https://malloc.se/blog/zgc-softmaxheapsize
I should mention the GC situation was worse until the last few years. Until ZGC and Shenandoah came around, Java still didn't collect frequently but when it did there were long pauses. This is what V8 and Go's collectors were designed to avoid. They have more overhead from collecting frequently, but low pauses. With the new Java collectors you get the best of both worlds.
You can set heap ratios and such for older collectors to decrease Java memory use with those, but IMO you're better off using ZGC and uncommit these days