In-depth understanding of the JVM virtual machine-JVM object allocation strategy

Summary:

  1. In the book, the object is assigned first in Eden, and the experiment does not match the actual results? Comparison and interpretation of the actual results
  2. Overview of the allocation details of JVM large objects, supplementing the missing survival parameters in the dynamic age judgment.
  3. Understand the mechanism of space allocation guarantee and why it exists. And details of changes to the JDK version
  4. Summarize personal experience and lessons

preface

The JVM’s object allocation strategy is one of the most common points of interview and one of the most important hurdles to learn and understand virtual machines. This article is not a simple summary of the contents of the book, on a personal experiment for the case in the book, findings and results of the book incredibly don’t match, so take a lot of studies about this time, according to the personal learning and summarized below to describe the individual object allocation strategy for the JVM.

Note: This section is not recommended to watch on mobile phones, but is recommended to watch on PC. In addition, students with conditions are strongly advised to read the third edition of “Deep Understanding of the JVM” to see the part about object priority allocation in the Eden region, which is more helpful to understand the differences described below.

Question: Does the experiment of object priority allocation in Eden zone not match the actual result?

If you look at the Eden region assignment blog, you will find a lot of similar information. This code is from Understanding the JVM. Let’s take a look at how this part of the code actually works compared to the results in the book.

According to the book, the correct result is that after Minor GC the object allocates the last 4M objects in the Eden region, while A1, A2, and A3 enter the old age and occupy 6M of old age memory because they cannot fit in the survivor region, but the actual result is quite different.

Conclusion: to match the situation in the book, you need to use the serial garbage collector + jdk1.7 version to have the book effect. In other cases, all sorts of inexplicable situations will be found.

Problem code:

public class MinorGcTest {
    private static final int _1MB = 1024 * 1024;

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

    public static void testAllocation(a) {
        byte[] allocation1, allocation2, allocation3, allocation4, allocation5;
        allocation1 = new byte[_1MB * 2];
        allocation2 = new byte[_1MB * 2];
        allocation3 = new byte[_1MB * 2];
        allocation4 = new byte[_1MB * 4];

    }/* parallel collector (JDK 1.8.0-221) -verbose: gC-xms20m -xmx20m -xmn10m -xx :+PrintGCDetails -xx :SurvivorRatio=8 [GC (Allocation Failure)] [PSYoungGen: 7925K->1006K(9216K)] 7925K->1006K(9216K), 0.0045482 secs] [Times: User =0.00 sys=0.00, real= 0.01secs] Heap PSYoungGen total 9216K, used 7637K [0x00000000FF600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 80%, informs [x00000000ff600000 0, 0 x00000000ffc79b40, 0 x00000000ffe00000) from space 1024 k, 98%, informs [x00000000fff00000 x00000000ffe00000 0, 0 x00000000ffefbbb0, 0) to space 1024 k, 0%, informs [x00000000fff00000 0, 0 x00000000fff00000, 0 x0000000100000000) ParOldGen total 10240 k, used 4387K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) object space 10240K, 42%, informs [x00000000fec00000 0, 0 x00000000ff048d18, 0 x00000000ff600000) Metaspace informs the 3302 k, capacity 4496 k, committed 4864K, reserved 1056768K class space used 355K, capacity 388K, committed 512K, Reserved 1048576K JDK 1.8.0-221 The following is the running result of the Serrial collector: -verbose: gC-xms20m -Xmx20M -Xmn10M -XX:+PrintGcDetail -XX:SurvivorRatio= 8-xx :+UseSerialGC The Eden region holds 6M objects, whereas the old region allocated the last 4M objects [GC (Allocation Failure) [DefNew: Secs] [Times: 0.001560k -> 0.001560k (0.001560k)] User =0.00 sys=0.02, real=0.01 secs] Heap def new generation total 9216K, used 7654K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 80% used [0x00000000fec00000, 0x00000000ff279b70, 0x00000000ff400000) from space 1024K, 99% used [0x00000000ff500000, 0x00000000ff5ffff8, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) tenured generation total 10240K, used 4276K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 41% used [0x00000000ff600000, 0x00000000ffa2d020, 0x00000000ffa2d200, 0x0000000100000000) Metaspace used 3271K, capacity 4496K, committed 4864K, reserved 1056768K class space used 355K, capacity 388K, committed 512K, reserved 1048576K */

