Under the hood: primitives, objects, and memory in Java
Why does changing one Java variable sometimes affect another? Explore primitive values, shared objects, and copied references—and build a clearer mental model of how Java handles memory.
So far, we have worked with two broad kinds of data in Java: primitive values and objects. They can look similar when we assign them to variables, but Java handles them differently under the hood.
Understanding that difference explains why changing one variable sometimes affects another, why objects can be null, and how Java decides when memory can be reclaimed.
Primitive variables contain values
Java has eight primitive types: byte, short, int, long, float, double, char, and boolean. A variable of one of these types holds its value directly.
int first = 42;
int second = first;
second = 99;
Assigning first to second copies the value 42. The two variables are independent, so changing second does not change first.
Object variables contain references
An object variable does not contain the object itself. It contains a reference: a value Java uses to locate that object. The object is typically allocated in an area of memory called the heap.
Person first = new Person("Ada");
Person second = first;
second.setName("Grace");
Here, new Person("Ada") creates one object. Assigning first to second copies the reference, not the object. Both variables now refer to the same Person. A change made through second is therefore visible through first.
A reference can also contain null, which means that it does not currently point to an object. Trying to use a member through a null reference causes a NullPointerException.
Stack and heap
When a method runs, the JVM creates a stack frame for that method call. The frame keeps track of information such as local variables and intermediate calculations. When the method returns, its frame is removed.
Objects, meanwhile, are generally allocated on the heap. The heap lives longer than an individual method call, which allows an object to remain available as long as something can still reach it.
The familiar shortcut—”primitives live on the stack and objects live on the heap”—is useful at first, but it is not the complete picture. A primitive can be stored as a field inside a heap object, and an object reference can be stored in a local variable, an object field, or an array. The JVM may also optimize the physical representation. The important semantic distinction is simpler: a primitive variable represents a value, while an object variable represents a reference.
What happens when a method is called?
Java always passes arguments by value. For a primitive, the copied value is the primitive itself. For an object, the copied value is the reference.
This is why a method can use its copied reference to mutate the same object, but assigning a different object to that parameter does not replace the caller’s variable. Java passes a reference by value; it does not pass variables by reference.
When is an object removed?
Java manages heap memory with a garbage collector. When an object can no longer be reached from any live part of the program, it becomes eligible for collection. The JVM decides when to reclaim that memory; setting a variable to null does not immediately delete an object.
The mental model to keep
- A primitive variable holds a value.
- An object variable holds a reference to an object.
- Copying a primitive copies its value.
- Copying an object variable copies its reference, so both variables can point to the same object.
- Java passes every argument by value—including object references.
- Unreachable objects become eligible for garbage collection.
That model is enough to reason about most everyday Java code without relying on an oversimplified picture of memory.