Skip to content
CodeKruPro

Your language · Java

Memory and the runtime

How the runtime stores objects, garbage collection, and copies versus references.

Learn: Memory and the Java runtime9 minute read

The runtime is everything that happens after you press Run: how Java code becomes something a machine can execute, where variables and objects are stored, and how memory is freed. Interviewers ask about it to check that you understand what your code really does. The answers also help you read errors such as OutOfMemoryError in a long CI run (a run on a server that tests your code automatically), or explain why changing a list in one place changed it somewhere else.

Step 1: JDK, JRE and JVM

javac compiles .java files into .class files that hold bytecode, a set of instructions for a virtual machine rather than for a real processor. The JVM (Java Virtual Machine) loads and runs that bytecode. It also compiles often-used code to machine code while the program runs (the JIT compiler) and frees memory with a garbage collector. The JRE is the JVM plus the standard library, enough to run programs, and the JDK adds tools such as javac and jar. Build machines and CI agents (the servers that build your project and run its tests) need a JDK.

The same bytecode runs on any JVM, so a JAR (a Java program packed into one file) built on Windows runs on a Linux CI agent. The bytecode is portable, and the JVM is built per platform.

Step 2: the stack and the heap

A thread is one path of running code; a program can run several at the same time (see Threads). Each thread has its own stack, a block of memory for method calls. Every method call adds a frame that holds the call's parameters and local variables, and the frame is removed when the method returns. A local primitive such as int count stores its value in the frame. A local object variable stores only a reference, the address of the object. All objects and arrays live on the heap, which every thread shares.

A main() frame on the thread stack holds count = 3 and the references a, b and c; a and b point to the same ArrayList on the heap, c points to a separate copy, and one ArrayList with no references is ready for garbage collection.
Variables hold references; the objects live on the shared heap.
import java.util.*;

public class Main {
    static int depth = 0;
    static void dive() { depth++; dive(); }

    public static void main(String[] args) {
        List<String> a = new ArrayList<>();
        List<String> b = a;                 // two references, one heap object
        b.add("shared");
        System.out.println(a + " same object: " + (a == b));

        List<String> c = new ArrayList<>(a); // a copy: a new heap object
        c.add("only in c");
        System.out.println(a + " " + c);

        try {
            dive();
        } catch (StackOverflowError e) {
            System.out.println("stack overflow after many calls: " + (depth > 1000));
        }
        System.out.println("is an Exception: " + Exception.class.isAssignableFrom(StackOverflowError.class));
    }
}
[shared] same object: true
[shared] [shared, only in c]
stack overflow after many calls: true
is an Exception: false

Endless recursion fills a thread's stack and throws StackOverflowError. Keeping too many objects reachable fills the heap and throws OutOfMemoryError. Both are Errors, not Exceptions (see Exceptions). The maximum heap is set with -Xmx; without it, the JVM takes a quarter of the memory it can see. Class metadata (information about each loaded class) lives in Metaspace (since Java 8), and the string pool is in the heap (since Java 7).

Step 3: garbage collection

The garbage collector starts from GC roots, such as local variables of running methods and static fields, and marks every object it can reach. Everything else is garbage, even objects that only point at each other. Most collectors are generational: new objects are checked often because most die young, and survivors move to an old area that is checked less often. On a normal machine the JVM picks the G1 collector. System.gc() is only a request.

An object can be collected once the last reference to it is gone: the variable is set to null, reassigned, or its method returns. Memory leaks in test frameworks are almost always references kept by mistake, such as a static list that collects every page or drivers (browser controllers) that are never quit.

Step 4: reflection and copies

Reflection lets code inspect and use classes while the program runs: list methods, read annotations (markers such as @Test) and call methods by name. TestNG and JUnit (Java test frameworks) use it to find and call methods marked @Test, and Selenium's PageFactory (a helper that sets up page elements) uses it to fill @FindBy fields. It is slower than a direct call and its errors appear only at run time, so everyday test code rarely needs it.

Assigning an object variable copies the reference, never the object. To get an independent list, copy it with new ArrayList<>(list) or List.copyOf(list). These are shallow copies: the list is new, but the elements inside are still the same objects, shared with the original list.

How to answer in an interview

For "heap or stack?", describe one line of code: "In User u = new User(); the variable u is in the method's stack frame and holds a reference. The User object is on the heap. When the method returns, the frame goes away, and the object is collected later if nothing else refers to it." For JDK versus JRE versus JVM, describe them as nested layers: JVM, then JVM plus libraries, then everything plus tools.

Common mistakes

  • Saying objects are stored on the stack.
  • Thinking an object is freed the moment its method returns.
  • Calling OutOfMemoryError an exception you should catch.
  • Expecting System.gc() to free memory at once.
  • Thinking b = a copies a list.

Recap

javac makes bytecode and the JVM runs it anywhere. Each thread's stack holds frames with local values and references, and the shared heap holds every object. The garbage collector frees what nothing can reach. Too-deep recursion overflows the stack, and too many reachable objects fill the heap.

8 questions0/8 solved

StatusQuestionDifficultyAsked in
Not startedStack or heap?EasyAsked in: Capgemini, TCS, Wipro
Not startedJDK vs JRE vs JVM; why Java is platform independentEasy-MediumAsked in: Infosys, Wipro
Pro+Heap versus stack memoryMediumAsked in: Capgemini, TCS, Wipro
Pro+How does garbage collection work?MediumAsked in: Accenture, TCS
Pro+When can an object be garbage collected?MediumAsked in: Accenture, TCS
Pro+StackOverflowError or OutOfMemoryError?Easy-MediumAsked in: Capgemini, TCS, Wipro
Pro+What is reflection and where would a framework use it?MediumAsked in: TCS
Pro+Facts about JVM memory areasMediumAsked in: Capgemini, Infosys, TCS +1
Not startedNot startedIn progressIn progressSolvedSolvedPro+Pro+

The first 2 questions of each topic are free. Pro+ opens every question.

Back to the plan