This is the fifth day of my participation in Gwen Challenge
The JVM garbage collection mechanism
What garbage collection algorithms are available in the JVM? What are the pros and cons of each?
How does the CMS garbage collector work? What are the stages?
Who is the culprit of the service shortage?
The JVM specification does not specify how the garbage collector is implemented; it only ensures that objects in use are not recycled. The most commonly used garbage collectors in today’s server environment are CMS and G1, but the JVM has several other common garbage collectors.
Garbage collection, semantically speaking, involves finding the garbage and then recycling it. The reverse is true of the GC process, which finds the active objects and then determines that the inactive objects are garbage and then deletes them. So garbage collection is only about active objects, not the size of the heap. This is a concept that we’ve been emphasizing and you have to keep in mind.
This article starts with several very important garbage collection algorithms, then focuses on the memory partitioning and GC process for generational garbage collection, and finally introduces several common garbage collectors in current JVMS.
Mark
The first step in garbage collection is to find active objects.
The process of traversing all reachable objects with GC Roots is called tagging.
As you can see, circles represent objects. Green for GC Roots and red for objects that can be traced. You can see that after the tag, there are still multiple gray circles that are recycled objects.
To Sweep
The cleanup phase is the collection of unmarked objects.
But this simple way of cleaning, there is an obvious drawback, that is the debris problem.
For example, 1K, 2K, 3K, 4K, 5K memory is applied.
For some reason, 2K and 4K memory that I no longer use needs to be handed over to the garbage collector for collection.
At this point, you should have 6K of free space. Next, I tried to apply for another 5K space, but the system told me I was out of memory. The longer the system runs, the more such debris there is.
In the days of Windows, one of the most useful features was memory defragment and disk defragment, which had the potential to significantly improve system performance. The starting point is the same.
Copy
There is no silver bullet to solve the fragmentation problem, only honest memory defragmentation.
A good way to do this is to provide an equivalent memory space, copy the surviving objects over, and then clean up the original memory space.
In programming, the replication algorithm is very effective for scaling or defragmentation problems. For example, the HashMap expansion uses the same idea, as does Redis rehash.
The whole process is shown as follows:
This seems to be the perfect way to solve the fragmentation problem. However, its disadvantages are also very obvious. It wastes almost half of its memory space to do this, which is an intolerable waste if resources are already limited.
Compact
In fact, it is possible to clean up memory without allocating an equal amount of extra space.
Think of memory as a very large array with some data removed according to random indexes. So the entire array of cleaning, in fact, do not need another array to support, the use of procedures can be achieved.
Its main idea is to move all the surviving objects and arrange them in order of memory address, and then reclaim all the memory after the end memory address.
We can use an ideal algorithm to look at this process.
last = 0
for(i=0; i<mems.length; i++){if(mems[i] ! =null){
mems[last++] = mems[i]
changeReference(mems[last])
}
}
clear(mems,last,mems.length)
Copy the code
But it’s important to note that this is an ideal state. The object reference relationship is usually very complex, we will not describe the specific algorithm here. All you need to know is that the general collation algorithm is less efficient than the copy algorithm.
generational
We briefly introduced some of the common memory collection algorithms, and today, JVM garbage collectors are extensions of several naive algorithms. Take a quick look at their features:
Copy algorithm
Copy algorithm is the most efficient among all algorithms, but its disadvantage is that it will cause a certain amount of space waste.
Mark-sweep
Efficiency is average, the disadvantage is that it will cause memory fragmentation problems.
Mark-compact
The efficiency is less than the first two, but there is no wasted space, and the memory fragmentation problem is eliminated.
So, there is no optimal algorithm, only the most suitable algorithm.
The JVM is a compute node, not a storage node. Ideally, the life cycle of an object ends as soon as it is used up. For resources that are frequently accessed, we want them to stay in memory.
Research shows that most subjects fall into two categories:
-
Most objects have a short life cycle;
-
Others are likely to live for a long time.
Most die quickly, others live long. ** This hypothesis is called the weak generational hypothesis.
Now highlight.
As you can see, most of the objects are ephemeral, while others live for a long time.
Today’s garbage collectors both physically and logically distinguish between these two classes of objects. We call the area occupied by objects that die fast the Young generation. The area occupied by other living long objects is called Old generation.
The old Generation is also called Tenured Generation in some places, so you’ll know what it means when you see it.
The young generation
The garbage collection algorithm used by the younger generation is the replication algorithm. Because very few objects survive after GC occurs in the young generation, copying these objects is very efficient.
And we also saw that copying is a waste of space, so there are lots of regions in the middle of the young generation.
As shown in the figure, the young generation is divided into one Eden space and two Survivor Spaces.
When the Eden area of the young generation is full, the Minor GC (GC) of the young generation is triggered. The specific process is as follows:
-
After the first GC in Eden, the surviving objects are moved to one of the Survivor partitions (from);
-
The Eden region will be GC again, and then the replication algorithm will be used to clean Eden and from region together. Surviving objects are copied to the TO section; Next, you just need to clear the FROM section.
So in this process, there is always a Survivor partition that is empty. The default ratio of Eden, from, and to is 8:1:1, so only 10% of space is wasted.
This ratio is configured with the -xx :SurvivorRatio parameter (default: 8).
In general, this level is all we need to know. However, in the ordinary interview, there is another point often mentioned, although not too often, it is TLAB, we also briefly introduced here.
TLAB is short for Thread Local Allocation Buffer. By default, the JVM allocates a Buffer area to each Thread to speed up the Allocation of objects. The buffer is placed in the Eden area.
This is similar to ThreadLocal in the Java language, avoiding manipulation of common areas and some lock contention.
Object allocation is preferentially allocated on TLabs, but tLabs are usually small. Therefore, when objects are relatively large, they are allocated in the shared area of Eden area.
TLAB is an optimization technique similar to stack allocation of objects (which leads to the topic of escape analysis, turned on by default). This is a very detailed optimization, not too much introduction, but occasionally interview will be asked.
The old s
In the old age, “mark-clearance” and “mark-collation” algorithms are generally used, because the survival rate of objects in the old age is generally relatively high, and the space is relatively large, so it is not cost-effective to copy them, so it is better to collect them on the spot.
So how did the object get into the old age? There are several ways.
(1) Promotion
If the object is old enough, it will “ascend” into the old age.
The age of an object is judged by its age. Each time a Minor GC occurs, the age of the surviving object is increased by one. Until a certain threshold is reached, these “old fogey” will be promoted to the old age.
These objects, if they become unreachable, will not be cleaned up until the old generation GC occurs.
‐XX:+MaxTenuringThreshold this threshold can be configured with ‐XX:+MaxTenuringThreshold, the maximum value of which is 15, since it is stored in 4bits (so there is no basis for articles on the web to set this value too high).
(2) Allocation of guarantee
If you look at the graph of the young generation, every time an object survives, it goes into one of the survival zones, and the default ratio is 10%. However, we cannot guarantee that less than 10% of the objects survive each time, and when there is not enough Survivor space, we need to rely on other memory (i.e., old age) for allocation guarantee. At this point, the object is also assigned directly to the old age.
(3) Large objects are allocated directly in the old age
Objects beyond a certain size will be allocated directly in the old age. This value is through the parameter – XX: PretenureSizeThreshold configured. The default value is 0, which means that all preferred Eden blocks are allocated.
(4) Age determination of dynamic objects
Some garbage collection algorithms do not require an age of 15 to advance to the old age, but use dynamic calculations. For example, if the sum of the sizes of objects of the same age in the surviving region is greater than half of the size of the surviving region, objects greater than or equal to age will go straight to the old age.
These dynamic decisions are generally not subject to external control, as long as we know they are. The following figure shows the allocation logic for an object.
Card marking
As you can see, the reference relationships of objects are a large network. Some objects may be in the Eden region, some may be in the old age, so how is this cross-generation reference handled? Since the Minor GC happens independently, if an older object references it, how do you ensure that objects of the younger generation survive?
For yes/no decisions, we usually use bitmaps and Bloom filters to speed up the search.
The JVM uses a similar approach. In fact, the old era is divided into many card pages (usually the number is a power of two).
Card Table is a collection used to mark the status of Card pages. Each Card entry corresponds to a Card page.
If the young generation has an object allocated, and the old generation has an object pointing to the new object, then the card page corresponding to the old age object is marked as dirty, and the card table requires very little storage space to preserve these states.
When garbage is collected, the card table can be read first for quick judgment.
HotSpot garbage collector
Next, there are a few of HotSpot’s garbage collectors, each of which has its own characteristics. It is important to know which garbage collector is being used during normal GC optimizations.
Until then, we have organized the generational garbage collection above into a larger picture, and you can map their locations as we introduce the following collectors.
Young generation garbage collector
(1) Serial garbage collector
Only one thread handles the GC, and all user threads are suspended during garbage collection.
This is the simplest garbage collector out there, but don’t think it’s useless. Simple and efficient, it is often used in client-side applications. Because the client application doesn’t create many objects frequently, users don’t feel a noticeable lag. Instead, it uses fewer resources and is more lightweight.
(2) ParNew garbage collector
ParNew is the multithreaded version of Serial. Garbage cleaning is done in parallel by multiple GC threads. The cleanup process still stops the user thread.
ParNew pursues “low pause times”, the only difference from Serial is the use of multi-threading garbage collection, in multi-CPU environment performance can be somewhat improved than Serial. However, thread switching requires additional overhead and therefore does not perform as well as Serial in a single-CPU environment.
Insane
Another multithreaded version of the garbage collector. The main differences from ParNew are:
-
Insane: Chase CPU throughput, be able to perform specified tasks in a short amount of time, ideal for background computing without interaction. Weak interaction strong computation.
-
ParNew: Seeks to reduce user pause time, suitable for interactive applications. Strong interaction weak computation.
Old age garbage collector
(1) Serial Old garbage collector
The Serial garbage collector, the counterpart of the younger generation, is a single-threaded version that is equally suitable for clients.
Serial, the younger generation, uses the copy algorithm.
Old Serial, from the Old days, uses a mark-collation algorithm.
Parallel Old
The Parallel Old collector is an older version of the Parallel Insane, which seeks CPU throughput.
(3) CMS garbage collector
The CMS (Concurrent Mark Sweep) collector is a collector that aims to obtain the shortest GC pause time. It enables the user thread and the GC thread to execute concurrently during garbage collection, so that the user does not feel a significant lag during garbage collection.
In the long run, the CMS garbage collector will be replaced by garbage collectors such as G1. After Java8, using it will throw a warning.
Java HotSpot(TM) 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
Configuration parameters
In addition to the above garbage collectors, there are more advanced garbage collectors such as G1 and ZGC that have special configuration parameters to make them work.
Using the -xx :+PrintCommandLineFlags parameter, you can view the default garbage collector used by the current Java version. You can see that the default collector for Java13 on my system is G1.
java -XX:+PrintCommandLineFlags -version
-XX:G1ConcRefinementThreads=4 -XX:GCDrainStackTargetSize=64 -XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:MinHeapSize=6815736 -XX:+PrintCommandLineFlags -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC
java version "13.0.1" 2019-10-15
Java(TM) SE Runtime Environment (build 13.01.+9)
Java HotSpot(TM) 64-Bit Server VM (build 13.01.+9, mixed mode, sharing)
Copy the code
Here are some configuration parameters:
-
-xx :+UseSerialGC Both young and old generations use serial collectors
-
-xx :+UseParNewGC Uses ParNew for younger generations and Serial Old for older generations
-
-xx :+UseParallelGC ParallerGC for younger generations, Serial Old for older generations
-
-xx :+UseParallelOldGC Both new and old generations use parallel collectors
-
-xx :+UseConcMarkSweepGC, which indicates that the younger generation uses ParNew and the older generation uses CMS
-
-xx :+UseG1GC uses the G1 garbage collector
-
-xx :+UseZGC Uses the ZGC garbage collector
To give you a better impression, take a look at the picture below. Their relationship is complicated. Note in particular that the -xx :+UseParNewGC argument was deprecated in Java9. Don’t be surprised that many programs (such as ES) report this error.
With so many garbage collectors and parameters, what exactly do we use? Where do you optimize?
At present, although the Java version is relatively advanced, but the most used is Still Java8. There is a cost to upgrading from Java8 to a higher version of Java architecture, so the CMS garbage collector will continue for a while.
The most widely used garbage collectors online are CMS and G1, as well as the Parallel avenge, the default of Java8.
-
CMS setting parameters: -xx :+UseConcMarkSweepGC.
-
The default Java8 parameter is -xx :+UseParallelGC.
-
The default Java13 parameter is -xx :+UseG1GC.
STW
Have you ever wondered what happens if a new object comes in during garbage collection (whether it’s markup or collation copy)?
In order to ensure that the program does not mess up, the best way is to suspend the user’s threads. During this time, you can’t new objects, you have to wait. The manifestation on the JVM is a temporary lag that does nothing. This headache phenomenon is called Stop the world. Hereinafter referred to as STW.
The marking phase is mostly for STW. If the user process is not paused, it is possible that other user threads will create new objects and references while the object is marked, causing confusion.
Today’s garbage collectors try to minimize this process. But even the most advanced ZGC will have a short STW process. What we need to do is to minimize GC pauses on our existing infrastructure.
There may be no idea of the effects of STW, so here’s an example to illustrate.
A high concurrency service with peak traffic of 100,000 beats/SEC and 10 load-balancing machines behind it requires an average of 1W /s per machine. If an STW occurs on a machine during this time and lasts one second, 10,000 requests that would otherwise take 10ms to return will have to wait at least one second.
The performance in the user is that the system has stalled. This lag can be particularly noticeable if GC is very frequent and can seriously affect the user experience.
While Java provides us with a great automatic memory management mechanism, it should not be abused because it is STW hard.
summary
Because of the limited space, this article only covers the most important points, and if you dig deeper, you’ll probably end up writing a whole book.
After all, various garbage collectors are designed to solve the STW headache by making GC times shorter, pauses smaller, and throughput higher.
Current recyclers, based on the weak generation assumption, are mostly generational recyclers. There are a number of different garbage collection algorithms for young and old generations, some of which can be used in combination.
algorithm
-
Mark
-
Sweep
-
Copy
-
Compact
generational
-
Young generation
-
Survivor
-
Eden
-
Old generation | Tenured Generation
GC
-
Minor GC
-
Major GC
noun
-
weak generational hypothesis
-
Distribution of guarantee
-
ascension
-
Card marking
-
STW
The pictures in the article are very fond of asking about the division of Eden, from, to and heap. But some of the interviewer’s questions are very old, and because the JVM’s update iteration is a bit fast, don’t refute them. Some of the pain points need to be experienced, and calmly explaining these changes will give you the upper hand in the interview.