Tune Code Before Your Garbage Collector (blog.vanillajava.blog)
36 points by peter_lawrey 6 days ago
hinkley 5 hours ago
It's like caching, in kind but not in type. Once you add it, people will stop trying to be parsimonious with resources and just reach for the cache every time. They'll just lean into it. In a hot minute you will discover you can't turn it off because people have lost their brains and the data flow of the app is through the cache and not through the call tree.
If you tune for allocation patterns that are in the code, then you are cementing those as continuing in perpetuity. Better to cut the fat first, so that you can tune for the necessary complexity instead of the accidental. That will be self-correcting because any new misuses will be taxed with higher performance regressions.
senderista 5 hours ago
I worked on Java code at AWS for a few years and nobody tried to optimize allocations. Then I changed jobs and started working on a Java MPP database and my first code review was brutal. You were expected to avoid allocations as much as possible (mostly by using the SoA pattern everywhere). At that scale no GC could save you from excessive allocations.
motoboi 4 hours ago
I believe the whole string vs stringbuffer that later was made redundant by compiler contributed to that vision.
People started dismissing allocation discipline as a thing from the past because "that thing was solved a lot ago and the compiler now is smart enough".
Well, for string, yes, but not for arbitrary objects.
hinkley an hour ago
cogman10 4 hours ago
peter_lawrey 6 days ago
In this post I look at a simple event to response latency benchmark, MarketDataSnapshot to NewOrderSingle at 50K/s for 30 minutes using JLBH to test Chronicle-FIX. The goal is to compare a system which is doing redundant work (in this case logging each message using SLF4J), compared with not logging (Chronicle-FIX records every message internally using Chronicle Queue) and how this changes the choice of Garbage Collector
peterabbitcook 4 hours ago
Reading this gives me considerable pause - I can’t think of many classes within the codebase I work on that don’t have @Slf4j at the top…
Since there wasn’t a link to the source code in that post, can you help me understand this - for the SLF4J baseline is your logger impl a console appender, a file appender, or a network service like an OTel collector? Does any of that matter for GC context?
layer8 4 hours ago
All common logging backends create a LogEvent or similar object for each logging call, and logging calls also typically construct new strings, which usually means a new StringBuilder object, its internal array (multiple ones if it grows), the final array it is copied to, and the String object that wraps that array.
These are typically short-lived objects and therefore cheap. Nevertheless, continually creating many such objects increases GC pressure, in particular if the logging happens in code that doesn't otherwise create many objects.
cogman10 3 hours ago
Cheap, not free, and even pretty simple to accidentally fool the GC on the lifetime of these objects.
Consider, for example, if you have a log message like this
logger.info("Hello {}", myOldObject);
if "myOldObject" is large enough or contains references to large things or has just been around for a while, it may be a part of OldGen at this point. And if that's the case, the LogEvent objects will end up automatically promoted to OldGen. Meaning the only time those can be be claimed is in an expensive major collection. The end result is that these things will ultimately fill up old gen and trigger more of the expensive old gen collections.That's why it can be faster in some circumstances to write the more wordy
if (logger.isInfoEnabled()) {
logger.info("Hello {}", myOldObject.toString());
}
Nothing saves you, however, if your string being logged is too long. It can be autopromoted to old gen if you are trying to log a 10mb string.hyperpape 2 hours ago
well_ackshually 3 hours ago
> All common logging backends create a LogEvent or similar object for each logging call, and logging calls also typically construct new strings, which usually means a new StringBuilder object, its internal array (multiple ones if it grows), the final array it is copied to, and the String object that wraps that array.
Which then gets discarded because that was a Log.verbose and your minimum log level in production is WARN.
Which is why many libraries have moved towards making your log message returned by a lambda. One constant lambda allocation (so, not a lot, an invokedynamic is absolutely fuck all.) that allows you to straight up skip allocating a full string that most likely is interpolating things and attempting to reach for context present on other threads is strictly better in 99.9% of the cases. The GC pressure is kept minimal and most importantly, constant.
layer8 3 hours ago
syngrog66 35 minutes ago
I stopped reading early when it became clear the author didn't understand the terms (or perhaps the entire language) they were using. Several indicia.
einpoklum 4 hours ago
Garbage collector?
To quote Bjarne Stroustrup:
> I don't like garbage. I don't like littering. My ideal is to eliminate the need for a garbage collector by not producing any garbage. That is now possible.