Goal
Maximise throughput (total work done per unit time) by doing GC work in parallel during stop-the-world pauses.
- Type: Stop-the-world, multi-threaded collector
- Focus: Throughput ✅, Latency ❌ (longer worst-case pauses are acceptable)
- Generational: Yes (Young + Old)
- Default history: Default up to JDK 8 (G1 is default from JDK 9+)
🧠 Mental model
[App threads run at full speed] →
[Stop-the-world GC pause, many GC threads work in parallel] →
[App resumes at full speed] → (repeat)

- While the app runs, there is no concurrent GC work stealing CPU.
- When memory pressure hits, all app threads stop; GC runs with multiple GC worker threads to finish quickly; then app resumes.
🧩 Heap layout (classic generational)
- Young Gen (Eden + Survivors): frequent Minor GCs (parallel, STW).
- Old Gen: occasional Major/Full GCs (parallel, STW).
- Layout is the classic two-generation scheme; sizing is managed by ergonomics.
📈 Throughput
- Strong side: app runs uninhibited between GCs; no concurrent barriers beyond the usual write barriers.
- Parallelism: GC uses many worker threads to finish pauses quicker on multicore machines.
- Result: excellent aggregate throughput for batch jobs, compute heavy services, and scenarios where tail latency isn’t strict.
⏱️ Latency
- Trade-off: pauses can be long, especially when the Old Gen needs collection (entire old space is handled in a single STW operation).
- If you need tighter tail latencies (p99/p999), consider G1 or ZGC.
🧵 Threads (how it scales)
- Uses multiple GC worker threads (typically proportional to CPU count) during pauses.
- More cores ⇒ shorter pauses (to a point), but still STW.
🔧 Quick tuning cheatsheet
Start here, then measure with JFR/JMX.
-
Enable (on modern JDKs where it’s not default):
-XX:+UseParallelGC -
Control GC worker threads (rarely needed; by default, ergonomics do fine):
-XX:ParallelGCThreads=<N> -
Young/Old balance (if you must nudge):
-XX:NewRatio=<n> # old/young ratio (e.g., 2 => old is 2x young) -XX:SurvivorRatio=<n> # Eden:Survivor sizing hint -
Adaptive sizing (on by default; let JVM chase a pause/throughput target):
-XX:+UseAdaptiveSizePolicy -XX:MaxGCPauseMillis=<target> # soft goal; Parallel may not meet strict low targets -XX:GCTimeRatio=<n> # trade GC time vs app time (lower -> more GC, lower pauses)
Tip: With Parallel GC, throughput targets (e.g.,
-XX:GCTimeRatio) tend to be more meaningful than strict lowMaxGCPauseMillisgoals. If you truly need predictable low pauses, Parallel isn’t the right tool.
🔍 When to use Parallel GC
- Batch/offline processing, ETL, report generation.
- Throughput-first microservices where occasional longer pauses are acceptable.
- Deployments with lots of CPU where parallel STW can finish fast and you don’t need sub-50 ms p99.
🧪 Observability (what to watch)
- Pause times & frequency: GC logs/JFR (
-Xlog:gc*,safepointon JDK 9+). - Promotion failures / Old Gen expansions (sign of too-small heap or too-young promotion).
- Allocation rate vs Minor GC cadence: ensure Young Gen sized for your allocation burst.
🗺️ Parallel vs G1 vs ZGC (1-liner)
- Parallel: best throughput, STW pauses (can be long on Old Gen).
- G1: balanced; region-based; mixed collections to avoid worst-case pauses.
- ZGC: ultra-low latency (sub-ms pauses) with concurrent work; great for big heaps.
🧰 Minimal flag sets
Use Parallel GC on JDK 17+:
java -XX:+UseParallelGC -Xms4g -Xmx4g -jar app.jarNudge toward more throughput (softer pause goals):
java -XX:+UseParallelGC -XX:GCTimeRatio=9 -Xms4g -Xmx4g -jar app.jarHeavier young gen (handle high alloc rate):
java -XX:+UseParallelGC -XX:NewRatio=2 -Xms8g -Xmx8g -jar app.jar