Last week, we normally deal with lots of things related to performance. But we didn’t dig into it and check why the problem happened. So I decide to write a post to explain JVM Memory Management. I know there are many blogs which are talking about it. It is not a new topic, but I will write down it from my own understanding and how I use some commands to prove this knowledge.
1. Check Memory Usage
Before we go to understand what is garbage collection, what is young generation, etc. First, we go to see our application’s memory usage status. You can use htop to see all thread’s memory usage. For Java/Scala Application, you have more choices.
# get java application pid
# force Garbage Collection from the Shell
>> jcmd <PID> GC.run
>> jps -l
# check which instances cost most memory
>> jmap -histo:live <PID> | head
>> jmap -histo:live <PID> | head -n 20
# check real-time memory usage status
>> jstat -gc <PID> 1000ms
2. Understand jstat output
Here we list my application jstat output:
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
61952.0 62976.0 0.0 32790.1 1968128.0 29322.0 2097152.0 24306.3 60888.0 60114.9 7808.0 7676.4 33 0.545 13 1.057 1.601
We explain each column’s meaning:
- S0C/S1C: the current size of the Survivor0 and Survivor1 areas in KB
- S0U/S1U: the current usage of the Survivor0 and Survivor1 areas in KB. Notice that one of the survivor areas are empty all the time. (See “Young Generation” to know reason)
- EC/EU: the current size and usage of Eden space in KB. Note that EU size is increasing and as soon as it crosses the EC, Minor GC is called and EU size is decreased.
- OC/OU: the current size and current usage of Old generation in KB.
- PC/PU: the current size and current usage of Perm Gen in KB.
- YGC/YGCT: YGC displays the number of GC event occurred in young generation. YGCT displays the accumulated time for GC (unit is second) operations for Young generation. Notice that both of them are increasing in the same row where EU value is dropped because of minor GC. (See “Young Generation” to know reason)
- FGC/FGCT: FGC displays the number of Full GC event occurred. FGCT displays the accumulated time for Full GC operations. Notice that Full GC time is too high when compared to young generation GC timing.
- GCT: total accumulated time for GC operations. Notice that it is sum of YGCT and FGCT values.
3. How to set JVM parameters
Here we explain why you see S0C, S1C, EC, OC value is like above. There are multiple parameters which can set these values by VM Switch
- -Xms: For setting the initial heap size when JVM starts
- -Xmx: For setting the maximum heap size
- -Xmn: For setting the size of the Young Generation, rest of the space goes for Old Generation
- -XX:PermGen: For setting the initial size of the Permanent Generation memory
- -XX:MaxPermGen: For setting the maximum size of Perm Gen
- -XX:SurvivorRatio: For providing a ratio of Eden space and Survivor Space, for example, if Young Generation size is 10m and VM switch is -XX:SurvivorRatio=2 then 5m will be reserved for Eden Space and 2.5m each for both Survivor spaces. The default value is 8
- -XX:NewRatio: For providing a ratio of old/new generation sizes. The default value is 2
4. JVM Memory Usage
The primary use of memory is in the heap and outside of the heap memory is also consumed in Metaspace, and the stack.
(1) Java Heap
The heap is where your class instantiations or objects are stored. Instance variables are stored in Objects. When discussing Java memory and optimization we most often discuss the heap because we have the most control over it and it is where Garbage Collection and GC optimizations take place. Heap size is controlled by the -Xms and -Xmx JVM flags.
(2) Java Stack
Each thread has its own call stack. The stack stored primitive local variables and object references along with the call stack (method invocations) itself. The stack is cleaned up as stack frames move out of context so there is no GC performed here. The -Xss JVM option controls how much memory gets allocated for each thread’s stack.
Metaspace stores the class definitions of your objects. The size of Metaspace is controlled by setting -XX:MetaspaceSize.
(4) Additional JVM
In addition to the above values, there is some memory consumed by the JVM itself. This holds the C libraries for the JVM and some C memory allocation overhead that it takes to run the rest of the memory pools above. This type of memory can be affected by Tuning glibc Memory Behavior.
5. JVM Memory Model
Until now, we already know the status of our application. But we still don’t know what is Eden, What is Survivor, etc. Here we talk about how does JVM organizes memory. And then finally, we will better understand how to optimize it. I suggest when we read this part, we’d better go back to part2 and part3 to map each concept to real data output. This would be better.
There are five JVM Memory Models:
- Old Memory
Eden + S0 + S1 === Young Gen (-Xmn)
Eden + S0 + S1 + Old Memory === JVM Heap (-Xms -Xmx)
JVM Heap memory is physically divided into two parts-Young Generation and Old Generation.
(1) Young Generation
Young generation is the place where all the new objects are created. When young generation is filled, garbage collection is performed. This garbage collection is called Minor GC. Young Generation is divided into three parts-Eden Memory and two Survivor Memory spaces.
- Most of the newly created objects are located in the Eden Memory space. All new allocation happens in Eden. It only costs a pointer bump.
- When Eden space is filled with objects, Minor GC is performed and all the survivor objects are moved to one of the survivor spaces. When Eden fills up, stop-the-world copy-collection into the survivor space. Dead objects cost zero to collect.
- Minor GC also checks the survivor objects and move them to the other survivor space. So at a time, one of the survivor space is always empty.
- 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.
Since Young Generation keeps short-lived objects, Minor GC is very fast and the application doesn’t get affected by this.
(2) Old Generation
Old Generation memory contains the objects that are long lived and survived after many rounds of Minor GC. Usually garbage collection is performed in Old Generation memory when it is full. Old Generation Garbage Collection is called Major GC and usually takes longer time.
Major GC takes longer time because it checks all the live objects. Major GC should be minimized because it will make your application unresponsive for the garbage collection duration.
throughput collections: -XX:+UseSerialGC -XX:+UseParallelGC -XX:+UseParallelOldGC
low-pause collectors: -XX:+UseConcMarkSweepGC -XX:+UseGIGC
6. Garbage Collection
All the Garbage Collections are “Stop the world” events because all application threads are stopped until the operation completes.
One of the best feature of java programming language is the automatic garbage collection. There are many JVM switch to enable the garbage collection strategy for the application: (I will not explain each) Serial GC (-XX:+UseSerialGC), Parallel GC(-XX:+UseParallelGC), Parallel Old GC(-XX:+UseParallelOldGC), Concurrent Mark Sweep(CMS) Collector (-XX:+UseConcMarkSweepGC) and G1 Garbage Collector( -XX:+UseG1GC).
7. How to optimize JVM parameters
We talk about so much, it looks like JVM already has automatic garbage collection, so we don’t need to do anything. In fact, there are still some tunings we can do.
(1) java.lang.OutOfMemoryError: PermGen
increase the Perm Gen memory space using -XX:PermGen and -XX:MaxPermGen
(2) a lot of Full GC operations
increase Old generation Memory space.
increase stack size by -Xss
(4) Good Practices
- set the minimum -Xms and maximum -Xmx heap sizes to the same value
- -Xmn value should be lower than the -Xmx value.
- older generation is the value of -Xmx minus the -Xmn. Generally, you don’t want the Eden to be too big or it will take long for the GC to look through it for space that can be reclaimed.
- keep the Eden size between one fourth and one third the maximum heap size. The old generation must be larger than the new generation.
To summary, there is no universal solution to fix all. When we meet problems, we need to use tool to find root and dig into it and then fix it.