    /*JDK 1.7.0_51 -verbose: gc-xms20m -xmx20m -xmn10m -xx :+PrintGCDetails -xx :SurvivorRatio=8 The Eden region lives 6M objects, Heap PSYoungGen total 9216K, used 7669K [0x00000000FF600000, 0x0000000100000000, 0x0000000100000000, 0x00000000ff600000 0x0000000100000000) eden space 8192K, 93%, informs [x00000000ff600000 0, 0 x00000000ffd7d448, 0 x00000000ffe00000) from space 1024 k, 0%, informs [x0000000100000000 x00000000fff00000 0, 0 x00000000fff00000, 0) to space 1024 k, 0%, informs [x00000000ffe00000 0, 0 x00000000ffe00000, 0 x00000000fff00000) ParOldGen total 10240 k, used 4096K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) object space 10240K, 40%, informs [x00000000fec00000 0, 0 x00000000ff000010, 0 x00000000ff600000) PSPermGen total 21504 k, used 3019K [0x00000000f9a00000, 0x00000000faf00000, 0x00000000fec00000) object space 21504K, 14%, informs [x00000000f9a00000 0, 0 x00000000f9cf2c78, 0 x00000000faf00000) JDK 1.7.0 _51 run results: The following is the running result of the Serrial collector. The last allocated object in the Eden region lasts for 4M. -verbose: gC-xms20m -xmx20m -xmn10m -xx :+PrintGcDetail -xx :SurvivorRatio= 8-xx :+UseSerialGC [gc [DefNew: 7505K->6677K(19456K), 0.0066489 secs] [Times: User =0.03 sys=0.02, real= 0.01secs] Heap def new generation total 9216K, used 5123K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 56% used [0x00000000f9a00000, 0x00000000f9e7b6a0, 0x00000000fa200000) from space 1024K, 52% used [0x00000000fa300000, 0x00000000fa385660, 0x00000000fa400000) to space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) tenured generation total 10240K, used 6144K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 60% used [0x00000000fa400000, 0x00000000faa00030, 0x00000000faa00200, 0x00000000fae00000) compacting perm gen total 21248K, used 2923K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 13% used [0x00000000fae00000, 0x00000000fb0dada0, 0x00000000fb0dae00, 0x00000000fc2c0000) No shared spaces configured. */


}
Copy the code

When I first read the book, I felt that the book was cheating me, so I collected all kinds of information on the Internet and found that most of them seemed to be “copying the book” (deep understanding of what the JVM virtual machine is saying), even if they were in the right direction, they were not compared according to the JDK version. So here’s a straightforward comparison of two different JDK versions for two different collectors.

The serial collector is also used. By default, however, the JDK runs with the Parallel collector instead of the Serial collector. So the result is a completely different run than what you see in the book. In addition, the JVM itself needs to take up a certain amount of memory, and there is a lot of “extra” memory in garbage collection, which will be described in the following section.

However, after rereading this part, I found that it was not particularly rigorous, and it is quite normal to lead to misunderstanding.

Below is a screenshot of the results of the PARALLEL collector compared to JDK1.7 and 1.8:

  • The print shows that the JDK7 version does not trigger GC, but the JDK8 version does.
  • In JDk1.8, a survivor is crammed with data, while JDk7 allocates all object contents to the Eden area.
  • Jdk1.8 uses the meta space (deprecating the permanent generation), while JDK7 has the permanent generation, you can see that the constant pool has actually been moved to the heap, so its total size is the size of the heap space.

The following is a screenshot of the results of the JDK1.7 and 1.8 comparison of the Serial collector:

  • Jdk8 allocated 4M space in the old era and JDK7 6M space.
  • Jdk7 uses half of the size of the survivor area to store data, while the Eden area is about 5M. Jdk8 directly allocates the 7M Eden region +1M survivor region, and the remaining objects are directly allocated to the old generation
  • Jdk1.8 uses the meta space (deprecating the permanent generation), while JDK7 has the permanent generation, you can see that the constant pool has actually been moved to the heap, so its total size is the size of the heap space.

