Edit Page

Memory Management: Stack and Heap

Introduction

In order to understand how variables are allocated in Java and how the JVM manages the main memory, we need to understand Java’s data types and how the memory model plays into that.

Primitive data types vs non-primitive data types

Java has primitive or value data types and non-primitive or reference data types. Primitive/value data types are a set of basic data types that are predefined by the Java language. There are eight primitive data types in Java: byte, short, int, long, float, double, boolean and char. For example, the variable x in int x = 20; is considered a primitive/value data type. In memory, the actual “value” of any variable of a primitive data type is the actual value for primitive types (20 in the previous example).

Non-primitive or reference data types contain the address or reference of dynamically created objects (e.g., clsses, interfaces, enums, arrays and immutable strings).

For example, the variable c in Car c = new Car(); is considered a non-primitive/reference data type. In memory, the actual “value” of any variable of a non-primitive/reference data type is the the reference for the object. In Java, the new keyword is used to instantiate an instance of a class by allocating memory for it and returning a reference to that memory location.

Memory Allocations

Before discussing the Java memory model, we need to discuss two different memory allocations: stack and heap memory. Both the stack and heap are memory areas stored in the RAM (Random Access Memory) and allocated by the operating system (OS). The heap size is determined by the OS but can grow as needed.

Stack

The stack is a region in memory where data are added and removed in a last-in-first-out (LIFO) order. When a function is executed, all local variables in the current active function are stored in the stack. The stack is considered fast with an efficient and easy access pattern to allocate and deallocate memory space. The stack, however, is a limited contiguous size memory determined when the program starts. Thus, a program may exceed the stack bound by allocating very large space for local variables or have an infinite deep recursion, which often results in a stack overflow condition and unrecoverable crash of the program. Each program routine or method has its own stack area.

Heap

The heap is another region in memory of variable size for allocating data. Unlike stack memory, heap memory is allocated explicitly by programmers (using the new keyword in Java) and the process of deallocating memory or marking it as free is handled by the garbage collector.

When we use the new keyword in a method, the reference (an integer) is created in the stack, but the object itself and all of its content (primitive and non-primitive/reference types) are created in the heap. In Java, objects may contain references to other objects, so the object’s content may in fact hold references to other nested objects.

Let’s take a look at this example that illustrated the use of stack and heap memory:

1
2
3
4
5
6
public void printCourseInfo(){
    Course c;
    c = new Course("CPIT", 252);
    System.out.println(c);
    c = null;
}
line 1
The highlighted line allocates a local reference variable in the stack. Before using new, c is uninitialized and has a value of null.
line 2
The use of new allocates an object in the heap. In this example, the program stores the reference or address to the new Course object, which is on the heap, in a local variable c on the stack.
line 3
Setting the value for the reference to null will make it available for garbage collection, which will automatically deallocate it when it is no longer in use or when the function exists.

How reference data types are stored in memory?

In Java, all objects are dynamically allocated on heap. When we declare a variable of non-primitive type (e.g., a variable of a class type as in Course c), only a reference is created on the stack and no memory is allocated for it yet. To allocate a memory to the object, we use the new keyword, which will allocate memory for it on the heap. Unlike C and C++, Java does not require the manual destruction of variables allocated on the heap.

Is Java Pass-by-value or pass-by-reference?

Pass by value: means we make a copy of the parameter’s value or content.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public static void main(String[] args) {
    int courseNumber = 252;
    System.out.println(courseNumber); // prints "252"
    setCourseNumber(courseNumber);
    System.out.println(courseNumber); // prints "252"
}

public static void setCourseNumber(int courseNumber){
    courseNumber = 405;
}

Pass by reference means we make a copy of the address (or reference) to the parameter rather than the value or content itself.

Java is NOT a pass-by-reference language. Instead, “Java passes the reference by value.” When a variable is passed to a method, the value of that variable on the stack is copied into a new variable inside the method being called. As previously mentioned, for primitive data types, the value of that variable on the stack is the value itself. For non-primitive data types, the reference or address is stored in the stack which points to a location on the heap. When a variable is passed to a method, the value of the variable on the stack is copied into a new variable inside the new method.

Example: Consider the following Java class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class Course{
  private String name;
  private int number;
  public Course(String name, int number){
    this.name = name;
    this.number = number;
  }
  public String getName(){return this.name;}
  public void setName(String name){this.name = name;}
  public int getNumber(){return this.number;}
  public void setNumber(int number){this.number = number;}
  @Override
  public String toString(){
    return this.name + " " + this.number;
  }
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class Main{
    public static void changeCourse(Course c) {
        c.setNumber(490);
    }
    public static void main(String[] args) {
        Course c1 = new Course("CPIT", 252);
        System.out.println(c1); // prints "CPIT 252"
        Course c2 = c1;
        c2.setNumber(405);
        System.out.println(c1); // prints "CPIT 405"
        System.out.println(c2); // prints "CPIT 405"
        changeCourse(c1);
        System.out.println(c1); // prints "CPIT 490"
        System.out.println(c2); // prints "CPIT 490"

    }
}
CPIT 252
CPIT 405
CPIT 405
CPIT 490
CPIT 490

Java memory