What is Java garbage collection?

In Java we create objects with new; and donot need to call delete. The garbage collector (GC) in a JVM subsystem that finds objects no longer reachable from running code and reclaims their heap memory automatically.
Object is alive(not garbage collected) if a chain of references exists from GC roots(thread stacks, static fields, JNI handles, etc) to that object. Everything else is garbage.
GC runs inside the JVM process on the heap Stack frames and metaspace are managed separately; GC focuses on object instances in the heap.
See Heap Area(Eden, S0, S1)

Heap & generations

GC runs on the JVM heap: young generation (Eden, Survivor S0/S1) and old generation (tenured). See Java memory layout for the full diagram.

How the garbage collector works (high level)

Different collectors implement these steps differently (parallel threads, concurrent marking, region-based collection). All share the goal: free memory for new allocations while keeping pauses acceptable.

Small example — Eden and minor GC

Java Garbage Collection
public class GcDemo {
public static void main(String[] args) {

    for (int i = 0; i < 100000; i++) {
        String temp = "order-" + i;     //  Each iteration allocates String in Eden
    }

    // All 100k strings are garbage
  }
}

Iteration             Eden (Size=3)
0                       (empty)
1        temp -----> "order-1"

                       "order-1"   // unreachable, No Reference. garbage. Still Memory Occupied
2        temp -----> "order-2"

                       "order-1"   // garbage
                       "order-2"   // garbage
3        temp -----> "order-3"

4    Eden is Full. JVM Need Space. JVM Pause. Runs Minor GC.
         temp -----> "order-4  // Everything else cleaned
      
C++ Memory Free

int main() {
    {
        std::string temp = "hello";
    }
}

Create
↓
Scope ends
↓
Destructor
↓
Memory freed
            

When exactly does GC run?

Eden fills up OR
Heap usage crosses thresholds (memory Pressure) OR
Allocation rate is high OR
JVM decides it's worthwhile OR
Sometimes when System.gc() is requested (only a hint; the JVM may ignore it)

Stop-the-world (STW) pause (millisec)

Means all application threads that use the heap are halted while the GC performs work (copying, marking roots, compaction). During STW your code does not run — API latency spikes if the pause is long.
Modern GCs cannot eliminate the STW compltely, even they collected garbage parallel to execution.

Concurrent mark phase

  In collectors like G1 or ZGC, in concurrent mark phase GC walks the heap to identify live objects while application threads mostly keep running. Only brief STW checkpoints bracket this phase (initial mark, remark in G1).
Concurrent mark phase duration depends on: 1. Heap size(more regions/pages to scan), 2. Live set size(more live objects → longer mark).

Can Java have memory leaks?

C++ have new() and delete(), and if we forget to delete() there can be memory leak, but Java does not have delete() so when memory leak can happen in Java?

1. Objects kept on getting allocated and their References not released


static List >byte[]< cache = new ArrayList><();
while (true) {
    cache.add(new byte[1024 * 1024]); // 1 MB each
}

The list keeps references forever, so GC cannot free the arrays. Eventually you'll get:
  java.lang.OutOfMemoryError: Java heap space
      

Allocation rate vs GC reclaim rate


Allocation rate > GC reclaim rate
        ↓
Heap fills up
        ↓
Frequent GC
        ↓
Long GC pauses
        ↓
OutOfMemoryError
      

Major and modern garbage collectors

Collector Era Style Typical use
Serial GC Classic Single-threaded, stop-the-world Small heaps, client apps
Parallel GC (Throughput) Classic Multi-threaded STW, maximize throughput Batch jobs, Java 8 default on server
CMS Older enterprise Concurrent old-gen marking (deprecated) Legacy systems (removed in modern JDKs)
G1 GC Modern default Region-based, predictable pause target Java 9+ default; enterprise services
ZGC Modern low-latency Concurrent, sub-ms pauses (typical goal) Large heaps, latency-sensitive apps
Shenandoah Modern low-latency Concurrent compaction Similar niche to ZGC (OpenJDK / some vendors)

