Skip to content
Ameen Abozaid
PublishedBEG-038-L01

Stack and heap: a picture

  • Lesson 1 of 5
  • 10 min read
  • Beginner
On This Lesson 11 topics

Lesson Overview

You already know that each method call gets its own local variables (BEG-014 introduced the call stack), and that new creates an object (BEG-020). This module puts both ideas into one picture so you can see what a running Java program holds in memory.

The picture has two areas. The stack is like a desk: one small working area per method call, holding sticky notes (the local variables). The heap is like a warehouse: a big shared space holding the real objects. A sticky note for an object does not contain the object. It contains the shelf address.

Why it matters: almost every confusing Java behaviour in the next lessons (two variables changing together, a method that "changes" its argument, memory that is never freed) becomes obvious once you can draw this picture. Later, JVM tuning and leak diagnosis build on it. In this lesson we only draw; the next lesson uses the drawing to explain sharing.

Learning Objectives

By the end of this lesson you will be able to:

  • Explain what a stack frame is and what it holds.
  • Say where primitive values and object values live.
  • Explain what a reference is and why a variable of object type is not the object itself.
  • Draw the stack and heap for a short program.
  • Say what happens to a frame when its method returns, and what does not happen to heap objects.
  • Recognise that arrays are objects and live on the heap.

Written Explanation

The stack: one frame per running method call

When a method is called, the JVM (the Java Virtual Machine, the program that runs your compiled code) creates a frame for that call and places it on top of the stack. The frame holds the method's parameters and local variables. When the method returns, its frame is removed, and the variables inside it are gone. You saw this behaviour in BEG-014: each call has its own variables, and the caller resumes when the callee finishes.

Because frames are stacked, the one on top is the method currently running. main sits at the bottom, since it was called first.

The heap: where objects live

Every object created with new is placed on the heap. The heap is shared: any frame that has the object's address can use it, and the object does not disappear when the method that created it returns. Its lifetime is decided by something else (lesson 3).

Primitives versus references

A local variable of a primitive type (int, double, boolean, and the others from BEG-005) stores its value directly in the frame. A local variable of any class type (String, Person, List<String>, an array) stores a reference: a value that identifies an object on the heap. In diagrams we draw it as an arrow.

