Java Heap Memory

In the last lesson you learned the JVM architecture. The JVM splits its memory into areas. The biggest one is the heap, and that is where almost every object you make ends up.

🤔 Where do your objects actually go?

You write new Student() and a student appears. But where does it live? Here is the simple answer:

  • Every object you create with new goes on the heap.
  • The heap is the runtime area where all objects and their instance fields live.
  • Think of it as a large storage room. A String, an array, a Student, a List — if you typed new, it is on the heap.
  • The variable that names the object does not live there. The object’s data does.

📦 The heap holds everything made with new

This program makes one object and reads a field from it. The object sits on the heap the whole time.

public class Main {
static class Student {
String name = "Alex"; // an instance field, stored inside the object
}
public static void main(String[] args) {
Student s = new Student(); // the Student object goes on the HEAP
System.out.println(s.name); // read the field from the heap object
}
}

Here is what happens:

  • new Student() builds a Student object and places it on the heap.
  • The object carries its own fields. The field name holds "Alex" inside the object on the heap.
  • The variable s lives on the stack, inside main’s frame, not on the heap.
  • s holds a reference, a small value that points to where the object sits.

So the object and its variable are in two places. The object is on the heap. The reference is on the stack.

Output

Alex

🔗 References point to heap objects

A reference is like a home address on a slip of paper. The slip is small and fits in your pocket. The house it points to is large and stays put. The slip is the reference; the house is the object.

The heap is shared, so many references can point at the same object. This program shows two references pointing to one array.

public class Main {
public static void main(String[] args) {
int[] first = {10, 20, 30}; // one array object on the HEAP
int[] second = first; // copy the reference, not the array
System.out.println(first == second); // do they point to the same object?
}
}

Here is what each line does:

  • int[] first = {10, 20, 30} builds one array on the heap and stores its reference in first.
  • int[] second = first copies the reference, not the array. Both variables now hold the same address.
  • first == second compares the two addresses. They match, so the result is true.
  • One array, two slips of paper carrying the same address.

Output

true

Java copied the reference, not the object. That is the foundation of the next idea.

👥 Aliasing: two names for one object

When two references point to the same object, they are aliases — two names for one thing. Change the object through one alias and the other sees it too. There is only one object.

This program changes a slot through one reference and reads it through the other.

public class Main {
public static void main(String[] args) {
int[] scores = {1, 2, 3};
int[] alias = scores; // alias and scores point to the same array
alias[0] = 99; // change the array through alias...
System.out.println(scores[0]); // ...and scores sees the change
scores[1] = 77; // now change through scores...
System.out.println(alias[1]); // ...and alias sees it too
}
}

Let’s trace it:

  • int[] scores = {1, 2, 3} makes one array; scores references it.
  • int[] alias = scores copies the reference, so both names point to the one array.
  • alias[0] = 99 changes the first slot, and scores[0] reads it back as 99.
  • scores[1] = 77 changes the second slot, and alias[1] reads it back as 77.

Output

99
77

This is the direct result of references sharing one heap object. Once aliasing clicks, a lot of confusing bugs make sense — two variables you thought were separate were one object all along.

🧵 The heap is shared by all threads

The heap belongs to the whole program. Here is the rule:

  • Any thread with a reference can reach any object on the heap.
  • The stack is different — each thread gets its own private stack.
  • Sharing is what lets two threads work on the same data.
  • It is also why two threads changing one object at the same time can cause bugs.

Remember: objects on the heap are shared, references are how each thread reaches them. This program makes one object in the main thread and lets a second thread change it.

public class Main {
static class Counter {
int value = 0; // instance field on the heap object
}
public static void main(String[] args) throws InterruptedException {
Counter shared = new Counter(); // one Counter on the HEAP
Thread worker = new Thread(() -> {
shared.value = 5; // the new thread changes the heap object
});
worker.start();
worker.join(); // wait for the worker to finish
System.out.println(shared.value); // main thread reads the same object
}
}

What is going on:

  • new Counter() makes one object on the heap; the main thread holds it in shared.
  • The worker thread gets the same reference, so it points at the very same object.
  • The worker sets shared.value = 5 on that shared object.
  • join() waits for the worker, then the main thread reads the same object and sees 5.

Output

5

♻️ Object lifecycle and garbage collection

You never free memory by hand in Java. The JVM does it. Here is how:

  • An object stays alive as long as something still refers to it.
  • When nothing can reach it, it becomes eligible for garbage collection.
  • Garbage collection is the JVM freeing the memory of objects you no longer use.
  • Eligible does not mean freed now. The JVM cleans it up at some later time it decides.

This program drops the only reference to an object, making it eligible.

public class Main {
public static void main(String[] args) {
String message = new String("hello"); // object on the HEAP, reachable
System.out.println(message);
message = null; // drop the only reference; the object is now unreachable
// the "hello" object is now eligible for garbage collection
}
}

