DEV Community

Shankar L
Shankar L

Posted on

Garbage Collection Explained

Why should you care?

When a program creates objects, those objects need memory.

For example:

Person user = new Person();
Enter fullscreen mode Exit fullscreen mode

At some point, the program may stop using that object.

The question is:

Who gets that memory back?

In languages with garbage collection, the runtime can automatically identify objects that are no longer reachable and reclaim their memory.

This removes a huge amount of manual memory-management work from the programmer.

But garbage collection is not magic.

It has rules, costs, and limitations.

The Problem

Imagine a program continuously creates objects:

for (int i = 0; i < 1000000; i++) {
    new Person();
}
Enter fullscreen mode Exit fullscreen mode

If memory were never reclaimed:

Create object
    ↓
Use memory
    ↓
Create another object
    ↓
Use more memory
    ↓
Repeat
    ↓
Memory exhausted
Enter fullscreen mode Exit fullscreen mode

The program would eventually run out of memory.

A garbage collector solves this by finding objects that are no longer reachable.

Object created
      ↓
Program uses object
      ↓
Object becomes unreachable
      ↓
Garbage Collector
      ↓
Memory can be reclaimed
Enter fullscreen mode Exit fullscreen mode

The Concept

The key concept behind garbage collection is reachability.

An object is generally considered alive if it can still be reached from a set of GC roots.

Conceptually:

GC Root
   ↓
Object A
   ↓
Object B
Enter fullscreen mode Exit fullscreen mode

Both A and B are reachable.

Now suppose:

GC Root
   ↓
Object A

Object B
   ↓
Object C
Enter fullscreen mode Exit fullscreen mode

If nothing can reach B or C anymore:

B → C
Enter fullscreen mode Exit fullscreen mode

they are unreachable.

The garbage collector can eventually reclaim their memory.

Simple Explanation

Think of a city full of houses.

Some houses are still connected to the road system.

Road
 ↓
House A
 ↓
House B
Enter fullscreen mode Exit fullscreen mode

These houses are reachable.

Now imagine another group of houses:

House C
 ↓
House D
Enter fullscreen mode Exit fullscreen mode

but there is no road connecting them to the city.

They are disconnected from the usable network.

Garbage collection works similarly.

It asks:

Can this object still be reached from something the program considers alive?

If not, the object may be garbage.

Real-world Analogy

Imagine a hotel.

Guests check into rooms:

Guest → Room
Enter fullscreen mode Exit fullscreen mode

When a guest leaves and nobody else is using the room:

Room → Empty
Enter fullscreen mode Exit fullscreen mode

The hotel staff eventually identifies empty rooms and makes them available again.

Garbage collection does something conceptually similar:

Object created
      ↓
Object reachable
      ↓
Object no longer reachable
      ↓
Garbage collector detects it
      ↓
Memory reclaimed
Enter fullscreen mode Exit fullscreen mode

The garbage collector is essentially the memory-management system that cleans up objects that are no longer needed.

Code Example

Consider Java:

public class Main {

    public static void main(String[] args) {

        Person user = new Person();

        user = null;
    }
}
Enter fullscreen mode Exit fullscreen mode

Initially:

user
 ↓
Person Object
Enter fullscreen mode Exit fullscreen mode

After:

user = null;
Enter fullscreen mode Exit fullscreen mode

we have:

user
 ↓
null

Person Object
     ↑
     │
No reference
Enter fullscreen mode Exit fullscreen mode

If there are no other references to that object, it becomes eligible for garbage collection.

Notice the wording:

Eligible for garbage collection

It does not mean:

The object is immediately deleted.

The garbage collector decides when and how reclamation occurs.

Garbage Collection Is Not free()

In C, programmers commonly manage memory manually:

int *p = malloc(sizeof(int));

/* use memory */

free(p);
Enter fullscreen mode Exit fullscreen mode

The programmer explicitly releases the memory.

In Java:

Person p = new Person();
Enter fullscreen mode Exit fullscreen mode

you normally do not write:

free(p)
Enter fullscreen mode Exit fullscreen mode

Instead, the runtime determines when the object is no longer reachable and can reclaim its memory.

So:

Manual Memory Management
→ Programmer controls deallocation

Garbage Collection
→ Runtime manages reclamation
Enter fullscreen mode Exit fullscreen mode

How Does the Garbage Collector Know?

A common conceptual approach is tracing garbage collection.

The collector starts from GC roots.

Examples of roots can include:

Active local references
Static references
Thread-related references
Runtime-managed references
Enter fullscreen mode Exit fullscreen mode

The exact root set depends on the runtime.