text
STACK (desk)                      HEAP (warehouse)
main frame
  age   [ 30 ]
  p     [ ●---------------------> Person { name ─────> "Ana" }

Notes on the diagram:

  • age holds 30 itself.
  • p holds a reference. The Person object, with its own field name, lives on the heap.
  • Fields of an object live inside the object, on the heap. A field of class type holds a reference too, so the diagram has an arrow from the object to the String.
  • null is the reference that points to nothing. We draw it as an arrow to nowhere, or write null in the box.

A drawing recipe

For any short program, draw in this order:

  1. One box per frame, main at the bottom, newer calls above.
  2. Inside each frame, one slot per parameter and local variable, in the order they are declared.
  3. For each new, a box on the heap with its fields.
  4. For each variable of class type, an arrow from its slot to the object (or null).
  5. When a method returns, cross out its frame. Leave the heap boxes alone.

You do not need to track exact addresses; arrows are enough. (Real JVMs decide how to store objects and may lay them out differently. The picture is a mental model, not a memory dump.)

What this means for lifetimes

  • A local variable's life matches its frame: it ends when the method returns.
  • An object's life does not depend on the method that created it. If a method creates an object and returns the reference, the object is still there; the caller's variable now points to it.

That is why Person make() can return a new Person safely. What dies is the method's frame and its sticky notes, not the warehouse item.

Examples

Example 1: a primitive and an object (simple)

java
public class L01a {
    static class Person {
        String name;
        Person(String name) { this.name = name; }
    }
    public static void main(String[] args) {
        int age = 30;
        Person p = new Person("Ana");
        System.out.println(age + " " + p.name);
    }
}

Output:

text
30 Ana

At the println, the stack holds one frame:

text
STACK                     HEAP
main
  args  [ ●-----> String[] (empty array, supplied by the JVM)
  age   [ 30 ]
  p     [ ●-----> Person { name ●-----> "Ana" }

args is a parameter, so it has a slot, and the array is an object on the heap.

Example 2: a method call and a returned object (practical)

java
static Person make(String n) {
    int len = n.length();
    Person tmp = new Person(n);
    System.out.println("in make, len = " + len);
    return tmp;
}
public static void main(String[] args) {
    Person p = make("Omar");
    System.out.println("back in main: " + p.name);
}

(Person is the same nested class as above.) Output:

text
in make, len = 4
back in main: Omar

While make runs, there are two frames:

text
STACK                          HEAP
make   (top)
  n     [ ●-----> "Omar" ]  <---------- same String object
  len   [ 4 ]
  tmp   [ ●-----> Person { name ●-----> "Omar" }
main
  args  [ ●-----> String[] ]
  p     [ (not assigned yet) ]

When make returns, its frame is removed: n, len, and tmp are gone. The Person is untouched, and main's p receives the reference. Notice n and the name field point to the same String object; the heap objects can be shared by several arrows. Lesson 2 builds on this.

Example 3: an array of objects (realistic)

java
Account[] accounts = new Account[2];
accounts[0] = new Account("Lina", 100.0);
System.out.println(accounts[0].owner + " " + accounts[0].balance);
System.out.println(accounts[1]);

(Account is a small class with owner and balance.) Output:

text
Lina 100.0
null

An array is an object, so it lives on the heap. The array of class type holds references, one per slot, and a new slot is null until you assign it:

text
STACK                      HEAP
main
  accounts [ ●-----> Account[2] { [0] ●-----> Account { owner "Lina", balance 100.0 }
                                  [1] null }

Creating the array did not create any Account; only the one new Account(...) did. This explains why accounts[1].owner would fail with a NullPointerException: there is no object at the end of that arrow (BEG-037 covered how to avoid that).

Backend Insight

A backend service handles many requests at the same time. Each request typically runs on its own thread, and each thread has its own stack, with its own frames and local variables. The heap, by contrast, is shared by all threads. (Threads are taught later; for now, remember that "stack = per call chain, heap = shared".)

This explains common real-world facts:

  • Objects such as a loaded configuration or a cache live on the heap and are visible to every request.
  • Local variables are private to one request's call chain, which is why they are naturally safe from interference.
  • When people talk about a server's "memory usage", they mostly mean the heap. Stacks are small and bounded.

When you later read a stack trace in a log (BEG-029), you are reading a printed copy of the stack frames at the moment of an exception, top frame first.

Common Mistakes

Mistake 1: "The object is inside the variable"

What goes wrong: you picture Person p as a box containing the person. Then assignments and method calls (next lesson) seem to behave randomly.

Fix: always draw an arrow from the variable's slot to a separate heap box.

Mistake 2: "The object disappears when the method ends"

What goes wrong: you avoid returning objects, or you think a returned Person is dangling.

Fix: the frame disappears, not the object. The object lives as long as something can still reach it (lesson 3).

Mistake 3: "Everything lives on the heap" or "everything on the stack"

What goes wrong: you cannot predict what is shared.

Fix: local variables and parameters are in frames. Objects, including arrays and the contents of fields, are on the heap. Treat a primitive as a value in the slot and an object as an arrow.

Mistake 4: forgetting the extra objects

What goes wrong: new Account[2] is drawn as two accounts.

Fix: it makes one array object with two null slots. Each new makes exactly one object (plus whatever its constructor creates).

Exercises

Level A: Foundation

  1. Fill in the blank: local variables of a running method live in a ______, and objects created with new live on the ______.
  2. For int n = 7; String s = "go";, state which variable holds the value itself and which holds a reference.
  3. True or false: when make returns the Person it created, that Person is removed along with make's frame.

Level B: Application

  1. Draw stack and heap at the final line of:
java
public static void main(String[] args) {
    double price = 9.5;
    String label = "tea";
    int[] counts = new int[3];
    counts[1] = 4;
}
  1. A method static Account open(String owner) creates new Account(owner, 0) and returns it; main calls Account a = open("Sami");. List which frames exist while open runs, and what remains after it returns.
  2. In Person p = null;, does a Person object exist on the heap? What does the slot of p contain?

Level C: Challenge

  1. Draw the picture at the println in:
java
static int total(int[] values) {
    int sum = 0;
    for (int v : values) sum += v;
    System.out.println(sum);
    return sum;
}
public static void main(String[] args) {
    int[] data = {2, 3, 5};
    int result = total(data);
}

Which frame holds values, and where does the array live?

Show solutions
  1. Stack frame; heap.
  2. n holds the value 7 itself. s holds a reference to a String object on the heap.
  3. False. The frame is removed, but the Person lives on the heap and the caller receives its reference.
  4. Frame main: args with an arrow to an empty String[]; price = 9.5; label with an arrow to a String "tea"; counts with an arrow to an int[3] object on the heap whose contents are 0, 4, 0. (A string literal is a String object; the exact place where the JVM stores literals is an implementation detail, so drawing it on the heap is fine at this level.)
  5. While open runs: main (bottom) and open (top). After the return: only main. open's frame, with owner and any other locals, is gone; the Account on the heap remains, and a in main points to it.
  6. No. The slot of p holds null, which means "refers to no object". The heap contains no Person.
  7. Frames: total (top) with values pointing to the array, sum = 10, v (only inside the loop); main with args, data pointing to the array, and result not yet assigned. There is one array on the heap, {2, 3, 5}, and both values and data point to it. The array is shared: it was not copied. (Lesson 2 explains this fully.)

Mini Challenge

Take any class from an earlier module (for example Account from Bank Accounts) and write a three-line main that creates two objects and keeps one of them in a local variable. On paper, draw the stack and heap. Then add a one-sentence caption saying which part would disappear if main were a method that had just returned. Keep the drawing: you will reuse it in lesson 2.

Summary & Next Lesson

Key takeaways

  • The stack has one frame per running method call; frames hold parameters and locals and disappear on return.
  • Primitives are stored directly in the slot; class-type variables store references to heap objects.
  • new creates one object on the heap. Arrays are objects too.
  • A frame is short-lived; an object lives as long as it can still be reached.
  • Draw arrows, not boxes inside boxes.

Next lesson

Next is References and aliasing: what happens when two variables point to the same object, and what that means when you pass objects to methods.

Self-check

  1. What does a stack frame hold?
  2. Where does the Person created by new Person("Ana") live?
  3. What does String s; hold inside a method after s = null;?
  4. After int[] a = new int[4];, how many objects are on the heap, and what do its elements hold?
  5. When a method returns, what is removed from the stack?
Show answers
  1. The method's parameters and local variables (and bookkeeping the JVM needs to resume the caller).
  2. On the heap. Only the reference is in the frame.
  3. null, meaning no object.
  4. One (the array); its four elements are int values, each 0.
  5. That method's frame and all its local variables.

References

  • The Java Tutorials: Passing Information to a Method: verified that object arguments are references passed by value, which supports the picture of a reference copied into a slot. The primitive-versus-reference split comes from earlier course modules; I did not separately verify it against the JLS.
  • Frame and heap layout are JVM implementation details; the diagrams here are a teaching model. I compiled and ran the three examples with JDK 21 and copied their output.

Practice

Apply this lesson with the module's lab, exercises and quiz.

Lab and exercises Quiz

Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.

Continue to next lesson

Type to search levels, phases, modules and lessons.