"Optimizing the GC when Migrating Cloud Workloads to Arm" with Kieran Hejmadi – JVM Weekly vol. 128
Today, we have a guest post from Kieran Hejmadi, an Software and Academic Ecosystem Manager in Arm.
Last May, I announced that JVM Weekly had joined the Foojay.io family. Foojay.io is a dynamic, community-driven platform for OpenJDK users, primarily Java and Kotlin enthusiasts. As a hub for the “Friends of OpenJDK,” Foojay.io gathers a rich collection of articles written by industry experts and active community members, offering valuable insights into the latest trends, tools, and practices within the OpenJDK ecosystem.
So like last month, I have something special - a repost of a great JVM-related article, originally posted on Foojay.io. Today, we have a guest post about Arm, from Kieran Hejmadi, a Software and Academic Ecosystem Manager in Arm itself - so knowledge from the source itself. The text is a bit short, but it is a useful introduction to Arm. And if that won’t be enough, I have one announcement at the end.
Optimizing the GC when Migrating Cloud Workloads to Arm
Introduction to Java on Arm
You might associate Arm primarily with smartphones and the Java-based Android runtime. However, OpenJDK has supported AArch64 on Linux since 2014— before Arm-based cloud instances were widely available. Fast forward a decade and major cloud providers have their own Arm-based instances like AWS Graviton, Microsoft Azure Cobalt, and others, prompting many organizations to migrate Java workloads from x86 to multi-architecture environments.
A multi-architecture deployment shift allows an organization to be adaptable and choose the architecture with the ideal price to performance ratio. Large independent software vendors, such as Uber, are already making this transition.
Thanks to Java's "write once, run anywhere" promise, your applications generally run on Arm without changes. So, blog over? Not quite. Migrating Java from x86 to AArch64 is like a pilot switching from flying one aircraft model to another. The cockpit controls (Java) remain familiar, allowing smooth basic operation, but instruments and settings might respond differently.
To achieve optimal flight performance, the pilot needs to adjust or recalibrate certain controls, ensuring everything goes smoothly. Without tuning, you'll still fly, but you won't fully benefit from the improved handling and efficiency of the upgraded aircraft.
Why GC Tuning Matters on Arm
One of the most important controls in your metaphorical performance cockpit is Garbage Collection (GC). The Arm Neoverse architecture—the backbone of many Arm-based cloud instances—is known for its scalable core counts and can leverage those cores to deliver strong GC performance, though it often requires some fine-tuning to reach peak efficiency.
Let’s explore the key settings you should adjust to unlock its full potential.
OpenJDK Version
Always use a modern OpenJDK (Java 11 or newer). Newer JDKs include Arm-specific optimizations for the latest Arm platforms, such as AWS Graviton 4.
Further, the latest OpenJDK uses has access to more advanced garbage collectors, notably Garbage-First (G1) GC, which significantly improves pause times and throughput compared to older Java versions.
Choosing the Right Garbage Collector
Each GC has its own set of trade-offs but understanding the differences is not obvious.
The table below can be used as a rough guide if you are considering building a Java application from scratch to run on Arm.
For most server workloads, a safe starting point is the G1 GC
. It balances throughput and latency well, collecting the heap incrementally to avoid long pauses. For latency-critical applications with large heaps, consider ZGC
or Shenandoah
(Java 17+), offering ultra-low pause times.
Heap Size and GC Pause Time
Heap size has a major impact on GC performance. If it’s too small, collections happen too frequently; if it’s too large, pause times can become excessive. How much of each your application can tolerate depends entirely on your specific use case.
Thankfully, the Java community anticipated this trade-off and provided tuning options like -XX:MaxGCPauseMillis
, allowing the JVM to handle the optimization—much like a pilot setting a cruise altitude and letting the autopilot take over.
Adaptive Heap Sizing
Choosing an optimum heap size is notoriously difficult, especially for applications workloads that fluctuate and require predicable performance, for example an e-commerce application.
A JVM parameter worth considering is an adaptive heap sizing strategy, -XX:+UseAdaptiveSizePolicy
which varies the generation and heap sizes dynamically during execution. This parameter can be useful if the performance in a staging environment doesn’t translate to real-world workloads in production.
Closing the feedback loop
Your Java application's garbage collection behavior may not always match your expectations, so a trial-and-error approach is often the most practical way to hit your performance targets.
To guide your tuning, make use of diagnostic tools: enable GC logging with -Xlog:gc
or use Java Flight Recorder (JFR) to monitor GC activity and make informed adjustments.
Developer Education for the Java Community
Of course, theory only goes so far—real-world experience is where the learning truly happens. That’s why I’ve put together a simple tutorial to help developers experiment with various GC tuning options using a basic Java example. You can follow along here for a hands-on walkthrough.
Just like a pilot’s cockpit, the Java runtime offers a wide array of controls, and the garbage collector is only one of them. We're always interested in learning which areas of performance tuning developers find most challenging. If you have ideas for future Java tutorials, feel free to share them with us here.
Need Assistance from Experts?
All these dials and knobs to tune can be a steep learning curve if you manage a large Java code base and have never had to fine-tune performance for a specific Architecture.
Recognising this, in April 2025 Arm launched a program to connect you to an Arm cloud migration expert who can assist in workload assessment and performance tuning.
Follow this link for more information.
PS1: On a side note – if you're interested in Java optimization for the Arm architecture, it's worth keeping an eye on the team of Arm engineers dedicated to exactly that. The "Arm Cloud Migration" program, led by Jason Andrews, is a great place to start. Jason was also involved in writing the article above – definitely someone to have on your radar! 👀
PS2: I will return to Arm topic by the end of this edition, but before that, let's review some of the other cool things that appeared on Foojay.io last month.
Foojay Podcast #70: Celebrating 5 Years of Foojay
We start classically with a podcast... and there's a reason for it!
In this anniversary episode, the creators of Foojay celebrate the five-year existence of the platform, which has gathered over 1600 articles from 250 authors. Conversations with founder Geertjan Wielenga and other community members highlight Foojay's evolution, its impact on the Java ecosystem, and plans for the future, including API development and integration with developer tools. Stick around until the end, and you'll also see my face, as I had the chance to add my two cents as well. ;)
Async File I/O with Java and io_uring
The article Async File I/O with Java and io_uring by David Vlijmincx explains how combining Java's virtual threads with the io_uring library and the Panama project enables efficient, asynchronous handling of I/O operations. This solution helps avoid the issue of "pinning" threads during I/O operations, which is especially beneficial in applications with high disk or network I/O activity.
Six JDK 24 Features You Should Know About
I hope you're not tired of JDK 24 yet, because I have more JDK 24 for you!
In the article Six JDK 24 Features You Should Know About, Simon Ritter discusses six key new features in JDK 24, including: JEP 483 (premature class loading and linking), JEP 485 (new intermediate operations in streams), JEP 486 (permanent removal of the security manager), JEP 491 (thread synchronization without pinning), JEP 498 (warnings on the use of unsafe memory methods), and JEP 501 (deprecation of the 32-bit x86 port).
From Reactive Streams to Virtual Threads
In the article From Reactive Streams to Virtual Threads, Adam Warski analyzes the transition from reactive streams to virtual threads in Java, exploring whether the new approach could replace existing asynchronous programming models. Virtual threads offer a simpler programming model that is closer to synchronous programming, but their application in the context of data streams still requires some practice in the industry. A very interesting food for thought.
Clean your Memory: From Finalize to Cleaner
The article Clean your Memory: From Finalize to Cleaner by Stefano Fago presents the evolution of resource management in Java, from the deprecated finalize()
method to the modern Cleaner API, which allows for more predictable and efficient cleaning of non-memory resources such as file handles and network connections. It also eliminates many issues associated with finalize()
, such as unpredictability and potential memory leaks.
If you haven't heard of these mechanisms, I recommend taking a look.
Foojay Webinar Live Stream: Java’s Place in the AI Revolution
We started with a podcast, and we finish with a webinar, where Pratik Patel talks with Zoran Sevarac, CEO of Deep Netts, and Frank Greco, Machine Learning consultant, about the role of Java in the AI revolution. They present tools and libraries that enable the creation of AI/ML applications in pure Java. The webinar provides a solid introduction to the topic, walking the listener through the taxonomy of concepts (for example, you'll learn what "generative AI" really means, and no, it doesn't refer to generating images), while also showing the practical side of available tools – not only DeepNetts.
PS3: I promised I would get back to the Arm topic, so here we go!
Soon, there will be an opportunity to meet in person in London at Devoxx UK, and I will have a talk titled Running Java on Arm – Is It Worth It?.
If you're interested in the topic of running Java on Arm processors (or you were inspired by this text), this is definitely a discussion you won't want to miss! I've done a lot (hopefully) of interesting research and gathered quite a few benchmarks, so I’m happy to share them.
See you in London! If you're attending the conference, it’ll be a great chance to give a high five 🙌.
PS4: If you still want to buy a ticket, you can use the code SEEMESPEAK25 to get 25% off.
PS5: But that’s not all – on my way to London, I’ll be stopping by Malmö for a talk at the Java Forum Malmö (local JUG) organized by Ivar Grimstad, where I'll be discussing JVM in the Age of AI! You can check out more about the event here.
See you in both Malmö and London.
PS6: We’ll also be able to meet at Geecon in Kraków and KotlinConf in Copenhagen later this month. I’ll remind you before the respective editions, be sure of that
PS7: That’s why you can expect that this month’s JVM Weekly will be quite conference-heavy, as I’ll want to show what these events look like, and additionally, I’ll be trying to grab a few photos and a guest report from Spring I/O.

PS8: This might just be a record number of postscripts in a single edition 😅