Live data from Hacker News

JDK 27 G1/Parallel/Serial GC Changes

tschatzl.github.io

11–20 of 36 posts

Re: JDK 27 G1/Parallel/Serial GC Changes

#11
post #3

Are people still dealing with GC issues? I find that it basically just more or less works out of the box on modern JVMs.

> Are people still dealing with GC issues?

Have you tried real-time audio processing for digital radio communications on a JVM that requires sub-millisecond latency on older, temperature-hardened CPUs?

Re: JDK 27 G1/Parallel/Serial GC Changes

#13
post #3

Are people still dealing with GC issues? I find that it basically just more or less works out of the box on modern JVMs.

> Are people still dealing with GC issues? Have you tried real-time audio processing for digital radio communications on a JVM that requires sub-millisecond latency on older, temperature-hardened CPUs?

Use ZGC.

Re: JDK 27 G1/Parallel/Serial GC Changes

#14
post #6

GC's haven't freed us from manual memory management, you just do all that manual work with environment variables, or making sure to "pick the right collector for the job", or debugging performance or heap size issues, or chasing down weak references or confused finalizers.

> you just do all that manual work with environment variables

You really don't anymore. For the past several years, Java's GCs mostly pick the right settings automatically, except for heap size, which will be taken care of soon (https://openjdk.org/jeps/8377305). The reason heap size isn't automatic is that with moving collectors it determines the CPU/RAM tradeoff, and doing that in a more natural way isn't trivial, but we have the algorithm now and will merge it soon.

> or making sure to "pick the right collector for the job"

There are really only four options, most of which are easy to choose among: Parallel for batch jobs where only throughput matters, ZGC for interactive applications where latency matters a lot, and then consider either G1 or Serial if there's a problem with those choices.

As someone who's worked for a long, long time solving manual memory management issues, the amount of effort required isn't just in a different ballpark, but in a different city. Sure, spending a few hours a year to reconsider your settings isn't nothing, but it isn't even remotely in the same category of pain with manual memory management (or even automatic memory management, but with malloc/free underneath).

Re: JDK 27 G1/Parallel/Serial GC Changes

#15
post #3

Are people still dealing with GC issues? I find that it basically just more or less works out of the box on modern JVMs.

It's selection bias. There's a huge number of GC language users, but those who experience problems tend to be the ones who comment. You're right; the vast majority of people use GC just fine and go about their day. We should update little to none when we see evidence of GC hardship.

I sometimes run into browser JavaScript GC issues for browser games specifically but on the JVM side i have not run into any pain points for a long time.

(I run a first-person shooter Minecraft server, and for this and other fast-paced gaming in general pause times under a millisecond or so is generally good.)

Re: JDK 27 G1/Parallel/Serial GC Changes

#16
post #13

Earlier quoted context omitted.

> Are people still dealing with GC issues? Have you tried real-time audio processing for digital radio communications on a JVM that requires sub-millisecond latency on older, temperature-hardened CPUs?

Use ZGC.

Does it provide hard latency bounds like Azul does (did?), and are they lower than disk/network latencies on modern hardware?

I moved to c++/rust years ago because those languages do, and tens of milliseconds matter for network services. At the time Java could pause for 10’s of seconds, which was 1000x worse than waiting for a spinning disk to seek.

These days, disks are 100s micros to single digit millis, so I guess if Java GC is finally working 30 years after they “fixed” its performance problems, then I’d want to be able to tune ZGC to not pause the app for more than ~ 500us, max.

This article is from last year, but suggests they’re still off by an order of magnitude:

https://www.morling.dev/blog/lower-java-tail-latencies-with-...

Also, that’s measuring a 30 second window.

If you hammer a 100GB-1TB heap in steady state with small allocations for, say, a month at 100% CPU, does it eventually do the typical Java thing, where a major compaction takes the process down for seconds or even minutes, or does it just slow down application requests so it can keep up with load?

Re: JDK 27 G1/Parallel/Serial GC Changes

#17
post #14
post #6

GC's haven't freed us from manual memory management, you just do all that manual work with environment variables, or making sure to "pick the right collector for the job", or debugging performance or heap size issues, or chasing down weak references or confused finalizers.

> you just do all that manual work with environment variables You really don't anymore. For the past several years, Java's GCs mostly pick the right settings automatically, except for heap size, which will be taken care of soon ( https://openjdk.org/jeps/8377305 ). The reason heap size isn't automatic is that with moving collectors it determines the CPU/RAM tradeoff, and doing that in a more natural way isn't trivial…

Concretely, what are current tail latencies, worst case?

Ten years ago, “rewrite in C++” was definitely easier than getting the Java GC to stay up under server load.

Most servers I work with run on big machines and are the only process, so figure a 100-250GB heap that lives for months, all async, small requests, so insane amounts of Future and String allocation spam.

Optimizing that stuff away in Java is harder than writing Rust, so assume idiomatic Java.

Also, is there any work on statically enforcing data race freedom in Java? That’s a bigger rust selling point than memory safety for me. I think swift has done some interesting work in that space. It would be nice to get those sorts of safety properties without manually writing borrow checker annotations.

Re: JDK 27 G1/Parallel/Serial GC Changes

#18
post #17
post #14

Earlier quoted context omitted.

> you just do all that manual work with environment variables You really don't anymore. For the past several years, Java's GCs mostly pick the right settings automatically, except for heap size, which will be taken care of soon ( https://openjdk.org/jeps/8377305 ). The reason heap size isn't automatic is that with moving collectors it determines the CPU/RAM tradeoff, and doing that in a more natural way isn't trivial…

Concretely, what are current tail latencies, worst case? Ten years ago, “rewrite in C++” was definitely easier than getting the Java GC to stay up under server load. Most servers I work with run on big machines and are the only process, so figure a 100-250GB heap that lives for months, all async, small requests, so insane amounts of Future and String allocation spam. Optimizing that stuff away in Java is harder than…

> Concretely, what are current tail latencies, worst case?

Well under 1ms for ZGC (to the point that OS-caused hiccups are of similar magnitudes).

> Ten years ago, “rewrite in C++” was definitely easier than getting the Java GC to stay up under server load.

Both could have been hard in some cases, but open-source "pauseless" GCs are only 3 years old (and all of the JDK's GCs are nothing like what they were ten years ago).

> Optimizing that stuff away in Java is harder than writing Rust, so assume idiomatic Java.

Quite the opposite. Performance issues due to memory management are, in practice, more serious in Rust than they are in modern Java.

> Also, is there any work on statically enforcing data race freedom in Java?

There isn't much demand for that atm. If we see growing demand, we could prioritise it.

Re: JDK 27 G1/Parallel/Serial GC Changes

#19
post #16
post #13

Earlier quoted context omitted.

Use ZGC.

Does it provide hard latency bounds like Azul does (did?), and are they lower than disk/network latencies on modern hardware? I moved to c++/rust years ago because those languages do, and tens of milliseconds matter for network services. At the time Java could pause for 10’s of seconds, which was 1000x worse than waiting for a spinning disk to seek. These days, disks are 100s micros to single digit millis, so I guess…

> Does it provide hard latency bounds like Azul does (did?), and are they lower than disk/network latencies on modern hardware?

Yes and yes (although we need to be more precise when we talk about latencies; see next paragraph).

> These days, disks are 100s micros to single digit millis, so I guess if Java GC is finally working 30 years after they “fixed” its performance problems, then I’d want to be able to tune ZGC to not pause the app for more than ~ 500us, max.

1. You don't need to tune it. The algorithm simply doesn't collect garbage in stop-the-world pauses.

2. Hiccups are sporadic. They should not be compared to the average latency of normal operation. The relevant question is, is ZGC introducing longer hiccups than those a non-realtime kernel would, and the answer is no.

> This article is from last year, but suggests they’re still off by an order of magnitude

The article doesn't measure GC pauses when it shows latencies (it says: "With ZGC on the other hand, the longest GC pause time observed is ~50 microseconds"). It measures the response latencies of some service. Note that allocation stalls also occur with malloc, it just isn't reported conveniently.

Of course, one of the greatest advantages of moving collectors still applies: Under high allocation rates, moving collectors (but not malloc/free!) allow you to compensate for increased CPU spent on memory management by increasing the heap (i.e. if your allocation rate doubles, you can increase the heap and keep the CPU cost of memory management the same). In the past, this advantage translated to higher throughputs compared to malloc/free, but suffered from GC pauses. Those pauses are gone today.

> If you hammer a 100GB-1TB heap in steady state with small allocations for, say, a month at 100% CPU, does it eventually do the typical Java thing, where a major compaction takes the process down for seconds or even minutes, or does it just slow down application requests so it can keep up with load?

No, it does not. You could, of course, construct some pathological cases where you'd have a high allocation rate for long-lived objects which would result in high CPU utilisation by the GC, but it's easier to get into pathological malloc/free cases in C++ (or Rust) than with ZGC. Let me put it another way: no matter your memory management strategy, it's possible to overwhelm it, but the likelihood that a real, "naive" program would overwhelm a malloc/free allocator is higher than it would the JDK's GCs.

Re: JDK 27 G1/Parallel/Serial GC Changes

#20
post #17
post #14

Earlier quoted context omitted.

> you just do all that manual work with environment variables You really don't anymore. For the past several years, Java's GCs mostly pick the right settings automatically, except for heap size, which will be taken care of soon ( https://openjdk.org/jeps/8377305 ). The reason heap size isn't automatic is that with moving collectors it determines the CPU/RAM tradeoff, and doing that in a more natural way isn't trivial…

Concretely, what are current tail latencies, worst case? Ten years ago, “rewrite in C++” was definitely easier than getting the Java GC to stay up under server load. Most servers I work with run on big machines and are the only process, so figure a 100-250GB heap that lives for months, all async, small requests, so insane amounts of Future and String allocation spam. Optimizing that stuff away in Java is harder than…

As the OC, I think my view is somewhere in the middle - I am neither as optimistic about it being "great now" nor do I think that "rewrite in C++" 10 years ago was easier.

My reason for disagreeing with the former view is that improvements in physical RAM available and tendency towards smaller workloads have allowed many Java (or other GC runtimes) to essentially "fix their problems because hardware got better". So you can waste more RAM, waste more cycles, but "it doesn't matter", and likely it is fine in many cases - but it's is not the same thing as claiming the GC algorithms are responsivle for that outcome. We have been 3 years away from GC solving memory management for at least 30 years.

My reason for disagreeing with the latter view is that for those who don't have 100-250 GB long-lived heaps (or whatever the contemporary version of that is), the pain level is far lower than rewriting in C++ or Rust, or likely the pain level of hiring enough engineers who can do either. It's a completely different engineering culture.

Post reply on HN