Question 27 · Section 3

Can you manually invoke GC?

Exception: ExplicitGCInvokedConcurrent for scheduled Concurrent GC (requires deep understanding of your GC).

Language versions: English Russian Ukrainian

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

  1. Do NOT use System.gc() in business code
  2. ExplicitGCInvokedConcurrent for production
  3. jcmd GC.run for diagnostics
  4. DisableExplicitGC carefully (Direct Buffers!)
  5. Find the caller via JFR
  6. 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 ignores System.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 calls System.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]]