Example — G1 GC (Garbage-First)

G1 divides the heap into equal-sized regions (1–32 MB). Eden, survivors, and old gen are logical sets of regions — not one fixed contiguous block.

Why banks choose G1

Banks choose G1 because
1. G1 gives predictable pause times(hence preferred due to debugging). MaxGCPauseMillis
2. Banks need regulatory audit trails, 24×7 availability, consistent response times for customer channels, and large long-lived caches (accounts, rates) in old generation — GC choice directly affects peak-hour latency and outage risk.

How G1 works


Young collection: collect all Eden + survivor regions (stop-the-world, parallel).
Concurrent mark: mark live objects across heap while app runs (mostly concurrent).
Mixed collection: collect young regions + some old regions with most garbage (“garbage-first”).

# Enable G1 (default on Java 9+ for server-class machines)
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-Xms4g -Xmx4g
      

Moving Objects from Eden to old generation

Tenuring / Promotion: Each object in young gen has an age (survived GC count). When age ≥ MaxTenuringThreshold (or survivor space is full), live objects are promoted to old generation.
Promotion failure (also called promotion denied): During minor GC, the collector needs free space in old gen to receive promoted objects. If old gen is full or too fragmented to fit them, promotion fails. The JVM may then trigger a full GC (expensive)
Common causes of Promotion Failure old gen too small (-Xmx mostly eaten by tenured objects), memory leak (objects never become garbage), spike of long-lived allocations, fragmentation under certain collectors.
Mitigation to Promotion: increase heap, fix leaks, tune tenuring threshold, use G1 mixed cycles earlier, or switch to ZGC for very large heaps; analyze with GC logs and heap dumps.

Key JVM parameters that affect GC

Flag Meaning
-Xms / -Xmx Initial and max heap size
-XX:+UseG1GC Select G1 collector
-XX:MaxGCPauseMillis G1 pause time goal (soft target)
-XX:NewRatio Old:young ratio (generational collectors)
-XX:SurvivorRatio Eden vs one survivor size
-XX:MaxTenuringThreshold GCs survived before promotion to old gen
-XX:+UseZGC Enable ZGC (Java 15+ production, Java 21+ generational ZGC)
-Xlog:gc* Unified GC logging (Java 9+)

Monitoring GC metrics

1. GC logs: -Xlog:gc*:file=gc.log:time,uptime,level,tags (Java 9+). Shows pause times, phases, promotion, heap before/after.
2. JFR (Java Flight Recorder) — low-overhead events: jdk.GCConfiguration, jdk.GCPhasePause, allocation profiling.
3. JMX / VisualVM / JDK Mission Control — live heap, GC count, GC time, old-gen usage.
4. Micrometer + Prometheus/Grafana — export jvm.gc.pause, jvm.memory.used from Spring Boot Actuator.

Metrics to watch:

GC pause time (p50, p99, max STW)
GC frequency (young vs old/mixed)
Heap used after full GC (trend up = leak?)
Promotion rate and promotion failures
Allocation rate (MB/s into Eden)

Tuning / demo GC (hands-on)

1. Run with verbose GC logging:

java -Xms512m -Xmx512m -Xlog:gc*:stdout:time,uptime GcDemo
      

2. Force GC in a demo (not for production):

System.gc(); // hint only — JVM may ignore; used in tutorials
      

3. Compare collectors on same workload:

java -XX:+UseG1GC -Xlog:gc*:file=g1.log MyApp
java -XX:+UseParallelGC -Xlog:gc*:file=parallel.log MyApp
java -XX:+UseZGC -Xlog:gc*:file=zgc.log MyApp
      

4. Tune G1 for banking-style latency target:

-Xms8g -Xmx8g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=100
-XX:+ParallelRefProcEnabled
-Xlog:gc*,gc+heap=debug:file=gc.log:time,tags
      

Process: measure baseline → change one flag at a time → compare p99 latency and GC logs under load test (JMeter/Gatling) → avoid tuning without production-like traffic.