The Java Virtual Machine defines various run-time data areas that are used during execution of a program. Some of these data areas are created on Java Virtual Machine start-up and are destroyed only when the Java Virtual Machine exits. Other data areas are per thread. Per-thread data areas are created when a thread is created and destroyed when the thread exits. The two main area are Heap and Stack area.
- Heap ->The heap is the run-time data area from which memory for all class instances and arrays are allocated. Whenever we create any object, it’s always created in the Heap space. Garbage Collection runs on the heap memory to free the memory used by objects that doesn’t have any reference. Any object created in the heap space has global access and can be referenced from anywhere of the application (global access). Heap memory lives for the entire duration of the application execution.
-
Stack->Each thread has a private Java Virtual Machine stack. A Java Virtual Machine stack stores frames which holds local variables and partial results and plays a part in method invocation and return. They contain method specific values that are short-lived and references to other objects in the heap that are getting referred from the method. Stack memory is always referenced in LIFO (Last-In-First-Out) order (figure n.1). Whenever a method is invoked, a new block is created in the stack memory for the method to hold local primitive values and reference to other objects in the method. As soon as method ends, the block becomes unused and become available for next method. Stack memory size is very less compared to Heap memory and because of simplicity in memory allocation (LIFO), stack memory is very fast when compared to heap memory. Please note that:
In the following code, numberObj and memoryTest are created on the heap while in the stack there are the references to them.
package education.jtrainer;
public class MemoryTest1 {
public static void main(String[] args) {
int number=10; // this is created in the Stack
Integer numberObj=new Integer(10); // this is created in the Heap, numberObj reference is kept in the stack
MemoryTest1 memoryTest = new MemoryTest1(); // this is created in the Heap, mem reference is kept in the stack
memoryTest.print(numberObj); // a block in the top of the stack is created to be used by print(numberObj) method. Since Java is pass by value, a new reference to Object is created in the print(numberObj) stack block
// at this point stack memory block allocated for print(numberObj) in stack becomes free.
// at this point stack memory block created for main() method is destroyed. Also the program ends at this line, hence Java Runtime frees all the memory and end the execution of the program.
}
private void print(Integer param) { //param is a reference to an object on the heap
System.out.println(param);
}
}
The picture below shows how the memory is allocated.
Figure 1: stack memory vs heap memory
Heap memory is divided into Young-Generation, Old-Generation or Tenured Generation, and Permanent Generation.
The Young Generation is where all new objects are allocated and aged. It includes the Eden zone and two Survivor Memory spaces S1 and S2
The Old Generation is used to store long surviving objects. Typically, a threshold is set for young generation object and when that age is met, the object gets moved to the old generation.
The Permanent generation contains metadata required by the JVM to describe the classes and methods used in the application. The permanent generation is populated by the JVM at runtime based on classes in use by the application. In addition, Java SE library classes and methods may be stored here. Classes may get collected (unloaded) if the JVM finds they are no longer needed and space may be needed for other classes.
Figure 2: heap memory areas
Why Java creators decided to split memory among 5 regions?
JRE has a process, called Garbage Collector (GC) running in the background that looks into all the objects in the memory and performs a marking process where garbage collector identifies which objects are in use (a reference to the object still exists) and which ones are not in use (a reference to the object do not exists anymore). This is not efficient because most of the newly created objects will become unused very soon and objects that are in-use for multiple garbage collection cycle are most likely to be in-use for future cycles too. This is the reason that Java Garbage Collection process is generational and we have Young Generation and Old Generation spaces in the heap memory. Java Garbage Collection can be minor (Minor GC) when Collecting garbage from Young space (consisting of Eden and Survivor spaces). Major GC is when cleaning the Old Generation. Full GC is when cleaning the entire Heap – both Young and Old Generation spaces.
Objects that are survived after many cycles of GC, are moved to the Old generation memory space. Usually it’s done by setting a threshold for the age of the young generation objects before they become eligible to promote to Old generation.
Let’s look at this process with the following java code, as usual the comments make the code self-explained
package education.jtrainer;
import java.util.ArrayList;
public class MemoryTest2 {
ArrayList<byte[]> arrayHeap= new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
//Thread.sleep(60000);
System.out.println("*****************MAIN METHOD STARTED*****************");
MemoryTest2 MemoryTest=new MemoryTest2();
for (int i=0; i<100000;i++) {
MemoryTest.FillTheHeap();
System.out.println("Heap filled with " + MemoryTest.arrayHeap.size()/10.0 +" Mbytes");
}
System.out.println("*****************MEMORY TEST FINISHED*****************");
}
private void FillTheHeap() {
byte[] numberObj=new byte[1000000]; // numberObj is a 1Mbyte array created in the Heap, numberObj reference is kept in the stack
arrayHeap.add(numberObj); //arrayHeap is referenced int he stack but the item of the list are in the heap
try {
Thread.sleep(100); //this is done for visualization purposes in VisualGC
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
If we start the above main method in the JVM using the followings parameters:
-Xms100M -> this is the initial heap size, it includes Eden, S1, S2, old generation (100 Mbytes)
-Xmx200M -> this is the maximum heap size, it includes Eden, S1, S2, old generation (200 Mbytes)
-Xmn30M -> this is Eden plus S1 and S2 size (30 Mbytes)
To see memory and GC operations in Graphical way, we can use jvusualvm tool which is part of JDK, so you don’t need to download it separately. Just run jvisualvm command in the terminal to launch the Java VisualVM application. Once launched, you need to install Visual GC plugin from Tools -< Plugins option
Once started, the following video shows what happens in the JVM memory:
You can see when the main method starts, the FillTheHeap method is called which add 1Mbyte byte array in the arrayHeap class attribute. The arraHeap reference is on the Stack while its content is on the heap. The video shows that every time this method is called 1Mbyte data is added in the Eden memory space. When the Eden (24Mb) is filled a minor GC takes place and the data is moved to the S1 space (1). Then the Eden is filled again with new data and and another minor GC takes place (2) which moves this new data in S0 space (2a) and the previous S1 data in the Old generation space (2b). This process keep running until the Old generation memory space is full (3) and the java application gives
java.lang.OutOfMemoryError: Java heap space.
Figure 3: heap memory and garbage collector
Please note that in this java code a memory leak is generated for training purposes: the byte array we add in the arrayHeap cannot be cleaned ad a reference (arrayHeap) to it exists in the stack space.
References
[1] – https://docs.oracle.com/cd/E13150_01/jrockit_jvm/jrockit/geninfo/diagnos/garbage_collect.html
[2] – https://www.journaldev.com/4098/java-heap-space-vs-stack-memory
[3] – http://kumarsoablog.blogspot.it/2013/02/jvm-parameter-survivorratio_7.html