Conclusion:

  • The PS collector in JDK7 has no GC, 4M objects are allocated directly in the old age. If you change the object size to 3M or 2M, the GC is triggered.
  • Under the Serial collector, a GC is triggered when memory is allocated to an object of 4M. The result of GC: the previously allocated 6M objects are in the old age, and the later allocated 4M objects are in the new age.
  • Serial collector increase – XX: PretenureSizeThreshold = 3145728 can produce PS collector the same results.

As you can see from the above, the effect of different collectors for JDK1.7 and 1.8 is quite different. What causes it?

This question actually very good answers: we first compare in the case of does not perform any code corresponding to the same garbage collector use two versions of a space size: from the point of the run results results of personal computers, serial collector in jdk7 will cost only 1 m or so, while the jdk8 consume close to 4 m memory size. Therefore, it is clear that the JVM needs to generate certain objects in order to ensure that the program runs, resulting in “contamination” of the test results, and we need to take this into account when conducting experiments.

This is similar to the idea of all test tools: the test tool itself needs some memory to run:

Heap
 def new generation   total 9216K, used 3993K [0x00000000fec00000.0x00000000ff600000.0x00000000ff600000)
  eden space 8192K,  48% used [0x00000000fec00000.0x00000000fefe6540.0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000.0x00000000ff400000.0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000.0x00000000ff500000.0x00000000ff600000)
 tenured generation   total 10240K, used 0K [0x00000000ff600000.0x0000000100000000.0x0000000100000000)
   the space 10240K,   0% used [0x00000000ff600000.0x00000000ff600000.0x00000000ff600200.0x0000000100000000)
 Metaspace       used 3258K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 354K.capacity 388K.committed 512K.reserved 1048576K
--------------------------
Heap
 def new generation   total 9216K.used 1525K [0x00000000f9a00000. 0x00000000fa400000. 0x00000000fa400000)
  eden space 8192K18%,used [0x00000000f9a00000. 0x00000000f9b7d418. 0x00000000fa200000)
  from space 1024K0%,used [0x00000000fa200000. 0x00000000fa200000. 0x00000000fa300000)
  to   space 1024K0%,used [0x00000000fa300000. 0x00000000fa300000. 0x00000000fa400000)
 tenured generation   total 10240K.used 0K [0x00000000fa400000. 0x00000000fae00000. 0x00000000fae00000)
   the space 10240K0%,used [0x00000000fa400000. 0x00000000fa400000. 0x00000000fa400200. 0x00000000fae00000)
 compacting perm gen  total 21248K.used 2871K [0x00000000fae00000. 0x00000000fc2c0000. 0x0000000100000000)
   the space 21248K13%,used [0x00000000fae00000. 0x00000000fb0cdff0. 0x00000000fb0ce000. 0x00000000fc2c0000)
No shared spaces configured.
Copy the code

Some of the effects of the JVM’s own objects on large object allocation

Since we don’t run any program will produce some object, the object will will certainly affect the verification results of the program, may wish to verify commented out behind a 2 m space objects, and a 4 m object allocation, simply assign two 2 m byte array will produce what the results, the following results show will find in the Eden area account for 8 m of space. Obviously, the JVM itself uses some data (4M), which is directly allocated to Eden, and the 4M byte array memory objects are also allocated to Eden.

Heap
PSYoungGen      total 9216K, used 8089K [0x00000000ff600000.0x0000000100000000.0x0000000100000000)
eden space 8192K, 98% used [0x00000000ff600000.0x00000000ffde6560.0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000.0x00000000fff00000.0x0000000100000000)
to   space 1024K, 0% used [0x00000000ffe00000.0x00000000ffe00000.0x00000000fff00000)
ParOldGen       total 10240K, used 0K [0x00000000fec00000.0x00000000ff600000.0x00000000ff600000)
object space 10240K, 0% used [0x00000000fec00000.0x00000000fec00000.0x00000000ff600000)
Metaspace       used 3261K, capacity 4496K, committed 4864K, reserved 1056768K
class space    used 355K.capacity 388K.committed 512K.reserved 1048576K
Copy the code

