Remix.run Logo
stmw 3 days ago

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.

pron 3 days ago | parent | next [-]

> So you can waste more RAM, waste more cycles

Just to be clear, the main reason for the use of moving collectors in the first place is to waste less cycles on memory management (otherwise we wouldn't use them). They exist to serve as an optimisation.

> We have been 3 years away from GC solving memory management for at least 30 years.

It's now 3 years in the past (since Generational ZGC); e.g. see https://netflixtechblog.com/bending-pause-times-to-your-will.... Of course, it doesn't solve all imaginable memory management issues, but in practice it makes it a non-issue for a large class of interesting and very common programs.

stmw 2 days ago | parent [-]

Maybe I should've mentioned at the start that I've implemented several GCs and worked on several Java VM implementations, so I am generally familiar with the tradeoffs between GC algorithms and other runtime details.

Even in the very positive blog you linked, you see statements like * "ZGC has a fixed overhead 3% of the heap size, requiring more native memory than G1. .." and * "Reference processing is also only performed in major collections with ZGC. We paid particular attention to deallocation of direct byte buffers, but we haven’t seen any impact thus far. This difference in reference processing did cause a performance problem with JSON thread dump support, but that’s a unusual situation caused by a framework accidentally creating an unused ExecutorService instance for every request."

This was my point about how this sort of thing is a type of manual memory management.

As for waste more RAM, waste more cycles wasn't a statemnt about whether a particular GC is better-performing for certain situations, but that the overall improvement likely has more to do with improvements in CPU speeds and RAM size, than the latest GC version (which tends to simply make a different set of engineering tradeoffs).

pron 2 days ago | parent [-]

You're right that managing any resource that isn't just Java heap memory requires manual management, but it isn't what we normally mean by memory management. The two have been somewhat tied together traditionally through reference processing (in the sense of reference queues), which is generally something we now discourage in Java programs, and may be deprecated and removed altogether at some point. It's traditionally been used as a convenience. The framework used in that post is, indeed, based on an old library that relies on manual or reference-processing-assisted management of non-heap resources.

> but that the overall improvement likely has more to do with improvements in CPU speeds and RAM size, than the latest GC version (which tends to simply make a different set of engineering tradeoffs).

Well, the biggest improvement has been the creation of a new "pauseless" collector, ZGC, with a novel GC algorithm (at least for OpenJDK), which does _zero_ GC work in STW pauses, i.e. no scanning, no marking, no moving. In particular, even roots, including stacks, are processed entirely concurrently with the program. The main practical impact of that has been saying goodbye to GC pauses, and getting low latency, that is perhaps even more predictable than malloc/free (and obviously, still has higher throughputs in a large class of interesting programs). The tradeoff is the usual footprint tradeoff, which is the core of moving algorithms, as well as more CPU cycles compared to STW collectors (but again, still less than malloc/free in many programs). The additional CPU can, of course, be compensated for with an even larger heap.

The general idea is to use RAM chips as hardware program accelerators, but in the past latency was also something you had to sacrifice, and this is no longer the case today.

hedora 3 days ago | parent | prev [-]

It definitely was easier for the projects I worked on, but they are exactly the use case where the heap is long lived and most of the machine.

I’ve also worked on systems with lots of small processes, and the operational issues that creates dwarfs GC problems: It takes one middle tier machine, and adds 64-128 network boundaries, and also creates an extremely difficult static memory allocation problem.

I know people do it anyway, but it’s rare that they can articulate a decent technical reason for it, and it wastes something like 90% of the hardware (even in carefully optimized code bases / deployments).

Anyway, I’m not the target market for such stuff.