Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
103: The G1 and ZGC Collectors
For a long time, the general consensus among Java developers was that Garbage Collection was a "black box." You'd give the JVM a few gigabytes of RAM, hope for the best, and then panic when your application randomly froze for two seconds during a peak traffic spike. If you've ever looked at a latency graph and seen those dreaded "sawtooth" patterns followed by a massive vertical spike, you've met a Stop-the-World (STW) event.
Most of us start with the G1 (Garbage First) collector because it's the default in modern JDKs. It's a massive improvement over the old Parallel collector, but there's a common trap I see engineers fall into: trying to "tune" G1 into something it isn't. I once worked with a colleague who spent three days tweaking -XX:MaxGCPauseMillis, trying to force G1 to keep pauses under 10ms for a high-frequency trading bridge. He was treating G1 like a precision instrument, but G1 is more like a powerful sledgehammer. It's designed for balance, not near-zero latency.
The regional bet of G1
G1 doesn't treat the heap as one giant block. Instead, it carves the heap into many small regions. The "magic" of G1 is that it tracks how much "garbage" is in each region and collects the ones with the most trash first—hence the name. It's incredibly efficient for the majority of business applications because it avoids the need to compact the entire heap every time.
However, G1 still has to stop your application threads to move objects around and update references. If you have a massive heap—say, 64GB or 128GB—and a lot of long-lived objects, G1 eventually hits a wall. You'll see "Mixed Collections" where the pauses start creeping up. The naive approach here is to just add more RAM or lower the pause target. But if you tell G1 to keep pauses ultra-short, it will simply trigger collections more frequently, which tanks your overall throughput. You end up spending more time cleaning the house than actually living in it.
When "Stop-the-World" becomes a business risk
This is where ZGC (the Z Garbage Collector) comes in. If you're building something where a 100ms pause is an eternity—like a real-time bidding system or a low-latency cache—G1 is the wrong tool. ZGC is a different beast entirely. While G1 stops the world to move objects, ZGC does almost everything concurrently. It uses a technique called "colored pointers" and "load barriers" to track objects while your application threads are still running.
I like to think of ZGC as a cleaning crew that cleans the office while the employees are still working. They don't lock the doors; they just move the trash cans around while you're typing. The result is staggering: ZGC pause times are typically under a millisecond, regardless of whether your heap is 8MB or 16TB. If your primary KPI is P99 latency, ZGC is almost always the right answer.
Trading CPU cycles for millisecond predictability
Now, before you go and switch every project to ZGC, we need to talk about the cost. In software engineering, there is no such thing as a free lunch. ZGC achieves its near-zero pauses by shifting the work. Instead of one big stop-the-world pause, ZGC spreads that work across your application threads using those load barriers. This means your application's raw throughput—the total number of requests it can handle per second—will likely drop slightly compared to G1.
So, the trade-off is this: G1 gives you better overall throughput but higher peak latency. ZGC gives you incredibly consistent, low latency but consumes more CPU to get there. If you're running a background batch processing job where no one cares if a thread pauses for a second, ZGC is overkill and actually slower. But if you're running a user-facing API where a 200ms spike triggers a timeout in your load balancer, ZGC is a lifesaver.
📋 Practical Task
Analyzing Latency Shifts: G1 vs ZGC
In this exercise, you will simulate a memory-intensive application to observe the difference in pause times between the two collectors. You don't need to write a complex app; a simple loop that fills a List with large objects and occasionally clears it will suffice.
- Create a Java program that allocates 100MB of data every few seconds into a global list, and clears the list every 10 allocations to trigger GC.
- Run the application using the G1 collector (the default) with a limited heap:
java -Xms4G -Xmx4G -Xlog:gc* -jar your-app.jar - Examine the GC logs. Look for the
Pause YoungorPause Fullentries and note the duration in milliseconds. - Stop the app and restart it using the ZGC collector:
java -Xms4G -Xmx4G -XX:+UseZGC -Xlog:gc* -jar your-app.jar - Compare the logs. Identify the "Pause" events in ZGC and observe how the duration compares to the G1 pauses.
Goal: Document the difference in the maximum pause time (P99) between the two runs. You should see ZGC's pauses remain consistently sub-millisecond, while G1's pauses fluctuate based on the heap occupancy.
There are no comments for now.