Introduction
Java memory management is handled by the Java Virtual Machine (JVM). The JVM allocates memory for Java objects on the heap, which is a portion of memory that can be dynamically resized during program execution.
The JVM uses a garbage collector to automatically free memory that is no longer being used by any active references in the program. The garbage collector identifies unreferenced objects and removes them from the heap, reclaiming their memory.
Java also provides mechanisms for controlling memory usage, such as the ability to allocate memory off the heap using native memory or direct byte buffers, or to use weak or soft references to prevent objects from being prematurely garbage collected.
public class MyClass {
public static void main(String[] args) {
// Allocate memory for a new object on the heap
MyClass obj = new MyClass();
// Use the object
// Set obj to null to indicate that it is no longer needed
obj = null;
// The garbage collector will eventually reclaim the memory used by obj
}
}
In this example, the JVM allocates memory for a new instance of the MyClass
class using the new
keyword. The obj
reference variable holds a reference to the new object.
Once the object is no longer needed, the obj
reference is set to null
to indicate that it can be garbage collected. The JVM's garbage collector will eventually determine that the object is no longer being used and free the memory that was allocated for it.
In this article, we will examine the way Java handles memory management and explore the different parts of the memory management system. Moreover, we will also discuss how Garbage Collection works in Java.
How does Java handle memory management?
Memory management is the process of allocating and freeing memory as needed by an application. The goal of memory management is to provide enough memory to the application while avoiding memory-related problems such as fragmentation, leaks, and out-of-memory errors. Java handles memory management by automatically managing the allocation and deallocation of memory for objects and variables. This is achieved through the use of a combination of the Java heap, the stack, and the method area.
Let us now understand each of these methods separately.
Java Heap
The Java heap is a region of memory that is used by the Java virtual machine (JVM) to store objects at runtime. The heap is the place where objects are stored when they are created dynamically at runtime.
In Java, objects are dynamically allocated on the heap using the new operator. Once an object is created and stored on the heap, it stays there until it is no longer referenced by any part of the program. At that point, the object becomes eligible for garbage collection, which is a process by which the JVM frees up memory occupied by unreferenced objects.
Here is an example of allocating an object on the heap in Java:
public class Main {
public static void main(String[] args) {
String str = new String("Hello, World!");
System.out.println(str);
}
}
In the example, a String object is created using the new operator and is stored on the heap. The reference to the object is stored in the str variable, which is a local variable on the stack. The str
variable acts as a pointer to the String object stored on the heap.
It's important to note that the heap size is limited by the amount of memory available on the machine and the heap size can be set using the -Xmx
and -Xms
JVM options. If the heap size is not set, the JVM will dynamically allocate the heap size based on the amount of memory available on the machine. If the heap size is too small, the JVM may throw an OutOfMemoryError, indicating that the heap has run out of memory. To avoid this, you can increase the heap size by setting the -Xmx JVM option to a larger value.
The Stack
The stack is a region of memory used by the Java virtual machine (JVM) to store method invocations and their local variables. Unlike the heap, which is used to store objects that have a longer lifespan, the stack is used to store temporary data that is created and destroyed as the program executes.
Each time a method is called, a new "frame" is pushed onto the top of the stack. This frame contains the arguments passed to the method, as well as any local variables defined within the method. When the method returns, its frame is popped off the stack, and the previous frame becomes the current frame.
The stack is useful for memory management because it provides a way for the JVM to automatically manage memory for temporary data. When a method is called, the JVM knows exactly how much memory it needs for its arguments and local variables, so it can allocate that memory on the stack. When the method returns, the memory for its arguments and local variables is automatically released, without the need for manual memory management.
Here is an example of how the stack is used in Java:
public class Main {
public static void main(String[] args) {
int x = 10;
int y = 20;
int z = add(x, y);
System.out.println(z);
}
public static int add(int a, int b) {
int c = a + b;
return c;
}
}
In the example, the main method calls the add method, which adds two integers and returns the result. When the add method is called, a new frame is pushed onto the stack, which contains the arguments a and b. The frame also contains the local variable c, which is used to store the result of the addition. When the add method returns, its frame is popped off the stack, and the memory for a, b, and c is automatically released.
The Method Area
The Method Area is a region of memory in the Java virtual machine (JVM) that is used to store class information, including class definitions, constant pool data, and metadata. The Method Area is shared among all threads running within the JVM, and is created at the start of the JVM's execution.
The Method Area is used to manage memory in Java by storing class information in a centralized location, where it can be easily accessed by multiple threads. This information includes the names and types of variables, methods, and other elements of a class, as well as the class hierarchy and relationships between classes. By storing this information in the Method Area, the JVM can avoid the need to repeatedly load class information into memory for each new instance of a class.
Here is an example of how the Method Area is used in Java:
class Animal {
int age;
String name;
void setAge(int a) {
age = a;
}
void setName(String n) {
name = n;
}
}
public class Main {
public static void main(String[] args) {
Animal cat = new Animal();
cat.setAge(3);
cat.setName("Tom");
}
}
In the example, the Animal class is stored in the Method Area. This information is used to create an instance of the Animal class, which is stored on the heap. When the setAge and setName methods are called, they use the information stored in the Method Area to access and update the age and name fields of the Animal object. This allows the JVM to manage memory efficiently, by storing class information in a centralized location, where it can be easily accessed by multiple threads.
How Garbage Collection works in Java?
Garbage Collection is a process in the Java virtual machine (JVM) that automatically frees up memory that is no longer being used by the program. The goal of Garbage Collection is to ensure that the JVM's memory is used efficiently and that resources are not leaked.
In Java, objects are dynamically allocated on the heap. When an object is no longer being used by the program, it becomes eligible for garbage collection. The JVM periodically runs the Garbage Collector, which frees up memory that is no longer reachable by the program.
Here is an example of how Garbage Collection works in Java:
public class Main {
public static void main(String[] args) {
Animal cat = new Animal();
cat.setAge(3);
cat.setName("Tom");
cat = null;
System.gc();
}
}
class Animal {
int age;
String name;
void setAge(int a) {
age = a;
}
void setName(String n) {
name = n;
}
}
In the example, an instance of the Animal class is created and assigned to the cat variable. The cat variable is then set to null, which makes the Animal object eligible for garbage collection. The JVM periodically runs the Garbage Collector, which frees up memory that is no longer reachable by the program. In this example, the System.gc
method is used to manually invoke the Garbage Collector.
By automatically freeing up memory that is no longer being used, Garbage Collection helps to ensure that the JVM's memory is used efficiently and that resources are not leaked. This allows Java programs to run more smoothly and reduces the need for manual memory management.
Let's take another example:
public class GarbageCollectionExample {
public static void main(String[] args) {
// Allocate memory for a new object on the heap
Integer obj = new Integer(42);
// Use the object
// Set obj to null to indicate that it is no longer needed
obj = null;
// Request garbage collection
System.gc();
// Sleep to give the garbage collector time to do its work
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("End of program");
}
@Override
protected void finalize() throws Throwable {
System.out.println("Garbage collection called");
}
}
In this example, we allocate memory for a new Integer
object and set its value to 42. We then set the obj
reference to null
to indicate that the object is no longer needed.
Next, we call the System.gc()
method to request that the garbage collector run. This method is not guaranteed to result in a garbage collection pass, but it increases the likelihood that one will occur.
We then sleep the program for one second to give the garbage collector time to run.
Finally, we print "End of program" to the console. When the program exits, the finalize()
method of the GarbageCollectionExample
class is called, which prints "Garbage collection called" to the console if the object was garbage collected.
If the object was not garbage collected, nothing will be printed to the console.
Summary
Java's memory management system is designed to make it easy for developers to manage their application's memory usage effectively. Here are some of the key components involved in Java memory management:
- Heap: The heap is a portion of memory where objects are allocated. It is managed by the JVM, which dynamically resizes it as needed.
- Garbage Collector: The garbage collector is responsible for automatically freeing memory that is no longer being used by any active references in the program. It identifies unreferenced objects and removes them from the heap, reclaiming their memory.
- Stack: The stack is a portion of memory where local variables and method calls are stored. It is managed by the JVM, which automatically allocates and deallocates memory as methods are called and returned.
- PermGen/Metaspace: The PermGen (Permanent Generation) or Metaspace is a portion of memory where class definitions and metadata are stored. In older versions of Java, this was a fixed-size memory space in the JVM, but in newer versions, it has been replaced by the Metaspace, which is dynamically sized.
- Direct Memory: Direct memory is memory allocated outside of the heap, using the
ByteBuffer.allocateDirect()
method. This memory is managed by the JVM, but it is not subject to garbage collection.
These components work together to manage the memory used by Java programs, ensuring that memory is allocated efficiently and freed when no longer needed.
Further Reading