In languages like C or C++, developers must manually allocate and free memory in their programs. Forgetting to free memory leads to memory leaks, while freeing it too early causes program crashes. Java solves this problem by taking over memory management completely. The JVM automatically decides where objects live, keeps track of them, and uses a background cleaner called the Garbage Collector (GC) to destroy them when they are no longer needed.

Let's look at how Java organizes its memory (Stack vs. Heap) and how GC works using a simple kitchen analogy.

Illustration of Java Stack Frames and Generational Heap spaces with GC markers
Real-World Analogy: The Kitchen Workbench & Pantry

Imagine you are a chef working in a busy restaurant kitchen. Your kitchen is divided into two main areas:

  • The Workbench (Stack Memory): This is your immediate prep counter. It is small, organized, and holds your active recipes and local utensils (method scopes and local variables). Once you finish a recipe step, you clear the workbench completely.
  • The Pantry (Heap Memory): This is a huge shared storage area. Any big bowl, cake, or ingredient platter you prepare (objects on the heap) is stored here. To find a dish, you place a labeled sticky note on your workbench pointing to its location in the pantry (object references).
  • The Cleanup Robot (Garbage Collector): The robot patrols the pantry. It looks at all the stored dishes. If a dish in the pantry doesn't have any sticky notes pointing to it from the active recipes on the workbench (it is unreachable), the robot throws it in the trash (reclaims the memory) so new dishes can be made!

Understanding the Divisions of Heap: Generational GC

To make cleaning efficient, the Java Pantry (Heap) is divided into age sections because most objects die young (e.g., intermediate variables used in a loop):

  1. Young Generation (Eden & Survivor Spaces): Newly created objects are placed in the Eden space. When Eden fills up, the GC robot sweeps it quickly, deleting unused objects. Objects that survive are moved to Survivor spaces. This quick sweep is called a Minor GC.
  2. Old Generation (Tenured Space): If an object survives several rounds of Minor GC sweeps because it is referenced long-term (e.g., application configurations), the robot relocates it to the Old Gen space. This space is swept less frequently because the items here are expected to stay. A full sweep here is called a Major GC / Full GC.

Walkthrough of Code Scenarios

Let's trace how the JVM manages memory during code execution:

Scenario 1: Instantiating on the Heap

When you run Dish mainCourse = new Dish("Lasagna");:

  • A reference variable mainCourse is placed on the Stack frame.
  • A new Dish object with the name "Lasagna" is allocated in the Heap (Eden space).
  • The reference on the stack points to the object on the heap.
Scenario 2: Re-assigning References (Object Orphanage)

If you re-assign the reference: mainCourse = new Dish("Spaghetti");:

  • A new "Spaghetti" object is created on the heap.
  • The reference mainCourse on the stack is updated to point to the new "Spaghetti" object.
  • The original "Lasagna" object on the heap now has no references pointing to it. It is orphaned (unreachable) and immediately becomes eligible for Garbage Collection!
Scenario 3: Popping Stack Frames (Method End)

When calling prepareDessert(), a new frame is pushed to the stack containing tempBowl pointing to "Chocolate Mousse". When the method exits:

  • The prepareDessert() frame is popped off the Stack. The local variable tempBowl is destroyed.
  • The "Chocolate Mousse" object remains on the Heap, but now has zero references pointing to it. It is now eligible for GC.

Java Implementation

Below is a Java code example demonstrating references going in and out of scope and explicitly dereferencing objects:

package io.practise.myPractice;
 
public class MemoryManagementDemo {
    public static void main(String[] args) {
        // 1. Stack reference 'mainCourse' points to heap Lasagna object
        Dish mainCourse = new Dish("Lasagna"); 
        
        // 2. Call prepareDessert() -> pushes a new stack frame
        prepareDessert(); 
        // Once prepareDessert() returns, its frame is popped.
        // The Mousse object created inside it is now eligible for GC!
        
        // 3. Re-assigning reference orphans the original Lasagna object
        mainCourse = new Dish("Spaghetti"); 
        // Lasagna is now unreachable and eligible for GC!
        
        // 4. Setting to null breaks the final reference path
        mainCourse = null; 
        // Spaghetti is now also eligible for GC!
        
        System.out.println("Memory setup completed. Ready for cleanup robot!");
    }
 
    private static void prepareDessert() {
        // Stack reference 'tempBowl' exists only inside this method frame
        Dish tempBowl = new Dish("Chocolate Mousse"); 
        System.out.println("Mixing: " + tempBowl.getName());
    }
}
 
class Dish {
    private String name;
 
    public Dish(String name) {
        this.name = name;
    }
 
    public String getName() {
        return name;
    }
}

Conclusion

Java's split memory model makes execution fast and safe. The Stack is used for quick, structured method executions, while the Heap holds larger dynamic objects. Thanks to the Garbage Collector, developers don't have to write cleanup code—the JVM automatically monitors references and sweeps away garbage, preventing memory leaks and keeping programs running smoothly!