Java's garbage collector runs automatically, identifying heap objects no longer reachable from any live reference and reclaiming their memory, without the programmer ever calling free or delete. Rather than shipping a single collector, the JVM offers several implementations with different tradeoffs: G1 (Garbage-First), the default since Java 9, balances throughput and pause times for general-purpose use; ZGC, a newer low-latency collector, targets sub-millisecond pause times even on very large heaps, at some cost to raw throughput, and gained a generational mode in recent JDK releases to further improve efficiency.
An object becomes eligible for collection when unreachable
Setting the reference to null doesn't force immediate collection — it just makes the object unreachable, eligible to be collected whenever the GC next runs, on its own schedule.
User user = new User("Alice"); // reachable via the "user" reference
user = null; // no more references to the User object exist
// the User object is now ELIGIBLE for collection — but not necessarily collected yet;
// the GC decides when to actually reclaim itChoosing a collector
G1 is a solid, well-tested default for most applications; ZGC is worth switching to specifically when a latency-sensitive service (a trading system, a real-time API) cannot tolerate even the brief pauses G1 occasionally introduces on a large heap.
$ java -XX:+UseG1GC -jar app.jar // explicit G1 (already the default since Java 9)
$ java -XX:+UseZGC -jar app.jar // switch to ZGC for sub-millisecond pause targetsWhy explicit free()/delete() doesn't exist in Java
Manual memory management in C/C++ is a real, historically common source of two serious bug classes: use-after-free (accessing memory already released) and double-free (releasing the same memory twice). Automatic garbage collection eliminates both categories entirely by design — the tradeoff is giving up precise control over exactly when memory is reclaimed, and accepting some CPU overhead for the collector's own bookkeeping.