Let’s read it:

  • new String("hello") makes a String on the heap; message references it, so it is reachable.
  • We print it, which works because it is still alive.
  • message = null clears the only reference. Nothing can reach the object now.
  • From here the object is eligible for collection, and the JVM may free it at any later time.

Output

hello

This is a quick taste. Garbage collection has its own full lesson. The rule: an object lives while it is reachable, and becomes eligible when it is not.

🌱 How the heap is divided inside

The heap is not one flat block. The JVM splits it by how long objects tend to live, which lets the garbage collector work much faster:

  • Most objects die young — a method makes one, uses it, drops it.
  • A few objects live a long time.
  • So the JVM groups new objects together and old objects together, and cleans the young group often.

These are the main parts of the heap:

Area What it holds
Young generation: Eden Brand new objects are created here first
Young generation: Survivor Young objects that lived through one cleanup move here
Old generation Objects that have survived many cleanups, the long-lived ones

The flow: a new object starts in Eden, moves to a Survivor area if it lives through a cleanup, and gets promoted to the Old generation if it keeps surviving. The garbage collection lesson goes deep into how each area is cleaned.

💥 When the heap fills: OutOfMemoryError

The heap is large but not endless. If you keep making objects and holding onto them, the heap fills up, and the JVM throws an OutOfMemoryError when there is no room for a new object.

This program keeps adding arrays to a list forever. The list holds every one, so none can be collected. The heap fills and the program crashes.

import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<int[]> hoard = new ArrayList<>();
while (true) {
hoard.add(new int[1_000_000]); // keep adding big arrays, never let go
}
}
}

Why does this run out:

  • Each loop makes a big array on the heap and adds it to hoard.
  • The list keeps a reference to every array, so every one stays reachable.
  • Nothing is eligible for collection, so the heap fills with arrays that can never be freed.
  • With no room left, the JVM gives up.

Output

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at Main.main(Main.java:9)

The message points right at the trouble: Java heap space. The heap ran out of room, usually because something held onto too many objects.

⚠️ Common Mistakes

Thinking the variable holds the object. It only holds a reference; the object is on the heap.

Student s = new Student();
// ❌ wrong: "the whole Student is stored in s"
// ✅ right: s holds a reference; the Student object lives on the heap

Expecting two references to be independent copies. They share one object, so a change through one shows through the other.

int[] a = {1, 2, 3};
int[] b = a;
b[0] = 99;
// ❌ wrong: "a is untouched because b is a separate copy"
// ✅ right: a and b share one array, so a[0] is now 99

Thinking an object is freed the instant you stop using it. Setting a reference to null only makes it eligible; the JVM frees it later.

❌ wrong: "message = null deletes the object right away"
✅ right: it becomes eligible; the garbage collector frees it at some later time

✅ Best Practices

Habits that keep heap memory clear in your code:

  • Think in references. Assigning one object variable to another copies the address, not the object.
  • Expect shared changes. Two references to one object see each other’s changes.
  • Let go of objects you no longer need, so the garbage collector can free their space.
  • Watch collections that only grow. A list or map you never clear is the top cause of OutOfMemoryError.
  • Read the error name first. OutOfMemoryError: Java heap space points you at the heap and objects held too long.

🧩 What You’ve Learned

Nicely done. Let’s recap Java heap memory.

  • ✅ The heap is the runtime area where all objects created with new, and their instance fields, live.
  • ✅ A variable holds a reference that points to its object on the heap. The object’s data is on the heap, not in the variable.
  • ✅ The heap is shared by all threads, so any thread with a reference can reach the same object.
  • ✅ Two references to one object are aliases. A change through one is seen through the other, because there is only one object.
  • ✅ An object lives while it is reachable, and becomes eligible for garbage collection when nothing refers to it.
  • ✅ The heap is split into Young (Eden plus Survivor) and Old generations, and a full heap throws OutOfMemoryError.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    Where are objects created with new stored?

    Why: Every object made with new lives on the heap; the variable only holds a reference to it.

  2. 2

    Two references point to the same array. You change a slot through one. What does the other see?

    Why: The two references are aliases for one heap object, so a change through one is visible through the other.

  3. 3

    When does an object become eligible for garbage collection?

    Why: An object is eligible once it is unreachable; the JVM then frees its heap space at some later time.

  4. 4

    What error happens when the heap fills up with objects that cannot be freed?

    Why: A full heap throws OutOfMemoryError: Java heap space, usually because something holds too many live objects.

🚀 What’s Next?

We kept saying that references and local variables live on the stack, not the heap. The stack is the other key memory area, and it explains how method calls and local variables work. Let’s look at it next.

Java Stack Memory

Share & Connect