Then it follows references:

Root
 ↓
Object A
 ↓
Object B
Enter fullscreen mode Exit fullscreen mode

Anything reachable is considered live.

Anything not reachable is a candidate for reclamation.

Conceptually:

          Root
         /    \
        ↓      ↓
      Obj A  Obj B
        ↓
      Obj C

      Obj D
        ↓
      Obj E
Enter fullscreen mode Exit fullscreen mode

Here:

A, B, C
→ Reachable

D, E
→ Unreachable
Enter fullscreen mode Exit fullscreen mode

D and E can potentially be reclaimed.

Mark and Sweep

One classic garbage collection technique is Mark-and-Sweep.

Mark

Start from the roots and mark every reachable object.

Root
 ↓
A ✓
 ↓
B ✓

C
Enter fullscreen mode Exit fullscreen mode

C is not reachable.

Sweep

The collector identifies unmarked objects and reclaims their memory.

A ✓
B ✓
C ✗ → reclaim
Enter fullscreen mode Exit fullscreen mode

Conceptually:

Mark
 ↓
Identify reachable objects
 ↓
Sweep
 ↓
Reclaim unreachable objects
Enter fullscreen mode Exit fullscreen mode

This is one of the fundamental ideas behind tracing garbage collection.

What About Cycles?

This is an important advantage of reachability-based garbage collection.

Consider:

A → B
↑   ↓
└───┘
Enter fullscreen mode Exit fullscreen mode

A and B reference each other.

But suppose nothing else references A.

Then:

GC Root
   ↓
nothing

A ↔ B
Enter fullscreen mode Exit fullscreen mode

The objects are unreachable even though they reference each other.

A reference-counting system can struggle with such cycles because both objects still have nonzero reference counts.

A tracing collector can identify that neither is reachable from the roots and reclaim them.

Generational Garbage Collection

Modern garbage collectors often use the observation that:

Most objects die young.

For example:

Temporary object
     ↓
Used briefly
     ↓
No longer needed
Enter fullscreen mode Exit fullscreen mode

So collectors may divide objects into generations or regions based on age.

A simplified model:

Young Generation
       ↓
Short-lived objects

Survivors
       ↓
Older Generation

Long-lived objects
Enter fullscreen mode Exit fullscreen mode

The exact design differs between garbage collectors.

The goal is to avoid repeatedly scanning every object in the heap.

Why Young Objects Matter

Imagine:

for (int i = 0; i < 1000000; i++) {
    String temp = createTemporaryString();
}
Enter fullscreen mode Exit fullscreen mode

Many temporary objects may become unreachable very quickly.

Instead of treating all objects equally, a generational collector can focus collection work on areas where garbage is expected to be abundant.

This can improve efficiency.

Garbage Collection Pauses

Garbage collection itself requires computation.

At certain points, the runtime may need to:

Identify objects
Update metadata
Move objects
Reclaim memory
Adjust references
Enter fullscreen mode Exit fullscreen mode

Some collectors may temporarily pause application threads for certain phases.

This can produce GC pauses.

For a normal desktop application, a small pause may not matter.

For a:

High-frequency trading system
Game
Real-time application
Large backend service
Enter fullscreen mode Exit fullscreen mode

latency can matter significantly.

Modern garbage collectors therefore use sophisticated techniques to reduce pause times.

Common Mistakes

Mistake 1: Thinking garbage collection happens immediately

Consider:

Person p = new Person();
p = null;
Enter fullscreen mode Exit fullscreen mode

The object may become eligible for collection.

It does not mean the garbage collector immediately frees it.

The runtime decides when collection occurs.

Mistake 2: Thinking garbage collection prevents all memory problems

It does not.

You can still accidentally retain references:

static List<Object> cache = new ArrayList<>();
Enter fullscreen mode Exit fullscreen mode

If objects are continuously added and never removed:

Cache
 ↓
Object
Object
Object
Object
...
Enter fullscreen mode Exit fullscreen mode

The objects remain reachable.

The garbage collector cannot reclaim them simply because the program no longer logically needs them.

This can result in a memory leak.

Mistake 3: Thinking System.gc() guarantees collection

In Java:

System.gc();
Enter fullscreen mode Exit fullscreen mode

is only a request or hint to the runtime.

It does not guarantee that garbage collection will immediately happen.

Mistake 4: Thinking garbage collection deletes objects

The important concept is memory reclamation.

The runtime determines that an object is unreachable and makes its storage available for reuse.

The language-level object itself is not necessarily "deleted" in the simple sense people often imagine.

Advanced Notes

Stop-the-world Collection

