Can you manually invoke GC?
Exception: ExplicitGCInvokedConcurrent for scheduled Concurrent GC (requires deep understanding of your GC).
Junior Level
Yes, you can: System.gc() — a request to the JVM to run garbage collection.
System.gc(); // "Please, clean up garbage"
BUT this is an anti-pattern!
- GC knows when to clean itself
- Manual call = application stop
- In production, do NOT use System.gc() for regular cleanup. Exception: ExplicitGCInvokedConcurrent for scheduled Concurrent GC (requires deep understanding of your GC).
When acceptable:
- In tests
- In benchmarks
- Before taking a dump
Middle Level
Why it’s bad
1. Full GC = Stop-The-World
→ All threads stop
→ Pause: seconds on large Heaps
2. GC knows better when to collect
→ Self-learning algorithms (G1, ZGC)
→ Manual call resets statistics
3. Inefficient
→ Eden still has plenty of space
→ Wasted CPU
Direct Buffers — the Exception
// NIO allocates memory outside Heap
ByteBuffer buf = ByteBuffer.allocateDirect(100_000_000);
// 100 MB outside -Xmx!
// JVM doesn't see Native Memory load
// → GC doesn't run (Heap empty)
// → Native Memory runs out
// → NIO calls System.gc()!
// If disabled: -XX:+DisableExplicitGC
// → OOM in Native Memory with empty Heap!
Control Flags
# Completely ignore System.gc()
-XX:+DisableExplicitGC
# Execute concurrently (G1)
-XX:+ExplicitGCInvokedConcurrent
Alternative: jcmd
# External GC call (without code changes)
jcmd <PID> GC.run
Senior Level
ExplicitGCInvokedConcurrent
Instead of Full GC STW:
→ G1 launches background cycle
→ Application keeps working
→ Minimal pauses
"Golden mean" for production
GC Thrashing Masking
// ❌ Attempt to "cure" a leak
try {
allocate();
} catch (OutOfMemoryError e) {
System.gc(); // Masks the problem!
allocate(); // Still crashes
}
// Application enters Thrashing:
// 95% time on GC, 5% on work
JIT Deoptimization
Full GC can trigger class unloading, which causes deoptimization
of hot methods. Code Cache is cleaned separately (CodeCache sweeper),
but class unloading during Full GC affects JIT-compiled methods.
→ After GC: interpretation
→ JIT recompiles from scratch
→ Throughput drops
Diagnostics
# Who calls System.gc()?
# 1. GC Logs
-Xlog:gc*
→ Look for: "Pause Full (System.gc())"
# 2. JFR
→ "System GC" event
→ Full stack trace of call
# 3. jcmd
jcmd <PID> GC.run # Manual call for tests
Best Practices
- Do NOT use System.gc() in business code
- ExplicitGCInvokedConcurrent for production
- jcmd GC.run for diagnostics
- DisableExplicitGC carefully (Direct Buffers!)
- Find the caller via JFR
- Benchmarks — only legitimate use
Senior Summary
- System.gc() = anti-pattern in production
- Direct Buffers = only exception
- ExplicitGCInvokedConcurrent = compromise
- JFR = find the caller
- JIT deoptimization = hidden overhead
- jcmd = external call for tests
Interview Cheat Sheet
Must know:
System.gc()— request to JVM for Full GC (STW, seconds on large Heaps); anti-pattern in production- GC knows better when to collect: self-learning algorithms (G1, ZGC) adapt to Allocation Rate; manual call resets statistics
- Direct Buffers — the exception: NIO memory outside Heap; GC doesn’t see load (Heap empty) → Native Memory runs out → NIO calls
System.gc() -XX:+DisableExplicitGC— completely ignoresSystem.gc(); dangerous for Direct Buffers (OOM in Native Memory with empty Heap)-XX:+ExplicitGCInvokedConcurrent— “golden mean”: G1 launches background concurrent cycle instead of Full GC STW- JIT deoptimization: Full GC → class unloading → hot methods deoptimized → interpretation → throughput drops
- Diagnostics:
-Xlog:gc*(look for “Pause Full (System.gc())”), JFR (“System GC” event with stack trace)
Common follow-up questions:
- Why are Direct Buffers an exception? —
ByteBuffer.allocateDirect()allocates native memory outside Heap; GC doesn’t see Heap load → doesn’t run → Native Memory runs out → NIO callsSystem.gc() - What is GC Thrashing? — Application enters cycle: 95% time on GC, 5% on work;
System.gc()masks the problem, doesn’t solve it - Why is JIT deoptimization a problem? — Full GC → class unloading → hot methods recompiled from scratch → throughput drops for seconds
- When is it legitimate to call System.gc()? — Benchmarks (JMH), tests, before taking a dump to clean garbage
Red flags (DO NOT say):
- “I call System.gc() after big operations for optimization” — GC knows itself; manual call = STW + statistics reset
- “DisableExplicitGC is always safe” — Direct Buffers: OOM in Native Memory with empty Heap
- “System.gc() cures a memory leak” — masks the problem; application will still enter GC Thrashing
Related topics:
- [[28. Why you should not call System.gc()]]
- [[4. What is Garbage Collection]]
- [[19. What happens on OutOfMemoryError]]
- [[13. What is G1 GC]]
- [[16. What is stop-the-world]]