preface
I recently read the first few chapters of the famous Understanding the Java Virtual Machine. As the saying goes, the paper comes to sleep shallow, must know this matter to practice, just as the book also provides a few small examples, the author of the memory allocation that a few small examples of their own run, feel under the JVM garbage collection and memory allocation.
preparation
I read the second edition, and there are several small experiments in memory allocation that are divided into the new generation and the old generation. The G1 collector does not have a clear generation, and the memory is divided into regions. The G1 collector is a different set of collectors than all other collectors. There is no way to do experiments, and there are no Eden regions, survivor regions, and space allocation guarantees mentioned in the book. So the JVM parameters are configured first to complete the experiment.
You can click Edit Configurations in the Run TAB of your IDEA and then set the VM options for the project. This section describes the following configuration parameters: -xlog :gc* Prints related information during garbage collection. -xx :+UseSerialGC indicates the combination of Serial+Serial Old collector for garbage collection; -xms20m sets the initial size of the heap to 20m, -xmx20m sets the maximum size of the heap to 20m, and -xmn10m sets the new generation size of the heap to 10m. In the VM Options, separate the parameters with Spaces. Next, record the experiment for several memory allocation and reclamation strategies described in section 3.6:
Objects are allocated in Eden area first
public class Lab { private static final int _1MB = 1024 * 1024; public static void main(String[] args) { byte[] allocation1, allocation2, allocation3, allocation4; allocation1 = new byte[2 * _1MB]; allocation2 = new byte[2 * _1MB]; allocation3 = new byte[2 * _1MB]; allocation4 = new byte[4 * _1MB]; }}Copy the code
The code is exactly the same as in the book. The output is as follows:
When allocation4 allocates, it finds that Eden is out of memory, so it performs a Minor GC. After GC, the new generation changes From 8168K to 927K. Eden clears 0, and From (a survivor zone) changes From 0 to 927K. But the 6m memory was not garbage, could not be cleaned, and could not fit into a survivor zone (only 1m), so it was put into the old age.
In the final result, the old age accounted for 60 percent, that is, the 6m content, all into the old age, Eden area accounted for about half, that is, allocation4 4m, survivor area accounted for 90 percent.
One question is, why did Eden occupy 8168m in the first place? Didn’t you just get 6m? This problem I went to search, probably because the program needs to initialize some necessary objects before running, such as Class loader, Object, static variables, etc., resulting in the initial memory occupation. Then, after the first Minor GC, some garbage was cleared, and 927K was still uncleared. Due to the replication algorithm, it entered the survivor zone, and the remaining 6m in Eden could not enter the survivor zone due to insufficient space, so it entered the old age, and then the 4m in Allocation4 entered Eden zone. This is Eden district priority allocation.
Big object goes straight to the old age
In the example above, you can see that the allocation of 4M allocation4 causes a Minor GC in the new generation. To avoid this, you can let the larger objects go directly to the old age. Add parameters – XX: PretenureSizeThreshold = 3145728 set, after more than 3 m’s object goes straight to the old age, so it was a Minor GC. After adding the parameters, run the same code as in the previous example,
It can be seen that the object of 4M directly entered the old age, while there was no GC in the new generation. At the same time, it can also be seen that although only 6m objects are divided, the new generation is fully occupied, which is consistent with the question we mentioned above.
Long-lived objects will enter the old age
The JVM defines an Age counter for each object. If the object survives after Eden’s birth and the first Minor GC and is accepted by a Survivor zone, it will be moved to a Survivor zone with an Age of 1, and each subsequent Minor GC in a Survivor zone will increase its Age by one year. The default object is 15 years old, entering the old age. We can set this critical age with the parameter -xx :MaxTenuringThreshold. Since my machine was initialized with about 1m memory that I didn’t know what to do (as mentioned above, maybe some initializing objects), SO I changed the memory and changed the heap size to 80M, 40M for new generation and old generation, and 32M for Eden area.
public class Lab { private static final int _1MB = 1024 * 1024; public static void main(String[] args) { byte[] allocation0, allocation1, allocation2, allocation3, allocation4; allocation1 = new byte[1 * _1MB]; allocation2 = new byte[16 * _1MB]; allocation3 = new byte[16 * _1MB]; allocation4 = new byte[16 * _1MB]; }}Copy the code
By default, we run it
Next we set the -xx :MaxTenuringThreshold parameter to 1
As you can see, on the second GC, the objects in survivor go straight to the old age. The meaning of this rule, I think, is that some objects are always in the survivor zone, but as we all know, survivor is divided into from zone and to zone. It seems that they are always in the survivor zone, but in fact, they are always replicating back and forth. Therefore, set an age limit and always skip back and forth to the old age, don’t bother. This replication, after all, also consumes resources.
Dynamic object age determination
In order to better adapt to the memory situation of different programs, the JVM does not require that the age must reach MaxTenuringThreshold before entering the old age. If the total size of all objects of the same age in Survivor zone is more than half of that in Survivor zone, the objects older than or equal to this age will enter the old age zone directly. You can use -xx :TargetSurvivorRatio to set the percentage of survivor zones in which the old age is entered. I changed the total size of memory to 20m, 😀. Since different machines occupy different sizes of memory in Eden area during initialization, I did not follow the instructions in the book for this part of experiment, and designed a small experiment as follows:
public class Lab { private static final int _1MB = 1024 * 1024; public static void main(String[] args) { byte[] allocation0, allocation1, allocation2, allocation3, allocation4; allocation1 = new byte[4 * _1MB]; allocation2 = new byte[4 * _1MB]; allocation3 = new byte[4 * _1MB]; }}Copy the code
As can be seen, there are two GC operations in total. When allocation2 is allocated, GC is performed. This is because there are objects headers, memory fillings and initial memory references mentioned above that must occupy space, so the total occupied size of Eden area after Allocation1 allocation is greater than 4m. Therefore, Eden has insufficient remaining space and performs the first Minor GC. At this time, 4M allocation1 directly enters the old age, and there are 925K objects in survivor area, whose age is set to 1. As mentioned above, it should be some first-class information of objects. Similarly, when allocation3 is allocated, the Eden region is still less than 4m, so Minor GC is still performed. However, at this time, we find that the Survivor region has changed from 925K to 1K, while the old memory is larger than 8m, which is exactly 924K larger than 8m. So everything in survivor 924K has gone to the old days. This is the dynamic object age determination, the 924K object is more than half of the survivor zone, so straight into the old age… As for why there is 1K left, I can only think that maybe it is not an object, it is some other weird thing…
Space allocation guarantee
Before a Minor GC occurs, the JVM checks the maximum amount of contigual space available for the old generation and performs Minor GC if the contigual space is greater than the total size of the new generation object or the average size of the previous promotions, otherwise, Full GC (there is a HandlePromotionFailure parameter, The code is as follows:
public class Lab { private static final int _1MB = 1024 * 1024; public static void main(String[] args) { byte[] allocation0, allocation1, allocation2, allocation3, allocation4; allocation1 = new byte[4 * _1MB]; allocation2 = new byte[2 * _1MB]; allocation3 = new byte[2 * _1MB]; allocation4 = new byte[4 * _1MB]; allocation4 = null; allocation4 = new byte[2 * _1MB]; }}Copy the code
When allocation3 is allocated, a Minor GC is performed. When the last ALlocation4 is allocated, there is 4m space left in the old generation, while the new generation has 6m content at this time. The allocation guarantee fails, and the average promotion to the old generation is 6m (promoted once). You can see the Full GC process between the two yellow lines in the figure. Finally, because the 4m space was no longer referenced, it was reclaimed during Full GC, so the space could be allocated normally. Undertake space allocation guarantee, should still worry old time overload not so much new generation object. So take control in advance, predict whether the old age can hold. However, it is only a prediction, even if the remaining continuous space is larger than the average promotion time, it does not mean that the promotion target size can fit this time.
Record on pit
When you first configure JVM heap-related parameters, you modify the following configuration file, and the result is that even IDEA cannot be started. Unable to open IDEA due to insufficient space. So change the parameters and change the configuration in Run.
Several configuration parameters
-xlog :gc* : prints garbage collection information. -xx :TargetSurvivorRatio=50: By default, if the total size of all objects of the same age in Survivor zone is greater than half of that in Survivor zone, objects older than or equal to this age are directly entered into the old zone. You can use -xx :TargetSurvivorRatio to set the percentage of survivor zones to enter the old age -xms20m-XMx20m-xmn10m: set the heap size -xx :MaxTenuringThreshold: Set the object in the age of survival survivor zone – XX: PretenureSizeThreshold = 3145728: set the size of the object directly into old age
conclusion
This article uses the Serial+Serial Old collector combination to experiment with memory allocation and garbage collection for the JVM. There are many garbage collectors, and I tried a few others. The CMS collector, JDK14, is no longer supported. ParNew can’t be set either; Parallel collectors, like Serial in general, are both new generation + old generation, with the new generation divided into Eden and Survivor. Of course, there are nuances, but I won’t go through them here, and I’ll look at the flow of the G1 later.