When building and deploying microservices with Java — whether using Spring Boot or WildFly — one of the often-overlooked performance factors is the Garbage Collector (GC). Should you care about which GC your application uses? Or should you leave it to the infrastructure? Let’s break it down.
Why Garbage Collector Choice Matters
Garbage Collection is a core part of how the Java Virtual Machine (JVM) manages memory. The GC’s job is to clean up unused objects to free up memory, but how and when it does that can significantly impact your application’s performance.
In microservices, where responsiveness, stability, and resource efficiency are crucial, the GC can be the silent bottleneck if not configured properly. Here’s why:
1. Latency Spikes
Some GCs (like Parallel GC) may cause long application pauses. These pauses can increase response times, break SLAs, or even cause failures in health checks — especially in Kubernetes environments.
2. Throughput vs. Responsiveness
Different GCs optimize for different things. For example:
- Parallel GC prioritizes throughput (processing as much as possible).
- G1GC, ZGC, and Shenandoah focus on minimizing pause times — ideal for services handling frequent API requests.
3. Memory Efficiency
In containerized deployments, memory is usually limited. A GC that can handle memory fragmentation and pressure gracefully (like G1GC) is often a better fit.
4. Startup Time
In some microservices that scale quickly (e.g., under load), faster startup may matter. In those cases, GC behavior can also influence how fast a new instance becomes ready.
Popular GC Options (Java 21)
GC | Pause Time | Throughput | Notes |
---|---|---|---|
G1GC (default) | Low | High | Good general-purpose choice for microservices. |
ZGC | Very Low | Moderate | Designed for ultra-low pause times, works well with large heaps. |
Shenandoah | Very Low | Moderate | Similar to ZGC, especially effective in Red Hat-based environments. |
Parallel GC | High | Very High | Good for batch processing, not ideal for APIs. |
Serial GC | High | Low | Only suitable for tiny apps or short-lived processes. |
Spring Boot vs. WildFly: Who Chooses the GC?
Spring Boot
You have full control over the JVM. Whether you run it as a fat JAR or inside a Docker container, you decide the GC strategy. You can pass GC options via command line, JAVA_OPTS
, or Dockerfile.
WildFly
WildFly (Jakarta EE) runs inside a managed JVM. But even here, you can (and should) configure the GC by editing the standalone.conf
file or Docker entrypoint.
Cloud & Kubernetes Deployments
In containerized environments, infrastructure often sets memory limits, but it’s still your responsibility to:
- Configure the GC explicitly.
- Set memory-aware options like
-XX:MaxRAMPercentage
. - Monitor and tune based on GC logs (
-Xlog:gc*
in Java 21+).
Best Practices
- Stick with G1GC unless you have specific latency or heap size needs.
- Consider ZGC or Shenandoah for low-latency, large-memory services.
- Avoid Parallel GC in APIs unless it’s a batch job.
- Always monitor GC logs in production.
- Tune JVM settings for container memory limits.
Final Thoughts
In microservices, small inefficiencies can scale into big problems. GC choice might seem like a low-level detail, but it has a direct impact on the performance, stability, and scalability of your services.
So yes — whether you’re using Spring Boot, WildFly, or any other Java stack, take a few minutes to understand and configure your Garbage Collector. Your uptime (and your users) will thank you.