In this article, we will try to understand the Java memory model and how the garbage collection works. In this article, I have used JDK8 Oracle Hot Spot 64 bit JVM. First, let me depict the different memory areas available for the Java process.
Once we launch the JVM, the operating system allocates the memory for the process. Here the JVM itself is a process and the memory allocation to that includes the Heap, MetaSpace, JIT code cache, thread stacks, and the shared libraries. We call it a native memory. The “Native Memory” is the memory provided to the process by the operating system. How much memory the operating system allocates to the Java process depends on the operating system, processor and the JRE. Let me explain the different memory blocks available for JVM.
Heap Memory: JVM uses this memory to store the objects. This memory in turn split into two different areas called “Young Generation Space” and “Tenured Space“.
Young Generation: The Young Generation or the New Space is divided into two portions called “Eden Space” and “Survivor Space“.
Eden Space: When we create an object, the memory will be allocated from Eden Space.
Survivor Space: This contains the objects that have been survived from the Young garbage collection or Minor Garbage Collection. We have two equally divided survivor spaces called S0 and S1.
Tenured Space: The objects which reach to max tenured threshold during the minor GC or young GC, will be moved to “Tenured Space” or “Old Generation Space“.
When we discuss the garbage collection process we will come to know how the above said memory locations will be used.
Meta Space: This memory is out of heap memory and part of the native memory. As per the document by default, the metaspace doesn’t have an upper limit. In earlier versions of Java, we call this as “Perm Gen Space“. This space is used to store the class definitions loaded by the class loaders. This is designed to grow to avoid the Out Of Memory Errors. But, if it grows more than the available physical memory, then the operating system will use virtual memory. This will have an adverse effect on the application performance as swapping the data from virtual memory to physical memory and vice versa is costlier operation. We have JVM options to limit the MetaSpace used by JVM. In that case, we may get out of memory errors.
Code Cache: JVM has an interpreter to interpret the byte code and converts the same into hardware dependent machine code. As part of JVM optimization, the Just In Time(JIT) compiler has been introduced. The frequently accessed code blocks will be compiled to native code by JIT and stored it in the code cache. The JIT-compiled code will not be interpreted.
Now let us discuss the garbage collection process. The JVM uses a separate demon thread to do garbage collection. As we said above when the application creates the object the JVM tries to get the required memory from the Eden space. The JVM performs GC as minor GC and major GC. Let us understand the minor GC.
Initially, the survivor space and the tenured space is empty. When the JVM is not able to get the memory from the Eden space it initiates the minor GC. During the minor GC, the objects which are not reachable are marked to garbage collect. JVM selects one of the survivor space as “To Space”. It might be S0/S1. Let us say JVM selected S0 as “To Space”. JVM copies the reachable objects to “To Space” that is S0 and increments the reachable object’s age by 1. The objects which are not fit into survivor space will be moved to tenured space. This process is called “premature promotion”. For the representational purpose, I have made the “To Space” as bigger than the allocated space. Remember the survivor space won’t grow.
In the above diagram, the objects marked with RED color indicates unreachable. All the reachable objects are GC roots. The garbage collector won’t remove the GC roots. The garbage collector removes the unreachable objects and empties the Eden space.
For the second minor GC, the garbage collector marks the unreachable objects from “Eden space” and the “To survivor space(S0)” and copies the GC roots to other survivor space S1 and the reachable objects age will be incremented.
In the above diagram, the objects marked with RED are eligible to GC and the other objects will be copied from Eden and the from survivor space to other survivor space S1 with the objects age incremental.
The above process repeats for each minor GC. When objects reached to max age threshold then those objects are copied to tenured space.
There is a JVM level option called “MaxTenuringThreshold” to specify the object age threshold to promote the object to tenured space. By default, the value is 15.
So it is clear that the minor GC reclaims the memory from “Young Generation Space“. The minor GC is “Stop the world” process. Some times the application pause is negligible. The minor GC will be performed with a single thread or multi-thread based on the GC collector applied.
If the minor GC triggers several times eventually the “Tenured Space” will be filled up and requires to be garbage collected. During this time the JVM triggers “major GC” event. Some times we call this as full GC. But, as part of full GC, the JVM reclaims the memory from “Meta Space”. If there are no objects in the heap, then the loaded classes will be removed from the metaspace.
Now let us see what are the possibilities that the JVM triggers the major GC.
- If the developer calls System.gc() or Runtime.getRunTime().gc() suggests the JVM to initiate the GC.
- If JVM decides there is not enough tenured space, then it triggers major GC
- Minor GC may trigger major GC. During the minor GC if JVM is not able to reclaim enough memory from Eden or survivor space.
- If we set “MaxMetaspaceSize” option for JVM and there is not enough space to load new classes, then JVM triggers major GC.
In the coming articles, we will analyze the garbage collection log to get insights to tune the JVM for better performance. Till then stay tuned!!!