Last Updated on September 5, 2024
The Java Virtual Machine (JVM) manages memory allocation and garbage collection in Java applications. It divides memory into several areas, each with a specific purpose:
- Heap: The largest memory area, where objects are created and stored. The garbage collector periodically frees memory occupied by objects that are no longer in use.
- Stack: Used to store method calls and their local variables. Each method call creates a new stack frame that is pushed onto the stack and popped off when the method returns.
- Method Area: Stores class-level information, such as the class bytecode, static variables, and method definitions.
- Native Method Stack: For native methods, which are methods implemented in languages other than Java.
The JVM’s memory management system is designed to be efficient and automatic. The garbage collector identifies objects that are no longer reachable and reclaims the memory they occupy. This process helps to prevent memory leaks and improve application performance.
Stack
The Java stack memory is crucial component of the Java Virtual Machine (JVM) that plays a vital role in managing method calls and their associated data.
It’s a LIFO (Last-In-First-Out) data structure where stack frames are pushed and popped during the execution of a Java program.
Each method call creates a new stack frame that is pushed onto the stack. This stack frame contains information about the method, such as its parameters, local variables, and return value.
When a method returns, its stack frame is popped off the stack, and the execution resumes in the calling method.
The stack memory is used to store primitive data types (like int, double, boolean) and references to objects.
The actual objects themselves are stored in the heap memory. When a method is called, the parameters and local variables are pushed onto the stack, and when the method returns, the return value is also pushed onto the stack before the frame is popped.
The default stack size varies depending on the JVM implementation and operating system.
If the stack size is too small, you might encounter StackOverflowError
due to insufficient memory for method calls.
public class StackOverflowExample {
public static void main(String[] args) {
recursiveMethod();
}
public static void recursiveMethod() {
recursiveMethod(); // Recursive call without a base case.
}
}
Execution of above program gives following error at some point:
Exception in thread "main" java.lang.StackOverflowError
at linkedlist.StackOverflowExample.recursiveMethod(StackOverflowExample.java:9)
at linkedlist.StackOverflowExample.recursiveMethod(StackOverflowExample.java:9)
at linkedlist.StackOverflowExample.recursiveMethod(StackOverflowExample.java:9)
at linkedlist.StackOverflowExample.recursiveMethod(StackOverflowExample.java:9)
The size of the stack can be configured using JVM options.
java -Xss<size> <class>
Set a stack size of 1 megabyte:
java -Xss1m MyApplication
A larger stack size can be necessary for applications with deep recursion or many nested method calls. We need to take into account the nature of our application, the number of threads, and the memory available on your system when setting the stack size.
Heap
The Java heap memory is a component of the Java Virtual Machine (JVM) that plays a pivotal role in object creation and management.
It’s a large pool of memory where objects are allocated and stored during the execution of a Java program.
The heap is divided into two main sections: the young generation and the old generation.
The young generation is further subdivided into the Eden space, where newly created objects are initially allocated, and the survivor spaces, which temporarily hold objects that have survived multiple garbage collection cycles.
When an object survives several garbage collection cycles, it is promoted to the old generation, where it is expected to remain for a longer period.
The garbage collector is responsible for managing memory within the heap. It periodically scans the heap to identify objects that are no longer reachable by the application. These objects are considered garbage and are reclaimed to free up memory.
The garbage collector uses various algorithms, such as mark-and-sweep and generational garbage collection, to efficiently identify and reclaim garbage.
If the heap size configured for the JVM is too small, it can result in OutOfMemoryError
even for relatively small applications.
public class OutOfMemoryExample {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
while (true) {
list.add(1);
}
}
}
The output of above program is following error, as it fill the heap at some point
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.base/java.util.Arrays.copyOf(Arrays.java:3513)
The size of the heap is typically larger than the stack and can be configured using JVM options.
The heap size in Java can be configured using the following JVM options:
- -Xms: Sets the initial heap size. This is the minimum amount of memory allocated to the heap when the JVM starts.
- -Xmx: Sets the maximum heap size. This is the maximum amount of memory that the heap can grow to.
This command sets the initial heap size to 256 megabytes and the maximum heap size to 512 megabytes for the application MyApplication
.
java -Xms256m -Xmx512m MyApplication
By adjusting the heap size, developers can control the amount of memory available for object allocation.
A larger heap can improve performance for applications that deal with large datasets or create many objects. On the other side, excessive heap size can lead to performance issues and memory consumption problems.
Conclusion
The stack and heap are two fundamental memory areas used by the Java Virtual Machine (JVM) for storing data during program execution.
They have distinct characteristics and purposes:
Stack is primarily used for managing method calls, their parameters, local variables, and return values. It follows a Last-In-First-Out (LIFO) order, meaning the most recently pushed stack frame is the first to be popped.
Heap is Used for storing objects and their instance variables. Objects are allocated dynamically at runtime using the new
keyword, and garbage collection is responsible for reclaiming memory occupied by objects that are no longer in use.
By understanding how memory is allocated, managed, and used, developers can optimize their code to avoid memory leaks, improve performance, and prevent runtime errors like OutOfMemoryError
and StackOverflowError
.