So what if I allocate 6M of space? You can see that allocation1** and allocation2 allocate 4 meters of space, while the JVM itself is taking up nearly 4 meters of space. At this point, the Eden region is already using 8 meters of space. The Eden region is full, so when Allocation3 comes in, The JVM is still using 4M of space, the Eden region is still full, and the survivor region cannot fit a1 or A2. So at this time A1 and A2 are directly promoted to the old generation, and A3 is allocated to the new generation.

A reader may wonder what the nearest 1M of objects in a ** Survivor1 zone are. ** In the GC log: 8089K->1016K Minor GC left about 1M of objects alive after garbage collection. Naturally, this part of the object is to copy to the from region (survivor), and the generation age +1.

[GC (Allocation Failure) [PSYoungGen: (*)=> 8089K->1016K(9216K) <=(*)] 8089K->5428K(19456K), 0.0038596 secs] [Times: user=0.19 sys=0.02, real=0.00 secs] 
Heap
PSYoungGen      total 9216K, used 3119K [0x00000000ff600000.0x0000000100000000.0x0000000100000000)
eden space 8192K, 25% used [0x00000000ff600000.0x00000000ff80dbf8.0x00000000ffe00000)
from space 1024K, 99% used [0x00000000ffe00000.0x00000000ffefe010.0x00000000fff00000)
to   space 1024K, 0% used [0x00000000fff00000.0x00000000fff00000.0x0000000100000000)
ParOldGen       total 10240K, used 4412K [0x00000000fec00000.0x00000000ff600000.0x00000000ff600000)
object space 10240K, 43% used [0x00000000fec00000.0x00000000ff04f298.0x00000000ff600000)
Metaspace       used 3348K, capacity 4496K, committed 4864K, reserved 1056768K
class space    used 361K.capacity 388K.committed 512K.reserved 1048576K
Copy the code

If the above description is still confusing, you can turn on the JVM option: -xx :+PrintHeapAtGC. This option is used to print the changes in the heap space. It helps you see the changes in the heap space before and after garbage collection, which is very helpful for debugging and understanding garbage collection.

Final parameter results: -verbose: gC-xms20m-XMx20m-xMn10m -xx :+PrintGCDetails -xx :SurvivorRatio=8 -xx :+PrintHeapAtGC

Here is the personal printout:

