Is it just me, or does all garbage collector development seem to go through an endless loop of:
* We've optimised this release for throughput
* We've optimised this release for latency
* We've optimised this release for minimising memory usage
...and each release causes a regression in the stuff they weren't optimising for, leading to another future release picking one of the other three targets.
Java and Go seem to have gone round this cycle more than once...
One important difference between Java and Go is that Java prides itself in having at least half dozen GC implementations and endless array of tuning parameters. So each factor you listed can be tuned at any time and not part of particular released version.
Whereas for Go memory usage is more or less same in almost every release. They have singular focus on latency improvement in general or very specific type of workloads with each new release.
My suspicion is that these correspond with changes in how these languages are used. I believe (but can’t prove) that Java has been shifting more from a batch processing language to a web services language. In the former throughput is king, in the latter latency is more important.
Also, the cost of memory keeps dropping, especially with auto scaling cloud services, which makes optimizing for memory footprint an increasingly bad idea.
I don't really see that cycle. I think Hotspot's latency and memory usage has always been atrocious, and they have slowly been working to improve that with ParallelGC->CMS->G1.
I don't think there is a black/white world like Hotspot people tend to claim about these things. You can have your cake and eat it - acceptable latency <10ms/1ms AND good throughput/memory usage is possible without excessive manual tuning. golang is an example of this, as are GCs in other language runtimes. In every other managed language I've worked in, I never even think about the GC 99% of the time. Whereas with Hotspot you have to tune and think about it 100% of the time. Hopefully it will get there one day and make working on JVM projects less painful.
Go's GC's throughput is significantly lower than that of all OpenJDK's GCs, and its latency numbers are misleading because they don't account for throttling. To give you an example, a GC that works as follows -- allocate by pointer bumping from a thread-local buffer, and when it runs out, freeze the thread forever -- would count as having zero latency according to Go's metrics, because this infinite pause is per-thread rather than stop-the-world; of course, the throughput will eventually drop to zero, too, but they don't report throughput. Why, then, do you see Go applications performing more-or-less OK? Because of two things: they allocate fewer objects, and they just run significantly more slowly than Java, except this slowdown is paced. In terms of algorithm, Go's GC is pretty-much a simplified version (no young generation) of OpenJDK's now-defunct CMS plus throttling. G1 is a generation beyond that, and ZGC is two.
Generational garbage collection doesn't help much with a language with value types, stack allocation, and decent escape analysis.
“It isn't that the generational hypothesis isn't true for Go, it's just that the young objects live and die young on the stack. The result is that generational collection is much less effective than you might find in other managed runtime languages” https://twitter.com/davecheney/status/1019430967054819328
Go's GC improvements are revealed in P99.9 latencies of servers over time, not just in the raw numbers of how long it's stopping the world.
That doesn't change the fact that Go's GC is a simplified CMS. It is true that Go does have an easier life -- Java does do escape analysis and allocates on the stack, but it doesn't have value types just yet, and so the allocation rates are higher, which is why Java is not drastically faster. I.e. a cruder GC that's similar to OpenJDK's GC from two generations ago works OK for Go. Despite Go having an easier life, Java 14 performs noticeably better than Go, partly due to having a better compiler, but also because its GCs are just better.
> The effect is that out of the box a 1 MB region size will be used, while for the fixed heap case the region size will be 2 MB. It might sound like a small difference, but the benchmark uses a significant amount of large objects that need special treatment when using 1 MB regions. This special treatment leads to a lot of memory that can’t be used, which in turn leads full collections and a poor overall experience.
This means the same problem as before can be observed, you'll just need slightly bigger objects to get there.
The Java way is more about selecting a reasonable default, and then exposing a tuning parameter for those whose use case falls well off of what was designed for.
Great to see progress in this space. I remember trying out G1 for the first time years ago and almost immediately noping out of it as the performance was terrible.
Working in low-latency space the angle for me is much more about GC latency (in microsecond-level scales).
I've never seen anyone doing a thorough evaluation of GC times across different implementations.
Does anyone here have any interesting sources to share?
It will be declared non-experimental in 15, but people are running it in production on 14. GCs, and performance in general, tend to improve quite significantly between releases.
I haven't used it myself but I have heard others do so with success. It looks like JDK14 has some significant improvements to ZGC so I would try to run on that if possible.
* We've optimised this release for throughput
* We've optimised this release for latency
* We've optimised this release for minimising memory usage
...and each release causes a regression in the stuff they weren't optimising for, leading to another future release picking one of the other three targets.
Java and Go seem to have gone round this cycle more than once...