17 - long lived objects will enter the elderly generation

The long-term survivors will enter the elderly generation

Most collectors in the HotSpot virtual machine use generational collection to manage heap memory. During memory recovery, you must be able to decide which surviving objects should be placed in the new generation and which surviving objects should be placed in the old generation. To do this, The virtual machine defines an object Age for each object (Age) counter, which is stored in the object header. An object is usually born in Eden area. If it still survives after the first Minor GC and can be accommodated by Survivor, the object will be moved to Survivor space, and its object Age will be set to 1 year old. Each time an object survives a Minor GC in Survivor area, its Age will increase by 1 year. When its Age increases to To a certain extent (15 by default), the object will be promoted to the older generation. The Age threshold of the object's promotion to the older generation can be set through the parameter * * - XX: MaxTenuringThreshold * *.

1.MaxTenuringThreshold=1

When we set the - XX: MaxTenuringThreshold=1 memory parameter, execute the following code:

/**
 * @Des: Long lived objects enter the old age test
 * VM Parameters: - verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC
 * -XX:MaxTenuringThreshold=1 : When the age of the new generation of objects reaches 1 year old, they can enter the old generation
 * -XX:+PrintTenuringDistribution: JVM At each Cenozoic GC, the age distribution of objects in the surviving area is printed.
 */
public class TestLongObjToOld {
    private static final int _1MB = 1024 * 1024;

    public static void testTenuringThreshold() {
        byte[] allocation1, allocation2, allocation3;
        allocation1 = new byte[_1MB / 4]; //When 256KB will enter the older generation depends on the setting of XX:MaxTenuringThreshold
        allocation2 = new byte[4 * _1MB]; //4048KB
        allocation3 = new byte[4 * _1MB];//4048KB eden occupies a total of 8352KB
        allocation3 = null;  //Break the reference and become a garbage object
        allocation3 = new byte[4 * _1MB]; //Apply for 4MB memory allocation again, if it cannot be put down, trigger Minor GC
    }

    public static void main(String[] args) {
        testTenuringThreshold();
    }
}

Output results:

[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age   1:     896768 bytes,     896768 total
: 6079K->875K(9216K), 0.0036167 secs] 6079K->4971K(19456K), 0.0036538 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age   1:        584 bytes,        584 total
: 5056K->0K(9216K), 0.0008881 secs] 9152K->4968K(19456K), 0.0009051 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4316K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  52% used [0x00000000fec00000, 0x00000000ff037058, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400248, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 4968K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  48% used [0x00000000ff600000, 0x00000000ffada120, 0x00000000ffada200, 0x0000000100000000)
 Metaspace       used 3244K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 353K, capacity 388K, committed 512K, reserved 1048576K

We can split the output results. When allocation1 and allocation2 objects are loaded, the sum of the two objects is 4.25MB, and the Eden area can be stored (the size of Eden area is 9216K). There is no problem. The memory diagram is as follows:


When the allocation3 object is created, it is found that there is insufficient space in the eden area, and the first GC will be triggered:

Let's first look at the printing of the first GC:

[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age   1:     896768 bytes,     896768 total
: 6079K->875K(9216K), 0.0036167 secs] 6079K->4971K(19456K), 0.0036538 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

GC: indicates that a garbage collection has occurred, and there is no Full modification in front, indicating that a Minor GC occurs at this time;

Allocation Failure: it indicates that the reason for GC this time is that there is not enough space in the younger generation to store new data.

The three parameters 6079k - > 875k (9216K) are: the capacity of the memory area before GC (this is the younger generation), the capacity of the memory area after GC, and the total capacity of the memory area.

The three parameters of 6079k - > 4971k (19456k) are: the size of the heap before garbage collection, the size of the heap after garbage collection, and the total size of the heap.

0.0036167 secs: represents the time consumption of this Cenozoic GC

Then we draw the following conclusions from this log:

  • This GC Cenozoic decreased by 6079 - 875 = 5204KB
  • The Heap area has been reduced by 6079 - 4971 = 1108KB in total
  • 5204KB - 1108KB = 4096KB means that a total of 4096KB objects have been transferred from the younger generation to the older generation

ok, let's draw a picture to show the following image:

Please note here: because our allocation1 object and allocation2 object are strong references and will not be recycled, they will certainly be directly placed in the survivor area. Allocation1 object can be placed, but our allocation2 object is too large to be placed in area S1. Therefore, according to the default guarantee mechanism of garbage collector mentioned above, Allocation2 objects will be stored directly into our old age. This also explains why 4096K (4MB) objects have finally entered the old age

After the first GC, the Eden area has enough space to store the allocation3 object.

Let's look at the second GC:

allocation3 = null; //Once this line of code is executed, our allotion3 object has no direct reference

As follows:


Then the last line of code begins to execute:

allocation3 = new byte[4 * _1MB]; //Apply for 4MB memory allocation again, if it cannot be put down, trigger Minor GC

If you continue to apply for allocating objects of 4MB size into Eden area this time, there will still be GC triggered when there is no allocation. Continue to analyze the following logs:

[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age   1:        584 bytes,        584 total
: 5056K->0K(9216K), 0.0008881 secs] 9152K->4968K(19456K), 0.0009051 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

Then we draw the following conclusions from this log:

  • This GC Cenozoic reduced 5056- 0 = 5056KB
    • Note: the Cenozoic is directly reduced to 0! Represents that all objects in the surviving area S1 have been transferred to the old age! (because our age threshold is exactly 1) the allocation3 object is directly recycled
  • The Heap area has been reduced by 9152- 4968= 4184KB in total
    • The main reason is that our allocation3 objects and a small number of system objects have been recycled
  • 5056kb - 4184kb = 872KB means that a total of 872KB objects have been transferred from the younger generation to the older generation

The final memory results are as follows:


Match with our last memory log result:

Heap
 def new generation   total 9216K, used 4316K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  52% used [0x00000000fec00000, 0x00000000ff037058, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400248, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 4968K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  48% used [0x00000000ff600000, 0x00000000ffada120, 0x00000000ffada200, 0x0000000100000000)

2.MaxTenuringThreshold=15

The code has not changed, but the JVM parameters have changed. We can directly look at the log results after running:

[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age   1:     877184 bytes,     877184 total
: 6079K->856K(9216K), 0.0025954 secs] 6079K->4952K(19456K), 0.0026263 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age   1:        728 bytes,        728 total
: 5037K->0K(9216K), 0.0009100 secs] 9133K->4949K(19456K), 0.0009267 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

Note: our magic discovery: after the second GC, the occupied space of the Cenozoic generation becomes 0! What's going on with NIMA! We have clearly set the threshold to 15

Here is another rule related to the age of the object, which allows the object to enter the elderly generation without waiting for 15 GC.

So what are the rules? I think some students should have guessed that our dynamic age judgment rules. In the next article, we will continue to bring you the dynamic age judgment rules and how the JVM space allocation guarantee mechanism plays.

Added by CapEsiouS on Sat, 25 Dec 2021 22:13:04 +0200