Tune Code Before Your Garbage Collector

Hacker News Top News

Summary

Benchmarking shows that optimizing Java code (e.g., reducing SLF4J logging) has a far greater impact on latency than choosing a garbage collector, especially at high percentiles.

No content available
Original Article
View Cached Full Text

Cached at: 07/13/26, 07:55 PM

# Why You Should Tune Code Before Your Garbage Collector Source: [http://blog.vanillajava.blog/2026/06/why-you-should-tun-code-before-your.html](http://blog.vanillajava.blog/2026/06/why-you-should-tun-code-before-your.html) ### Why You Should Tune Code Before Your Garbage Collector Optimising your memory allocations in Java could make far more difference than your choice of Garbage Collector and may even change which is the best garbage collector\. 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 For the p99 \(worst 1 in 100\) the choice of Garbage Collector makes a different on par with optimising how loggin is done [![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhz2R-QJ2T4etyhbtJhchUdCqFPAibBnBnJDlFyRR-wgyzN_9Zu_AnqS6QY60xVhuaFdorkvT9smrXJ0z6p2l3FDXG6zmE-gmUCxRD0HOaRKEZQ3kgJsovRsVDwG8LIvqimrUSCCFhEdJMluuRcA28VHEl1ZLhKuHLeVcp4zzIcyqTXbzsHZXX_64lZbxbd/s1600/Screenshot%20from%202026-06-08%2016-26-30.png)](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhz2R-QJ2T4etyhbtJhchUdCqFPAibBnBnJDlFyRR-wgyzN_9Zu_AnqS6QY60xVhuaFdorkvT9smrXJ0z6p2l3FDXG6zmE-gmUCxRD0HOaRKEZQ3kgJsovRsVDwG8LIvqimrUSCCFhEdJMluuRcA28VHEl1ZLhKuHLeVcp4zzIcyqTXbzsHZXX_64lZbxbd/s1600/Screenshot%20from%202026-06-08%2016-26-30.png) However, for the p99\.99 \(worst 1 in 10,000\) optimsing how the logging is done is orders of magnitude more signifciant than the choice of Garbage Collector [![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSuq8kQhCQq9B6npcB29I11UIgJoR93QMOEE8LJp85vyVUZreUlTSoXdChyphenhyphenGSuGDT0tu24cVeiph8PV5DdFjmFt_QR-z5IvfSc92XL9Q7Q2xYvUqw-X9iFyz41k8t_MMqvh_Gb0D9vlDwkHvBXBU_KZJ9IrLsw1XQzvyzadIPPO184qKCksFOmOH68aOmv/s1600/Screenshot%20from%202026-06-08%2016-26-03.png)](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSuq8kQhCQq9B6npcB29I11UIgJoR93QMOEE8LJp85vyVUZreUlTSoXdChyphenhyphenGSuGDT0tu24cVeiph8PV5DdFjmFt_QR-z5IvfSc92XL9Q7Q2xYvUqw-X9iFyz41k8t_MMqvh_Gb0D9vlDwkHvBXBU_KZJ9IrLsw1XQzvyzadIPPO184qKCksFOmOH68aOmv/s1600/Screenshot%20from%202026-06-08%2016-26-03.png) ## Unoptimised Benchmark This takes the optimised benchmark and adds one SLF4J log line of just the message to be sent\. One log line might not sound like much but ding this on every message makes a big difference esp when the rest of the code is written for low latency\. Table 1\. RTT latency distribution with slf4j loggingGC option p99 p99\.99 Parallel large Eden, no large pages **16\.86** 20,480 ZGC, no large pages 12\.21 19,694 G1 \+ COH, no large pages 12\.02 20,349 G1, 2 MiB large pages 13\.94 20,021 Shenandoah generational, no large pages 12\.30 19,235 Note The p99\.99 are thousands of microseconds or 19 to 20 milliseconds\. The p99\.99 or worst 1 in 10,000 might sound rare, however at 50K/s that is 5 times per second or 300 times per minute Based on these results, you might conclude that Shenandoah is a good option, and avoid Parallel GC\. ## How much difference is the disk IO making IO is often a significant proportion of delays, and we can see that just moving where the logs are written\. In this case to a tmpfs filesystem\. Table 2\. RTT latency distribution with slf4j logging to /dev/shmGC option p99 p99\.99 Parallel large Eden, no large pages **34\.50** 10,994 ZGC, no large pages 10\.86 9,617 G1 \+ COH, no large pages 10\.90 10,043 G1, 2 MiB large pages 10\.64 11,682 Shenandoah generational, no large pages 10\.32 9,552 While the p99 for parallel GC is possibly an outlier, you still might favour Shenandoah, however just moving where the log is being written makes far more difference\. Note The p99\.99 are thousands of microseconds or 9 to 10 milliseconds\. ## Remove the redundant logging Chronicle\-FIX already records every message using Chronicle\-FIX so the slf4j log is redundant\. For low latency coding we use Chronicle\-Queue for all recording and almost no logging as it can be the biggest source of delays otherwise\. As a policy, we don’t have info level logging after startup\. ie\. either its an error/warning and on by default, or a debug logging and off by default\. ie\. make a decision as to whether it’s really needed or not\. Table 3\. RTT latency distribution with only Chronicle\-Queue recordingGC option p99 p99\.99 Parallel large Eden, no large pages **7\.14** **8\.94** ZGC, no large pages 7\.82 9\.36 G1 \+ COH, no large pages 8\.30 12\.4 G1, 2 MiB large pages 8\.59 154\.4 Shenandoah generational, no large pages 7\.83 233\.7 As you would expect, the p99 has dropped about 2 microseconds, however what is significant is that the p99\.99 has dropped by three orders of magnitude\. Also, the preferred garbage collector has flipped due to the change in work load, all for the sake of one log line\. In this case, parallel GC looks best, and Shenandoah came out worst on the p99\.99\. Note The p99\.99 are microseconds or more than a 1000x faster than logging to /dev/shm ## Conclusion The choice of the best Garbage Collector and how to tune it depends on your workload\. It makes sense to ensure your workload has been optimised first because a\) it can make more difference in some cases, b\) it can change your preference as well\. ### Popular posts from this blog ### [Asking multiple AI to optimise the same code](http://blog.vanillajava.blog/2025/07/asking-multiple-ai-to-optimise-same-code.html) As different AIs are implemented differently, they don't all provide the same answer, nor do they consistently outperform one another\. The best approach is to use multiple AI and pick the one you like best\. My goal here is not to declare a winner based on one example, but instead to show the variety of answers you can get with different AI\. I asked each AI to Suggest how to implement this more optimally private static String formatOffset\(int millis\) \{ String sign = millis < 0 ? "\-" : "\+"; int saveSecs = Math\.abs\(millis\) / 1000; int hours = saveSecs / 3600; int mins = \(\(saveSecs / 60\) % 60\); int secs = \(saveSecs % 60\); if \(secs == 0\) \{ if \(mins == 0\) \{ return sign \+ twoDigitString\(hours\); \} return sign \+ twoDigitString\(hours\) \+ twoDigitString\(mins\); \} return sign \+ twoDigitString\(hours\) \+ twoDigitString\(mins\) \+ twoDigitString\(secs\); \} private static String twoDigitString\(int value\) \{ \.\.\. ### [Demystifying Java Object Sizes: Compact Headers, Compressed Oops, and Beyond](http://blog.vanillajava.blog/2024/12/demystifying-java-object-sizes-compact.html) Introduction Measuring an object’s size in Java is not straightforward\. The platform encourages you to consider references and abstractions rather than raw memory usage\. Still, understanding how objects fit into memory can yield significant benefits, especially for high\-performance, low\-latency systems\. Over time, the JVM has introduced optimisations like Compressed Ordinary Object Pointers \(Compressed Oops\) and, more recently, Compact Object Headers\. Each of these can influence how large or small your objects appear\. Understanding these factors helps you reason about memory usage more concretely\. Measuring Object Sizes In principle, you can estimate an object’s size by creating instances and observing changes in the JVM’s free memory\. However, you must neutralise certain factors to get consistent results\. For example, turning off TLAB allocation \( \-XX:\-UseTLAB \) makes memory usage more directly observable\. Repeated measurements and median calculations can reduce the im\.\.\. ### [Updated Biography](http://blog.vanillajava.blog/2025/08/updated-biography.html) Peter Lawrey is an Australian/British software engineer and entrepreneur best known for work on ultra\-low\-latency Java systems and for leading the open\-source OpenHFT libraries\. He is the founder and chief executive of Chronicle Software, a London\-based company whose technology is used in trading and market\-infrastructure workloads\. Lawrey is also a recognised Java community figure: he was named a Java Champion in 2015, has been described by conference organisers as having provided the most answers for the Java and JVM tags on Stack Overflow, and writes the long\-running Vanilla Java blog\. \( Chronicle Software , javachampions\.org , qconnewyork\.com , blog\.vanillajava\.blog \) Career Lawrey founded and leads Chronicle Software, which builds enabling technology for event\-driven trading and market\-data platforms\. The company states that its software underpins systems at several tier\-one banks; a 2024 press announcement similarly described Chronicle as supplying "8 of the t\.\.\.

Similar Articles

Watching Go's new garbage collector move through the heap

Lobsters Hottest

Go 1.26 makes Green Tea the default garbage collector, improving cache-friendliness. This article visualizes heap allocation with Go and C#, and discusses challenges with non-moving collectors and sparse pages.

Are Performance-Optimization Benchmarks Reliably Measuring Coding Agents?

Hugging Face Daily Papers

This paper audits three performance-optimization benchmarks (GSO, SWE-Perf, SWE-efficiency) for coding agents, finding that runtime instability, scoring rules, and task coverage significantly affect reliability, and that many tasks are already solved by at least one public submission.

Meta Garbage Collection: Using OCaml's GC to GC Rust

Lobsters Hottest

Soteria Rust, a symbolic execution tool for verifying Rust programs, uses OCaml's garbage collector to manage memory for its Tree Borrows aliasing model, achieving a 10x speedup and reducing time complexity from quadratic to linear.

Every byte matters

Lobsters Hottest

This article explains the importance of understanding CPU cache lines and data structure layout for performance optimization in programming, using examples in Java and C. It discusses the overhead of unnecessary bytes and the trade-offs between Array of Structs and Struct of Arrays.