java-memoria-stack-heap-valor-referencia

Memory in Java: stack, heap, values and references

  • 4 min

The stack and heap are two memory areas with distinct functions during the execution of a Java program.

So far, we have created variables like int a = 5 or String s = "Hello". We have assumed Java “stores” that data in RAM and that’s it.

But where exactly does it store them? And how?

If you modify an object through one variable and see the change from another, both variables likely contain references to the same object.

Let’s open the black box of memory in Java with a simplified model that is useful for reasoning about variables, objects, and method calls.

The two memory areas: stack and heap

The JVM uses several memory areas. To start, we’ll focus on two: the stack for each thread, which manages method calls, and the shared heap, where objects are normally placed.

This is the immediate execution memory area.

  • Orderly: It works like a stack of plates (LIFO: Last In, First Out).
  • Tied to calls: Each invocation creates a frame with its local state.
  • Ephemeral: When a method finishes, its frame is no longer available.
  • Small: It has a limited size (if you overdo it, the famous StackOverflowError occurs).

Conceptually, the local state of calls is stored here. The JVM can optimize some values and move them to registers, so this model shouldn’t be interpreted as an exact physical map.

This is the shared area where objects and arrays are normally created.

  • Dynamic: Objects are created here and remain until no one uses them.
  • Managed: This is where the Garbage Collector works to clean up what is no longer needed.

As a mental model, objects created with new live in the heap. The JVM can apply internal optimizations as long as the observable behavior of the program doesn’t change.

Value types vs reference types

Here comes the key concept. Depending on the data type, Java stores it differently.

Primitive types (by value)

The 8 primitive types we saw (int, double, boolean, char…) are Value Types.

  • Behavior: The variable contains the primitive value. If it’s a local variable, it’s part of its frame; if it’s a field, it’s part of the object that contains it.

If you have int a = 10, in the Stack memory there is a little box named a that contains the number 10.

int a = 10;
int b = a; // A PHOTOCOPY of the value 10 is made.
b = 20;    // We change the copy.

System.out.println(a); // Prints 10. The original does NOT change.
Copied!

This is intuitive. When copying, we clone the data.

Object types (by reference)

Anything that is not a primitive (Classes, Arrays, Strings, Scanner) is a Reference Type.

  • The object normally lives in: The heap.
  • The variable contains: A reference. A local variable is part of the frame; a reference field is part of its object.

The variable does not contain the object, but a reference that allows locating it. Java does not expose this reference as a manipulable memory address.

Let’s see the dangerous code:

// Imagine a Point class with x, y
Point p1 = new Point(0, 0);
Point p2 = p1; // WATCH OUT! We copy the REFERENCE (the remote), not the object.

p2.x = 50; // We use remote 2 to change the TV channel.

System.out.println(p1.x); // Prints 50. p1 has changed!
Copied!

Since p1 and p2 point to the same object in the Heap, whatever you do with one affects the other. This is called Aliasing.

The null value

Now that we understand references, understanding null is easy. null means you have a remote control (a variable in the Stack) that points to no TV (no object in the Heap).

If you try to press a button on that remote (variable.method()), Java can’t find the TV and it crashes: NullPointerException.

Parameter passing: by value or reference?

This is a typical technical interview question. In Java, parameter passing is ALWAYS by value.

  • If you pass an int, a copy of the number is passed.
  • If you pass an Object, a copy of the reference (a copy of the remote control) is passed.
public void change(int n, Point p) {
    n = 99;      // Modifies the local copy of n. The original n does not change.
    p.x = 99;    // Modifies the object pointed to by the remote p. The original object DOES change.
    p = null;    // Breaks my local remote. The original remote still points to the object.
}
Copied!

It’s subtle, but important: you can modify the contents of the object passed to you, but you cannot make the original variable point to a different object.