{Heap before GC invocations=1 (full 0):
PSYoungGen      total 9216K, used 7925K [0x00000000ff600000.0x0000000100000000.0x0000000100000000)
eden space 8192K, 96% used [0x00000000ff600000.0x00000000ffdbd490.0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000.0x00000000fff00000.0x0000000100000000)
to   space 1024K, 0% used [0x00000000ffe00000.0x00000000ffe00000.0x00000000fff00000)
ParOldGen       total 10240K, used 0K [0x00000000fec00000.0x00000000ff600000.0x00000000ff600000)
object space 10240K, 0% used [0x00000000fec00000.0x00000000fec00000.0x00000000ff600000)
Metaspace       used 3252K, capacity 4496K, committed 4864K, reserved 1056768K
class space    used 353K.capacity 388K.committed 512K.reserved 1048576K
[GC (Allocation Failure) [PSYoungGen: 7925K- > 1006K(9216K7925)]K- > 5413K(19456K), 0.0040614 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap after GC invocations=1 (full 0):
PSYoungGen      total 9216K, used 1006K [0x00000000ff600000.0x0000000100000000.0x0000000100000000)
eden space 8192K, 0% used [0x00000000ff600000.0x00000000ff600000.0x00000000ffe00000)
from space 1024K, 98% used [0x00000000ffe00000.0x00000000ffefbbb0.0x00000000fff00000)
to   space 1024K, 0% used [0x00000000fff00000.0x00000000fff00000.0x0000000100000000)
ParOldGen       total 10240K, used 4406K [0x00000000fec00000.0x00000000ff600000.0x00000000ff600000)
object space 10240K, 43% used [0x00000000fec00000.0x00000000ff04daf8.0x00000000ff600000)
Metaspace       used 3252K, capacity 4496K, committed 4864K, reserved 1056768K
class space    used 353K.capacity 388K.committed 512K.reserved 1048576K
}
Heap
PSYoungGen      total 9216K.used 3220K [0x00000000ff600000. 0x0000000100000000. 0x0000000100000000)
eden space 8192K27%,used [0x00000000ff600000. 0x00000000ff829810. 0x00000000ffe00000)
from space 1024K98%,used [0x00000000ffe00000. 0x00000000ffefbbb0. 0x00000000fff00000)
to   space 1024K0%,used [0x00000000fff00000. 0x00000000fff00000. 0x0000000100000000)
ParOldGen       total 10240K.used 4406K [0x00000000fec00000. 0x00000000ff600000. 0x00000000ff600000)
object space 10240K43%,used [0x00000000fec00000. 0x00000000ff04daf8. 0x00000000ff600000)
Metaspace       used 3261K.capacity 4496K.committed 4864K.reserved 1056768K
class space    used 355K.capacity 388K.committed 512K.reserved 1048576K
Copy the code

Large object allocation strategy:

The object goes straight into the old age

As mentioned earlier, for different collectors, there are different collections in different JDK versions, so to avoid any misunderstandings, the individual will manually select the Serial collector to verify the results according to the JDK8 version +.

So what is a big object? Large objects usually refer to objects that occupy a large amount of contiguous memory space, such as byte arrays and large strings. Worse, generating a large number of “dead” large objects directly results in frequent startup of the garbage collector.

The reason to avoid large objects in Java is that even though there is a lot of memory space in the Eden region, according to the principle of generational theory, objects are allocated in Eden first. Since large objects cannot be allocated in the Eden region because of the capacity of the Eden region, we can only force garbage collection to execute ahead of time and move the surviving objects to the survivor region. However, in the most serious case, after garbage collection is completed, all the new generation of objects survive, so they still cannot be allocated to Eden. In this case, large objects can only be stored in the old era (the results of serial and Paralle collectors are different). Therefore, large objects mean high memory replication overhead.

Hotspot provides: -xx: PretenureSizeThreshold, objects larger than this value are allocated directly in the old age. The goal is to prevent Eden and Survivor regions from duplicating repeatedly.

Note: The -xx: PretenureSizeThreshold parameter is valid only for serial and ParNew collectors. If you want to use another generation of collectors, another option to consider is the **ParNew + CMS ** collector.

* JDk7 and Jdk8 use Parallel garbage collectors by default.

The following code will verify the above statement. The example code is as follows:

public class Object2Old {

    /** * JVM parameters:  * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728 * - XX: PretenureSizeThreshold = 3145728 (equivalent to 3 m) *@param args
     */
    public static void main(String[] args) {
        byte[] allocation1;
        allocation1 = new byte[1024 * 1024 * 3];
        // allocation1 = new byte[1024 * 1024 * 3];}}Copy the code

The code result is as follows:

Because there is no use – XX: + UseSerialGC parameter, the default is used, the Parallel collector so – XX: PretenureSizeThreshold = 3145728 this parameter obviously is of no effect.

Heap
 PSYoungGen      total 9216K, used 8089K [0x00000000ff600000.0x0000000100000000.0x0000000100000000)
  eden space 8192K, 98% used [0x00000000ff600000.0x00000000ffde6550.0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000.0x00000000fff00000.0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000.0x00000000ffe00000.0x00000000fff00000)
 ParOldGen       total 10240K, used 0K [0x00000000fec00000.0x00000000ff600000.0x00000000ff600000)
  object space 10240K, 0% used [0x00000000fec00000.0x00000000fec00000.0x00000000ff600000)
 Metaspace       used 3258K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 354K.capacity 388K.committed 512K.reserved 1048576K
Copy the code

Now let’s go -xx :+UseSerialGC again. The result of this operation is as follows. If this is also expected, large objects of 4M are directly allocated to the old age for storage:

Heap
 def new generation   total 9216K, used 3993K [0x00000000fec00000.0x00000000ff600000.0x00000000ff600000)
  eden space 8192K,  48% used [0x00000000fec00000.0x00000000fefe6540.0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000.0x00000000ff400000.0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000.0x00000000ff500000.0x00000000ff600000)
 tenured generation   total 10240K, used 4096K [0x00000000ff600000.0x0000000100000000.0x0000000100000000)
   the space 10240K,  40% used [0x00000000ff600000.0x00000000ffa00010.0x00000000ffa00200.0x0000000100000000)
 Metaspace       used 3258K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 354K.capacity 388K.committed 512K.reserved 1048576K

Copy the code

Long-lived objects enter the old age

Most of the garbage collectors in Hotspot have the concept of generational. Before JDK8, there was the concept of permanent generation. Permanent generation belongs to the method area, which I won’t cover here, and since JDK8, permanent generation has been removed from the JVM, so we focus on the new generation and the old generation. The new generation consists of the Eden zone and two Survivor zones. By default, the allocation ratio is 8:1:1. The old generation occupies a large amount of memory except for the new generation.

The process of JVM object survival is as follows: Objects are allocated in Eden first. When garbage collection is encountered, the root node enumeration is used to find whether the object is alive. If the current object can be held by survivor, the surviving object is copied from Eden to survivor, and the age of its object is set to 1. Each time an object in survivor survives garbage collection, the corresponding object’s age is **+1**, and when the object reaches age 15, it is directly promoted to the old age.

To change this setting, run: -xx: MaxTenuringThreshold. The default value is 15. Note that the range is 15

The example code here assigns an object of 4M and an object of 1M. If set to 15, the object age of 15 is allocated according to the default rule (why there are 4M objects in the old age is explained in the dynamic object age judgment). If the value is set to 1, it will be found that the Minor GC is triggered. After the collection, the remaining 1M objects will enter the S1 region, and the 4M array objects allocated first will enter the S1 region because the age is 1, and then enter the old age directly because S1 cannot be stored, so the 1M objects allocated later will be in the EDEN region. Whereas the previously allocated 4M objects in the old days, S1 holds the garbage collected objects.

public class ObjectAge {
    /** * JVM parameters:  * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1(/15) * -XX: MaxTenuringThreshold=15 * -XX: MaxTenuringThreshold=1 *@param args
     */
    public static void main(String[] args) {
        byte[] allocation1 = new byte[1024 * 1024 * 4];
        byte[] allocation2 = new byte[1024 * 1024 * 1];

    }/* Long lived objects enter old age: JVM default need use 4 m memory -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- is | | 15 years old age -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- Heap PSYoungGen total 9216K, used 5327K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 65%, informs [x00000000ff600000 0, 0 x00000000ffb33fb0, 0 x00000000ffe00000) from space 1024 k, 0%, informs [x0000000100000000 x00000000fff00000 0, 0 x00000000fff00000, 0) to space 1024 k, 0%, informs [x00000000ffe00000 0, 0 x00000000ffe00000, 0 x00000000fff00000) ParOldGen total 10240 k, used 4096K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) object space 10240K, 40%, informs [x00000000fec00000 0, 0 x00000000ff000010, 0 x00000000ff600000) Metaspace informs the 3433 k, capacity 4496 k, committed 4864K, reserved 1056768K class space used 369K, capacity 388K, committed 512K, Reserved 1048576 k -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | 1 year old age situation | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- [GC (Allocation Failure) [PSYoungGen: 8192K->1008K(9216K)] 8192K->5606K(19456K), 0.0023319 secs] [Times: User =0.00 sys=0.00, real=0.00 secs] Heap PSYoungGen total 9216K, used 2170K [0x00000000FF600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 14%, informs [x00000000ff600000 0, 0 x00000000ff722a70, 0 x00000000ffe00000) from space 1024 k, 98%, informs [x00000000fff00000 x00000000ffe00000 0, 0 x00000000ffefc020, 0) to space 1024 k, 0%, informs [x00000000fff00000 0, 0 x00000000fff00000, 0 x0000000100000000) ParOldGen total 10240 k, used 4598K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) object space 10240K, 44%, informs [x00000000fec00000 0, 0 x00000000ff07dae0, 0 x00000000ff600000) Metaspace informs the 3328 k, capacity 4496 k, committed 4864K, reserved 1056768K class space used 361K, capacity 388K, committed 512K, reserved 1048576K */
}
Copy the code

Dynamic age judgment of objects

Following the question left in the last section: why did 4M objects appear in the old age?

The HotSpot virtual machine does not always use the ** -xx: MaxTenuringThreshold parameter as an indicator of how old the object is. Here’s a more important rule: if the total size of all objects of the same age in the Survivor space is greater than half of the size of the ** Survivor space, objects that are older than or equal to that age can go straight to the old age.

So how do we understand this? Let’s go straight to the actual case.

public class DynamicAge {

    private static final int _1MB = 1024 * 1024;

    /** * JVM parameters: * -verbose: gC-xms20m -xmx20m -xmn10m -xx :+PrintGCDetails -xx :SurvivorRatio=8 -xx :+UseSerialGC * -xx: MaxTenuringThreshold=15 * -XX: MaxTenuringThreshold=1 * *@param args
     */
    public static void main(String[] args) {
        // Allocation1 + allocation2 is larger than 1/2 of survivor space
        System.out.println(_1MB / 4);
        byte[] allocation1 = new byte[_1MB / 4];
        byte[] allocation2 = new byte[_1MB / 4];
        byte[] allocation3 = new byte[_1MB * 4];
// byte[] allocation4 = new byte[_1MB * 4];
// allocation4 = null;
// allocation4 = new byte[_1MB * 4];

    }/* Run result: JVM program default: 4303K memory code from deep understand the JVM, note to use the serial collector, personal test version of JDK8 [GC (Allocation Failure)] DefNew: 4651K->1023K(9216K), 0.002138secs] 4651K->1023K(9216K), 0.002138secs] [Times: User =0.00 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 5174K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 50% used [0x00000000fec00000, 0x00000000ff00dbf8, 0x00000000ff400000) from space 1024K, 99% used [0x00000000ff500000, 0x00000000ff5ffff8, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) tenured generation total 10240K, used 843K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 8% used [0x00000000ff600000, 0x00000000ff6d2cd8, 0x00000000ff6d2e00, 0x0000000100000000) Metaspace used 3435K, capacity 4496K, committed 4864K, reserved 1056768K class space used 369K, capacity 388K, committed 512K, reserved 1048576K */
}
Copy the code

As the comments indicate, the code was taken from the book, but I commented out the rest of the code when I ran it, because the JVM itself takes up a certain amount of memory. From the results of the run, the a1 and A2 objects were judged to take up more than half the size of survivor. So go straight to the old age of the dynamic objects described earlier, and note that this is not 512K, but also includes small objects that exist within the JVM itself.

There’s actually a loophole here, but it’s ok what happens if objects of different ages are equally distributed? For example, the object age of 2 is 30%, the object age of 3 is 30%, and the object age of 4 is 30%. In this case, the JVM provides a parameter: -xx :TargetSurvivorRatio parameter, which sets the target usage of survivor. The default 50 indicates that the target usage of a survivor object is 50%. What this means is that if the sum of survivor objects is greater than the target survival rate, then all objects that are older than that age will go straight to the old age, which means that if the sum of survivor objects is greater than 50%, then all objects that are older than that age will go straight to the old age, This situation is also known as premature entering the old age.

Due to the impact of JVM objects in a certain amount of memory, no specific code can be found to accurately simulate…… in the case of -xx :TargetSurvivorRatio parameter

In addition, the need to know the change of the survivor space, can use parameters: – XX: + PrintTenuringDistribution

Space allocation guarantee

Prior to Minor Gc, the JVM first checks to see if the maximum available continuous space in the old generation is greater than the total space for all objects in the new generation. If this condition was set up, will see a called: – XX: HandlePromotionFailure parameter Settings whether to allow distribution guarantees failure.

If the allocation guarantee is allowed to fail: the maximum continuous memory is checked to see if it is greater than the average size of memory objects promoted to older ages, and Minor GC is attempted

If allocation guarantees are not allowed to fail: or if all of the above conditions are not met, FULL GC is performed.

The above is described in the book as an adventure. The reason why this is risky is that the new generation uses the replication algorithm, which directly refers to the Eden +2 survivor fields for the replication of the surviving objects. S2 usually has to be left empty as a rotating backup. In the most extreme case, if all objects survive after Minor GC, an old age guarantee is needed to ensure that the objects are properly allocated and run. However, if even the old age does not have enough continuous memory to hold the objects, then it is necessary to stop the World to perform a full GC for deep garbage cleaning. How does the old generation know they don’t have enough space? Obviously, the old generation does not know how many surviving objects will come from the new generation. Therefore, the average size of the objects promoted by the new generation to the old generation can only be used as the experience value to judge whether to carry out full GC.

Note: – XX: HandlePromotionFailure before JDK6update24 this parameter set is effective

After this release, minORGc occurs whenever the continuous space of the old generation is greater than the total size of the new generation or the average size of the promotions. (That is, without the previous judgment step)

Since most of the development is in JDK1.8, I haven’t tested this option before jdk6Update24 like in the book, so let’s ignore this parameter and see how it works inside the hotspot source code:

bool TenuredGeneration::promotion_attempt_is_safe(size_t 
max_promotion_in_bytes) const { 
    // The maximum available continuous space in the old age
    size_t available = max_contiguous_available(); 
    // Each promotion to the average size of the old age
    size_t av_promo  = (size_t)gc_stats()->avg_promoted()->padded_average(); 
    // Whether the old age available space is greater than the average promotion size, or whether the old age available space is greater than the size of all objects in the new generation at the time of this GC
    bool   res = (available >= av_promo) || (available >= 
max_promotion_in_bytes); 
    return res; 
} 
Copy the code

Where can I see this code?

How to download JDK source code, hotspot source code

File location: Hotspot-0c94c41dCD70 \ SRC \share\vm\memory\ tenuredgeneration.cpp

bool TenuredGeneration::promotion_attempt_is_safe(size_t max_promotion_in_bytes) const {
  size_t available = max_contiguous_available();
  size_t av_promo  = (size_t)gc_stats()->avg_promoted()->padded_average();
  bool   res = (available >= av_promo) || (available >= max_promotion_in_bytes);
  if (PrintGC && Verbose) {
    gclog_or_tty->print_cr(
      "Tenured: promo attempt is%s safe: available("SIZE_FORMAT") %s av_promo("SIZE_FORMAT"),"
      "max_promo("SIZE_FORMAT")",
      res? "":" not", available, res? "> =":"<",
      av_promo, max_promotion_in_bytes);
  }
  return res;
}
Copy the code

Conclusion:

The basics of JVM object allocation are explained in great detail in The Deep Understanding of the JVM, but from the perspective of actual experiments, we can not completely recite the eight parts of the article. For example, dynamic age judgment has the parameter of survival rate as a measure of how old the object is. Also in different JDK version of the garbage collector for recycling can also see the details of the obvious and the inside of the book introduces different, more “pain” is the JVM itself need to maintain the application object is the result of the interference to the garbage collection, we need to be careful judgment and eliminate “side effect” to get the correct results in the book.

From this experiment for individuals is also a big harvest, is for all data to be questioned and actual verification, otherwise easily lead to perpetuated this misunderstanding, recently to find this part of the materials found many individual is according to the book “copy”, neither specification version also did not say which one is the use of the garbage collector, scratching their heads.

Finally, thank you for your patience to read, if you think it useful might as well support it?

Other:

Why is the generation age of the JVM 15?

The answer from: segmentfault.com/a/119000002…

Next, why is the JVM generational age 15? Not 16,20 or something like that?

It’s really not why it can’t be any other number (except 15), it’s really impossible!

The thing is that part of the HotSpot virtual machine’s object header is used to store the runtime data of the object itself, such as HashCode, GC generational age, lock status flags, thread-held locks, biased thread IDS, biased timestamps, etc. The length of the data is 32 – bit and 64 – bit respectively on a 32-bit and 64-bit VM (without compression pointer enabled). It is officially called Mark Word.

For example, in a 32-bit HotSpot virtual machine, if the object is not locked, then Mark Word’s 32bit space has 25 bits to store the object hash code, 4 bits to store the object generational age, 2 bits to store the lock flag, and 1bit is fixed to 0.

Do you see why? The generational age of the object is 4, 0000, and the maximum is 1111, which is 15, not 16,20 or something like that.

References:

The JVM’s memory allocation strategy… Segmentfault.com/a/119000002…

JVM (9) Memory allocation strategy:

Blog.csdn.net/liupeifeng3…

Deep into the JVM 8: the JVM memory allocation mechanism blog.csdn.net/pang5356/ar…