Some garbage collection phases may temporarily stop application threads.

Conceptually:

Application
    ↓
Pause
    ↓
GC work
    ↓
Resume
Enter fullscreen mode Exit fullscreen mode

This is called a stop-the-world pause.

Not every GC operation necessarily requires the entire application to stop, and modern collectors perform significant work concurrently.

Concurrent Garbage Collection

Some modern collectors perform parts of garbage collection while application threads continue running.

Conceptually:

Application ───────────────→
GC          ───────────────→
Enter fullscreen mode Exit fullscreen mode

instead of:

Application ─── Pause ──────→
GC               ↑
Enter fullscreen mode Exit fullscreen mode

This can reduce latency, although it introduces additional complexity and overhead.

Object Movement

Some garbage collectors move objects to compact memory.

Imagine:

Before:

┌────┬────┬────┬────┬────┐
│ A  │    │ B  │    │ C  │
└────┴────┴────┴────┴────┘
Enter fullscreen mode Exit fullscreen mode

After compaction:

┌────┬────┬────┬────┬────┐
│ A  │ B  │ C  │    │    │
└────┴────┴────┴────┴────┘
Enter fullscreen mode Exit fullscreen mode

This can reduce fragmentation.

But if an object moves, references to it must remain correct.

The runtime and garbage collector handle this according to the collector's design.

Garbage Collection and References

This connects directly to the previous topic.

Consider:

Person a = new Person();
Person b = a;
Enter fullscreen mode Exit fullscreen mode

Conceptually:

a ──────┐
        ↓
     Person
        ↑
        │
b ──────┘
Enter fullscreen mode Exit fullscreen mode

If:

a = null;
Enter fullscreen mode Exit fullscreen mode

the object is still reachable:

b
 ↓
Person
Enter fullscreen mode Exit fullscreen mode

Therefore, it cannot yet be reclaimed.

Only after all relevant paths from the GC roots disappear can the object become unreachable.

Garbage Collection and Memory Allocation

The complete lifecycle can be visualized as:

Allocation
    ↓
Object created
    ↓
Object used
    ↓
References change
    ↓
Object becomes unreachable
    ↓
Garbage collector detects it
    ↓
Memory reclaimed
    ↓
Memory reused
Enter fullscreen mode Exit fullscreen mode

This is the connection between the previous topic and this one.

Memory allocation gives the program storage.

Garbage collection determines when certain dynamically allocated storage can be reclaimed.

The Bigger Picture

Garbage collection connects several fundamental concepts:

Objects
   ↓
References
   ↓
Reachability
   ↓
Garbage Collection
   ↓
Memory Reclamation
   ↓
Memory Reuse
Enter fullscreen mode Exit fullscreen mode

At the system level:

Application
     ↓
Runtime
     ↓
Garbage Collector
     ↓
Heap
     ↓
Operating System
     ↓
Physical Memory
Enter fullscreen mode Exit fullscreen mode

The programmer works mostly with objects and references.

The runtime handles much of the underlying memory-management complexity.

The Most Important Mental Model

Do not think:

Object becomes unused
        ↓
Immediately deleted
Enter fullscreen mode Exit fullscreen mode

Instead think:

Object becomes unreachable
        ↓
Eligible for collection
        ↓
Garbage collector runs
        ↓
Memory reclaimed
Enter fullscreen mode Exit fullscreen mode

And the key question is:

Can the object still be reached from a live root?

If yes:

Keep it
Enter fullscreen mode Exit fullscreen mode

If no:

It may be reclaimed
Enter fullscreen mode Exit fullscreen mode

Summary

Garbage collection is an automatic memory-management mechanism used by many programming languages and runtimes.

Its fundamental idea is:

Find objects that are no longer reachable
        ↓
Reclaim their memory
        ↓
Reuse that memory
Enter fullscreen mode Exit fullscreen mode

The most important concepts are:

  • Reachability determines whether an object is still potentially needed.
  • GC roots provide the starting points for reachability analysis.
  • Mark-and-sweep is a classic tracing technique.
  • Generational collection takes advantage of short-lived objects.
  • Compaction can reduce fragmentation.
  • GC pauses can affect application latency.
  • Garbage collection does not eliminate all memory problems.
  • An unreachable object is eligible for collection, not necessarily immediately collected.

The deeper lesson is:

Garbage collection is not about finding objects that the programmer thinks are useless. It is about determining which objects are no longer reachable according to the runtime's rules.

Once you understand reachability, garbage collection stops being a mysterious background process and becomes a logical consequence of how references and object lifetimes work.

Top comments (0)