1. Core overview of the heap

  • There is only one heap memory per JVM instance, and the heap is also the core area of Java memory management
  • The Java heap area is created at JVM startup and its size is determined, making it the largest memory area managed by the JVM
    • The size of heap memory is adjustable

The code tests HeapDemo1

/** * -Xms10m -Xmx10m */
public class HeapDemo1 {
    public static void main(String[] args) {
        System.out.println("start...");
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("end..."); }}Copy the code

HeapDemo2

/** * -Xms20m -Xmx20m */
public class HeapDemo2 {
    public static void main(String[] args) {
        System.out.println("start...");
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("end..."); }}Copy the code

The two pieces of code are identical except for the heap space set up at run time. After executing the two programs at the same time, open JVisualVM (direct terminal execution of JVisualVM command under MAC). Double-click the HeapDemo1 process to view visual GC(when running JVisualVM for the first time, choose Tools -> Plug-ins to install the relevant Visual GC plug-in)

In the figure, the total size of the Eden, Survivor0, Survivor1 and Old Gen zones marked for HeapDemo1 is 10m

For the HeadDemo2 process, the sum of the parts is 20m:

  • The Java Virtual Machine Specification states that the heap can be in a physically discontinuous memory space, but logically it should be treated as continuous
  • All threads share the Java heap, where Thread Local Allocation Buffers (TLAB) can also be allocated.
  • The Java Virtual Machine Specification describes Java pairs as: all object instances and arrays should be allocated on the heap at runtime. (The heap is the run-time data area from which memory for all class instances and arrays is allocated)
    • It should be emphasized that “almost” all object instances are allocated memory here — from a practical usage perspective
  • Arrays and objects may never be stored on the stack (except for “allocation on the stack”), because the stack frame holds a reference to the location of the object or array in the heap
  • Objects in the heap are not removed immediately after the method ends, but only during garbage collection
  • The heap is the focus area where the GC performs garbage collection

Code demo:

package com.nasuf.jvm;

public class SimpleHeap {
    private int id;

    public SimpleHeap(int id) {
        this.id = id;
    }

    public void show(a) {
        System.out.println("My ID is " + id);
    }

    public static void main(String[] args) {
        SimpleHeap s1 = new SimpleHeap(1);
        SimpleHeap s2 = new SimpleHeap(2);
        int[] arr = new int[10];
        Object[] arr1 = new Object[10]; }}Copy the code

1.1 Memory Segmentation

Most modern garbage collectors are designed on the basis of generational collection theory, and the heap space is subdivided into:

  • Java7 and before, heap memorylogicallyDivided into three parts:New area + pension area + permanent area
    • Young Generation Space (Young/New)
      • It is divided into Eden district and Survivor district
    • Tenure Generation Space (Old/Tenure)
    • Permanent Space (Perm)
  • Java8 and later, heap memorylogicallyDivided into three parts:New area + endowment area + meta-space
    • Young Generation Space (Young/New)
      • It is divided into Eden district and Survivor district
    • Tenure Generation Space (Old/Tenure)
    • Meta Space (Meta)

Agreement: new area = new generation = young generation, pension area = old age area = old age, permanent area = permanent generation

If the above code is executed with an argument-XX:+PrintGCDetails, you can see the following output:

Heap
 PSYoungGen      total 6144K, used 942K [0x00000007bf980000.0x00000007c0000000.0x00000007c0000000)
  eden space 5632K, 16% used [0x00000007bf980000.0x00000007bfa6bad0.0x00000007bff00000)
  from space 512K, 0% used [0x00000007bff80000.0x00000007bff80000.0x00000007c0000000)
  to   space 512K, 0% used [0x00000007bff00000.0x00000007bff00000.0x00000007bff80000)
 ParOldGen       total 13824K, used 0K [0x00000007bec00000.0x00000007bf980000.0x00000007bf980000)
  object space 13824K, 0% used [0x00000007bec00000.0x00000007bec00000.0x00000007bf980000)
 Metaspace       used 2657K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 287K.capacity 386K.committed 512K.reserved 1048576K

Copy the code

If you switch to JDK7, the following information is displayed:

Heap
 PSYoungGen      total 6656K, used 869K [0x00000007ff900000.0x0000000800000000.0x0000000800000000)
  eden space 6144K, 14% used [0x00000007ff900000.0x00000007ff9d9418.0x00000007fff00000)
  from space 512K, 0% used [0x00000007fff80000.0x00000007fff80000.0x0000000800000000)
  to   space 512K, 0% used [0x00000007fff00000.0x00000007fff00000.0x00000007fff80000)
 ParOldGen       total 13824K, used 0K [0x00000007feb80000.0x00000007ff900000.0x00000007ff900000)
  object space 13824K, 0% used [0x00000007feb80000.0x00000007feb80000.0x00000007ff900000)
 PSPermGen       total 21504K, used 2656K [0x00000007f9980000.0x00000007fae80000.0x00000007feb80000)
  object space 21504K, 12% used [0x00000007f9980000.0x00000007f9c182c0.0x00000007fae80000)
Copy the code

2. Set the heap memory size to OOM

  • The Java heap area is used to store Java object instances, so the size of the heap is set at JVM startup and can be selected-Xmxand-XmsTo set up
    • -XmsUsed to represent the starting memory of the heap area (i.e., young generation + old generation), equivalent to-XX:InitialHeapSize
      • -XIs a running parameter to the JVM
      • ms: memory start
    • -XmxUsed to represent the maximum memory of the heap area (i.e., young generation + old generation), equivalent to-XX:MaxHeapSize
      • mx: memory max
  • When the size of memory in the heap exceeds the maximum memory specified by -xmx, it is thrownOutOfMemoryErrorabnormal
  • The -xms and -xmx parameters are typically set to the same value to improve performance by eliminating the need to re-delimit the size of the heap once it has been cleaned up by the Java garbage collection mechanism
  • By default
    • Initial memory size: physical computer memory size/ 64
    • Maximum memory size: Physical computer memory size/ 4

Code demo (output initial memory Settings) :

package com.nasuf.jvm;

public class HeapSpaceInitial {
    public static void main(String[] args) {
        // Returns the total heap memory in the Java virtual machine
        long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
        // Returns the maximum heap memory that the Java virtual machine is trying to use
        long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;

        System.out.println("-Xms: " + initialMemory + "M");
        System.out.println("-Xmx: " + maxMemory + "M");

        System.out.println("System memory size:" + initialMemory * 64.0 / 1024 + "G");
        System.out.println("System memory size:" + maxMemory * 4.0 / 1024 + "G"); }}Copy the code

The output is as follows:

-Xms: 245M -Xmx: 3641M The system memory size is:15.3125G System memory size:14.22265625G
Copy the code
package com.nasuf.jvm;

public class HeapSpaceInitial {
    public static void main(String[] args) {
        // Returns the total heap memory in the Java virtual machine
        long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
        // Returns the maximum heap memory that the Java virtual machine is trying to use
        long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;

        System.out.println("-Xms: " + initialMemory + "M");
        System.out.println("-Xmx: " + maxMemory + "M");

        try {
            Thread.sleep(1000000);
        } catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code

-xMS600m -XMx600m output initialMemory and maxMemory:

-Xms: 575M
-Xmx: 575M
Copy the code

View at this time:

$ jps
19459 Launcher
19507 Jps
19460 HeapSpaceInitial
72644
27743

# nasuf @ promote in /Library/Java/JavaVirtualMachines [16:01:45]
$ jstat -gc 19460
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
25600.0 25600.0  0.0    0.0   153600.0 12288.5   409600.0     0.0     4480.0 774.3  384.0   75.9       0    0.000   0      0.000    0.000
Copy the code

Among them:

  • S0C: S0 Capacity
  • S1C: S1 Capacity
  • S0U: S0 Used
  • S1U: S1 Used
  • EC: Eden Capacity
  • EU: Eden Used
  • OC: Old Capacity
  • OU: Old Used

Calculate (S0C + S1C + EC + OC) / 1024 = 600m, that is, our initial set value, and the output value is 575m, because only one of S0 and S1 is in use, so we can calculate (S0C + EC + OC) / 1024 = 575m, Therefore, the system memory size calculated from 575M is smaller than the actual system memory size.

Alternatively, run the -xx :+PrintGCDetails argument to see the memory:

Heap
 PSYoungGen      total 179200K, used 9216K [0x00000007b3800000.0x00000007c0000000.0x00000007c0000000)
  eden space 153600K, 6% used [0x00000007b3800000.0x00000007b41001a0.0x00000007bce00000)
  from space 25600K, 0% used [0x00000007be700000.0x00000007be700000.0x00000007c0000000)
  to   space 25600K, 0% used [0x00000007bce00000.0x00000007bce00000.0x00000007be700000)
 ParOldGen       total 409600K, used 0K [0x000000079a800000.0x00000007b3800000.0x00000007b3800000)
  object space 409600K, 0% used [0x000000079a800000.0x000000079a800000.0x00000007b3800000)
 Metaspace       used 2658K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 287K.capacity 386K.committed 512K.reserved 1048576K
Copy the code

Note: PSYoungGen is 179200K, and the calculation method is Eden space + from space or Eden space + to space

OutOfMemoryError test

package com.nasuf.jvm;

import java.util.ArrayList;
import java.util.Random;

public class OOMTest {
    public static void main(String[] args) {
        ArrayList<Picture> list = new ArrayList<>();
        while(true) {
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            list.add(new Picture(new Random().nextInt(1024 * 1024))); }}}class Picture {
    private byte[] pixels;
    public Picture(int length) {
        this.pixels = new byte[length]; }}Copy the code

Run parameter: -xMS600m -xmx600m. Use Visual GC plug-in in JVisualVM to check memory:

You can seeOldThe section is continuously filled until no further GC is available, resulting in the following OOM log output:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.nasuf.jvm.Picture.<init>(OOMTest.java:23)
	at com.nasuf.jvm.OOMTest.main(OOMTest.java:15)
Copy the code

From the sampler you can further see the objects in memory:

3. The Young and the old

  • Java objects stored in the JVM can be divided into two classes:

    • One is transient objects with a short lifetime, which can be created and killed very quickly
    • One class of objects has a very long lifetime, and in some extreme cases can be consistent with the lifetime of the JVM
  • The Java heap can be further subdivided into YoungGen and OleGen

  • Among them, the young generation can be divided into Eden space, Survivor0 space and Survivor1 space (sometimes called FROM zone or TO zone).

    Note: The following parameters are not normally called in development:

  • Configure the ratio of Cenozoic and old age in the heap structure

    • The default-XX:NewRatio=2, represents the new generation accounted for1, the old age accounted for2The new generation occupies the entire heap spaceA third
    • You can modify-XX:NewRatio=4, represents the new generation accounted for1, the old age accounted for4The new generation occupies the whole heap1/5
  • In HotSpot, the default ratio of Eden space to the other two Survivor Spaces is 8:1:1

  • Of course developers can adjust this space ratio with the -xx :SurvivorRatio option. For example – XX: SurvivorRatio = 8

  • Almost all (but not always) Java objects are newly generated in Eden (if an object is too large, it may go straight to the old age)

  • Most destruction of Java objects occurs in the new generation

    • A special study by IBM shows that 80% of the new generation die young
  • You can set the maximum memory size for the new generation using the -xmn option

    • Use the default value for this parameter. If both are set-XX:NewRatio, then will be-XmnShall prevail

Code demo

package com.nasuf.jvm;

/** * -Xms600m -Xmx600m */
public class EdenSurvivorTest {
    public static void main(String[] args) {
        System.out.println("testing...");
        try {
            Thread.sleep(1000000);
        } catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code

Execute the above code with the parameter -xMS600m -XMx600m; Through the Visual GC plug-in in JVisualVM, you can see the following information:

By default, the ratio of new generation to old generation is 1:2

Alternatively, you can run the jinfo-flag command to view the NewRatio information.

$ jps
13490 Launcher
13491 EdenSurvivorTest
12420
13765 Jps
13213 Main

$ jinfo -flag NewRatio 13491
-XX:NewRatio=2
Copy the code

You can also observe from the figure above that the Eden:Survivor0:Survivor1 ratio is 6:1:1 instead of 8:1:1 due to the JVM’s memory allocation adaptive mechanism. However, if we add -xx: -useadaptivesizePolicy to the program parameters, that is, to cancel the UseAdaptiveSizePolicy mechanism and execute the code, we can find that it does not take effect. It is useful to specify -xx :SurvivorRatio=8 to set the ratio of Eden to Survivor zones in the new generation:

4. Diagram the object assignment process

4.1 Overview of object allocation Process

Allocating memory for new objects is a rigorous and complex task. JVM designers need to consider not only how and where to allocate memory, but also, because the allocation algorithm is closely related to the reclamation algorithm, whether the GC will fragment the memory space after performing the reclamation

  • The object of new is Eden first, which has a size limit
  • When Eden fills up, the program needs to create objects. The JVM’s garbage collector will collect Eden’s garbage (Young GC / Minor GC), destroy the objects in Eden area that are no longer referenced by other objects, and then load new objects to Eden area
    • Note: Young GC is not triggered when Survivor zone is full, but this does not mean that there is no garbage collection in Survivor zone — when a Young GC occurs, both Eden zone and Survivor zone are garbage collected
  • Then move the remaining objects in Eden zone to Survivor0 zone
  • If the garbage collection is started again, the last surviving item is placed in Survivor0, and if it is not collected, it is placed in Survivor1
  • If garbage collection is experienced again, it is put back to Survivor0 and then to Survivor1
  • When to go to the old age? You can set times-XX:MaxTenuringThreshold=<N>The default is15time
  • In the old age, which is relatively idle, GC is triggered again when the old age runs out of memory:Major GC, for the old memory cleaning
  • Object that cannot be saved after the Major GC has been performed by older generationsOOMabnormal

4.2 Graphical Process

Conclusion:

  • For Survivor0,Survivor1: swap after replication, who is empty who is To
  • About garbage collection: frequently collected in the new generation, rarely collected in the old age, almost never collected in the permanent generation/meta-space

The test code

package com.nasuf.jvm;

import java.util.ArrayList;
import java.util.Random;

/** * -Xms600m -Xmx600m */
public class HeapInstanceTest {
    byte[] buffer = new byte[new Random().nextInt(1024 * 200)];

    public static void main(String[] args) {
        ArrayList<HeapInstanceTest> list = new ArrayList<>();
        while (true) {
            list.add(new HeapInstanceTest());
            try {
                Thread.sleep(10);
            } catch(InterruptedException e) { e.printStackTrace(); }}}}Copy the code

When a program throws an OOM exception:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.nasuf.jvm.HeapInstanceTest.<init>(HeapInstanceTest.java:10)
	at com.nasuf.jvm.HeapInstanceTest.main(HeapInstanceTest.java:15)
Copy the code

View each GC memory waveform through JVisualVM as follows:

5. Minor GC、Major GC与Full GC

JVM GC does not always collect the above three memory regions (new generation, old age, method area, or meta space) together, but most of the time it collects for the new generation.

In the implementation of HotSpot VM, there are two types of GC in the collection region: Partial GC and Full GC.

  • Partial collection: Garbage collection that does not collect the entire Java heap, which is divided into:
    • Minor GC/Young GC: This is only garbage collection for the New generation (Eden/S0/S1)
    • Major GC (Old GC) : Just Old GC
      • Currently, only the CMS GC has a separate collection behavior for older generations
      • Note that the Major GC is often confused with the Full GC, and you need to be specific about whether it is old age collection or whole heap collection
    • Mixed GC: Collects garbage from the entire new generation and part of the old generation
      • Currently, only the G1 GC has this behavior
  • Full GC: Collects the entire Java heap and method area garbage collection

5.1 Young Minor GC triggering Mechanism

  • When the young generation runs out of space, a Minor GC is triggered. When the young generation is full, the Eden region is full, and when the Survivor region is full, no GC is triggered (each Minor GC cleans up the young generation’s memory).
  • Because Java objects are mostly ephemeral, Minor GC is frequent and generally fast. This definition is clear and easy to understand
  • The Minor GC raisesSTM (Stop The World), suspend other users’ threads until the garbage collection is complete

5.2 Major GC/Full GC trigger mechanism in the old days

  • A Major or Full GC occurs when an object disappears from an old GC
  • The Major GC occurs, often accompanied by at least one Minor GC (but not always, the Parallel Scavenge Avenge strategy is used directly to select a Major GC).
    • That is, the Minor GC will be attempted first if the old decade runs out of space, and then the Major GC will be attempted later if the old decade runs out of space
  • Major GC is generally slower than Minor GC10The time of STW is longer than that of STW
  • Throw OOM if there is not enough memory after Major GC

5.3 Full GC triggering mechanism

Full GC execution can be triggered in one of five ways:

  • callSystem.gc()Full GC is recommended, but not necessarily performed
  • There is not enough space in the old era
  • Method area space is insufficient
  • The average size entering the old age after passing the Minor GC is greater than the memory available for the old age
  • The Eden zone, Survivor0 (From), and Survivor1 (To) zone copy the object To a Survivor1 (To) zone whose size is greater than the available memory of To Space, and the available memory of the old age is smaller than the size of the object

Note: Full GC is something to avoid during development or tuning, so that pause times are shorter

5.4 GC Log Analysis

The test code

package com.nasuf.jvm;

import java.util.ArrayList;
import java.util.List;

/** * Test Minor GC/Major GC/Full GC * -xms9m-xmx9m-xx :+PrintGCDetails */
public class GCTest {
    public static void main(String[] args) {
        int i = 0;
        try {
            List<String> list = new ArrayList<>();
            String a = "nasuf";
            while (true) { list.add(a); a = a + a; i++; }}catch (Throwable t) {
            t.printStackTrace();
            System.out.println("The number of iterations is:"+ i); }}}Copy the code

Log output:

[GC (Allocation Failure) [PSYoungGen: 2013K->496K(2560K)] 2013K->1055K(9728K), 0.0008150 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2456K->496K(2560K)] 3015K->2023K(9728K), 0.0007776 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 2456K->0K(2560K)] [ParOldGen: 6647K->5472K(7168K)] 9103K->5472K(9728K), [Metaspace: 2652K->2652K(1056768K)], 0.0024768 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 5472K->5472K(9728K), 0.0003580 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 5472K->5447K(7168K)] 5472K->5447K(9728K), [Metaspace: 2652K->2652K(1056768K)], 0.0039904 secs] [Times: user=0.00 sys=0.00, real=0.00Secs] The number of iterations is:17
Heap
 PSYoungGen      total 2560K, used 81K [0x00000007bfd00000.0x00000007c0000000.0x00000007c0000000)
  eden space 2048K, 4% used [0x00000007bfd00000.0x00000007bfd147b8.0x00000007bff00000)
  from space 512K, 0% used [0x00000007bff00000.0x00000007bff00000.0x00000007bff80000)
  to   space 512K, 0% used [0x00000007bff80000.0x00000007bff80000.0x00000007c0000000)
 ParOldGen       total 7168K, used 5447K [0x00000007bf600000.0x00000007bfd00000.0x00000007bfd00000)
  object space 7168K, 75% used [0x00000007bf600000.0x00000007bfb51ce8.0x00000007bfd00000)
 Metaspace       used 2684K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 290K.capacity 386K.committed 512K.reserved 1048576K
java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOfRange(Arrays.java: 3664).at java.lang.String. <init> (String.java: 207).at java.lang.StringBuilder.toString(StringBuilder.java: 407).at com.nasuf.jvm.GCTest.main(GCTest.java18) :Process finished with exit code 0
Copy the code

You can see:

  • Full GC must be performed before an OOM exception is thrown
  • [PSYoungGen: 2013K->496K(2560K)] 2013K->1055K(9728K), 0.00150 secs],2013KRefers to the size occupied by the new generation before GC is executed,496KRefers to the size of the new generation after performing GC,2560KRefers to the total size of the Cenozoic; At the back of the2013KRefers to the amount of heap space occupied by the new generation before GC is performed (this is the same as the amount occupied by the new generation before GC is performed because there is no data in the old generation at the time of the first GC), and1055KIs the amount of heap space taken up after GC,9728KIs the size of the entire heap space (that is, the run parameter set-Xms9m -Xmx9m)

6. Generation idea of heap space

Why do YOU need to generational the Java heap? Can’t it work without generations?

  • According to research, the life cycle of different objects is different, 70% to 99% of objects are temporary objects
    • New generation: Eden is composed of two Survivor (also known as FROM /to, S0/S1) of the same size, and TO is always empty
    • Old age: Stores objects that have survived multiple GC cycles in the New generation
  • The only reason for generational is to optimize GC performance. If there is no generation, then all the objects are in one place, which is like putting all the people in one school in one classroom. To find out which objects are useless during GC, all areas of the heap are scanned. Many objects are dead, so if you divide the generation, you can make a lot of space by putting the newly created object in one place and recycling the dead object area first when GC is performed

7. Memory allocation policy

If the object is still alive after Eden is born and passes the first Minor GC and can be accommodated by Survivor, it is moved to Survivor space and the object age is set to 1. Each time an object survives a Minor GC in a Survivor zone, its age increases by 1, and when it reaches a certain age (the default is 15, but it varies from JVM to JVM, and from GC to GC), it is promoted to the old age.

The age threshold for the object to be promoted to the old age can be set with the option -xx :MaxTenuringThreshold

The object allocation principle for different age groups is as follows:

  • Priority allocated to Eden district

  • Large objects are allocated directly to the old age

    • Try to avoid too many large objects in your program

    The test code

    package com.nasuf.jvm;
    
    /** * -Xms60m -Xmx60m -XX:NewRatio=2 -XX:SurvivorRatio=8 * -XX:+PrintGCDetails * => Eden: 16m, Survivor0/Survivor1: 2m, Old: 40m */
    public class YoungOldSpaceTest {
        public static void main(String[] args) {
            byte[] buffer = new byte[1024 * 1024 * 20]; // 20m}}Copy the code

    Log output:

    Heap
     PSYoungGen      total 18432K, used 2650K [0x00000007bec00000.0x00000007c0000000.0x00000007c0000000)
      eden space 16384K, 16% used [0x00000007bec00000.0x00000007bee969d0.0x00000007bfc00000)
      from space 2048K, 0% used [0x00000007bfe00000.0x00000007bfe00000.0x00000007c0000000)
      to   space 2048K, 0% used [0x00000007bfc00000.0x00000007bfc00000.0x00000007bfe00000)
     ParOldGen       total 40960K, used 20480K [0x00000007bc400000.0x00000007bec00000.0x00000007bec00000)
      object space 40960K, 50% used [0x00000007bc400000.0x00000007bd800010.0x00000007bec00000)
     Metaspace       used 2952K, capacity 4556K, committed 4864K, reserved 1056768K
      class space    used 315K.capacity 392K.committed 512K.reserved 1048576K
    Copy the code

    ParOldGen total 40960K, used 20480K, 20m is allocated to the old age

  • Long-lived objects are assigned to the old age

  • Dynamic object age determination

    • If the sum of the size of all objects of the same age in the Survivor zone is greater than half of the size in the Survivor zone, objects older than or equal to that age can go straight to the old age without waitingMaxTenuringThresholdIn the required age
  • Space allocation guarantee

    • -XX:HandlePromotionFailure

8. TLAB

TLAB(Thread Local Allocation Buffer)

  • The heap is a thread shared area where any thread can access the shared data
  • Because object instances are created so frequently in the JVM, it is not thread-safe to partition memory from the heap in a concurrent environment
  • In order to avoid multiple threads operating on the same address, it is necessary to use mechanisms such as locking, which will affect the allocation speed

What is a TLAB?

  • The Eden region continues to be partitioned from the perspective of the memory model rather than garbage collection, and the JVM allocates a private cache region for each thread, which is contained within Eden space
  • When multiple threads allocate memory simultaneously, TLAB can avoid a series of non-thread-safe problems and improve the throughput of memory allocation, so we can call this memory allocation method as fast allocation strategy
  • All known JVMS derived from OpenJDK provide a TLAB design

TLAB restatement:

  • Although not all object instances can successfully allocate memory in TLAB, the JVM does use TLAB as the first choice for memory allocation

  • In the program, the developer can set whether to enable TLAB space with the option -xx :UseTLAB

    Code verification of TLAB status:

    public class TLABTest {
        public static void main(String[] args) {
            System.out.println("testing...");
            try {
                Thread.sleep(1000000);
            } catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code

    After the program starts, execute it on the command line:

    $ jps
    25059
    27944 Launcher
    27945 TLABTest
    27947 Jps
    13213 Main
    
    $ jinfo -flag UseTLAB 27945
    -XX:+UseTLAB
    Copy the code

    The output -xx :+UseTLAB indicates that TLAB is enabled by default

  • By default, TLAB memory space is very small, only accounts for 1% of the whole Eden space and, of course, we can through the options – XX: TLABWasteTargetPercent set TLAB space occupied the percentage of Eden space size

  • When an object fails to allocate memory in TLAB space, the JVM tries to allocate memory directly in Eden space by using locking mechanisms to ensure atomicity of data operations

The whole process of object allocation:

9. Summary of heap space parameter setting

Website that https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

  • -XX:+PrintFlagsInitialView the default initial values of all parameters
  • -XX:+PrintFlagsFinalView the final values of all parameters (subject to change, no longer the initial values)
  • -XmsInitial heap space memory (physical memory by default1/64)
  • -XmxMaximum heap space memory (default is physical memoryA quarter)
  • -XmnSet the size of the New generation (initial and maximum)
  • -XX:NewRatioConfigure the ratio of Cenozoic and old age in the heap structure
  • -XX:SurvivorRatioSet the ratio of Eden and S0/S1 space in the new generation
  • -XX:MaxTenuringThresholdSet the maximum age of new generation garbage
  • -XX:+PrintGCDetailsPrint verbose GC processing logs
    • Print GC brief information:
      • -XX:+PrintGC
      • -verbose:gc
  • -XX:HandlePromotionFailureWhether to set space allocation guarantee. Before Minor GC occurs, the virtual machine checks to see if the maximum available contiguous space of the old age is greater than the total space of all objects of the new generation
    • If it is, the Minor GC is safe
    • If the value is smaller than the value, the VM views the value-XX:HandlePromotionFailureSet whether to allow guarantee failure
      • ifHandlePromotionFailure=true, it continues to check whether the maximum available continuous space of the old age is greater than the average size of objects promoted to the old age
        • If it is, a Minor GC is attempted, but this Minor GC is still risky
        • If less than, a Full GC is performed instead
      • ifHandlePromotionFailure=falseInstead, do a Full GC

    After JDK6 Update24 (JDK7),HandlePromotionFailureThe parameters no longer affect the virtual machine’s space allocation guarantee policy. Observe the source code changes in the OpenJDK, although they are defined in the source codeHandlePromotionFailureParameter, but it is no longer used in code. JDK6 Update24 changes toMinor GC is performed whenever the contiguous space of the old generation is greater than the total size of the new generation object or the average size of the previous promotions, otherwise Full GC is performed

Escape analysis

There is a description of Java heap memory in Understanding the Java Virtual Machine:

As JIT compilers evolve and escape analysis techniques mature, on-stack allocation, scalar replacement optimization techniques will lead to subtle changes where all objects allocated to the heap overnight become less “absolute.

It is common knowledge in Java virtual machines that objects are allocated memory in the Java heap. However, there is a special case where an object may be optimized for stack allocation if, after Escape Analysis, it is found that there is no Escape method. There is no need to allocate memory on the heap and no garbage collection. This is also the most common out-of-heap storage technique

In addition, the aforementioned deep OpenJDK based customization of TaoBaoVM, where the innovative GCID(GC Invisible Heap) technology implements off-heap to move long-lived Java objects from the Heap to off-heap, In addition, GC cannot manage Java objects inside GCIH, so as to reduce the collection frequency of GC and improve the collection efficiency of GC

10.1 an overview of the

  • How can objects on the heap be allocated to the stack using escape analysis
  • This is a cross-function global data flow analysis algorithm that can effectively reduce the synchronization load and memory heap allocation stress in Java programs
  • Through escape analysis, the Java HotSpot compiler can figure out the scope of a reference to a new object and decide whether or not to allocate the object to the heap
  • The basic behavior of escape analysis is the dynamic scope of the analyzed object
    • When an object is defined in a method and is only used in method memory, no escape is considered to have occurred
    • An object is considered to have escaped when it is defined in a method and referenced by an external method. For example, as a call parameter to be passed elsewhere

The code for

public void test(a) {
    Object o = new Object();
    // use v
    // ...
    o = null;
}
Copy the code

Objects that do not escape can be allocated to the stack, and stack space is removed at the end of the method execution

public static StringBuffer createStringBuffer(String s1, String s2) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb;
}
Copy the code

If you want the StringBuffer sb to not escape the method, you can write it like this:

public static String createStringBuffer(String s1, String s2) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb.toString();
}
Copy the code

More escape analysis code examples

package com.nasuf.jvm;

/** * Escape analysis ** How to quickly determine if escape has occurred by looking at whether the object entity of new is likely to be called outside the method */
public class EscapeAnalysis {
    public EscapeAnalysis obj;

    /** * The EscapeAnalysis method returns the EscapeAnalysis object@return* /
    public EscapeAnalysis getInstance(a) {
        return obj == null ? new EscapeAnalysis() : obj;
    }

    /** * To assign a value to a member attribute, escape occurs */
    public void setObj(a) {
        this.obj = new EscapeAnalysis();
    }

    /** * Object scope is only valid in the current method, no escape */ occurs
    public void useEscapeAnalysis(a) {
        EscapeAnalysis e = new EscapeAnalysis();
    }

    Note: In this example, we focus on the object entity of new in the getInstance() method, not the variable e */
    public void useEscapeAnalysis2(a) {
        EscapeAnalysis e = getInstance();
        // getInstance().xxx() will also escape}}Copy the code

Parameter Settings:

  • Escape analysis is enabled by default in HotSpot after JDK 6U23
  • If using an earlier version, developers can use:
    • options-XX:+DoEscapeAnalysisExplicitly turn on escape analysis
    • Through the options-XX:+PrintEscapeAnalysisView the filter results of escape analysis

Conclusion: If you can use local variables in your development, don’t define them outside the method

10.2 Code Optimization

Using escape analysis, the compiler can optimize code as follows:

  • Stack allocation. Convert heap allocation to stack allocation. If an object is allocated in a subroutine so that Pointers to it never escape, the object may be a candidate for stack allocation, not heap allocation
  • Synchronous ellipsis. If an object is found to be accessible only from one thread, operations on the object can be performed without regard to synchronization
  • Separate objects or scalar substitutions. Some objects may be accessible without needing to exist as a continuous memory structure, and some (or all) of the object may be stored not in memory, but in CPU registers

10.2.1 Assignment on the stack

  • The JIT compiler, based on the results of escape analysis at compile time, finds that an object can be optimized for stack allocation if it does not escape the method. After allocation, execution continues in the call stack, and finally the thread terminates, the stack space is reclaimed, and the local variable object is reclaimed. This eliminates the need for garbage collection
  • A common escape scenario
    • Assign values to member variables, return values to methods, and pass instance references

Code demo

package com.nasuf.jvm;

/** * -XX:+PrintGCDetails */
public class StackAllocation {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            alloc();
        }
        // Check the execution time
        long end = System.currentTimeMillis();
        System.out.println("The time spent is:" + (end - start) + " ms");
        // To check the number of objects in the heap, the thread sleep
        try {
            Thread.sleep(1000000);
        } catch(InterruptedException e) { e.printStackTrace(); }}private static void alloc(a) {
        // No escape occurred
        User user = new User();
    }

    static class User {}}Copy the code

-XMx1g-xMS1g-xx: -doescapeAnalysis, output:

The time spent is:86 ms
Copy the code

Use the sampler in JVisualVM -> Memory to view the in-memory object state as follows:

You can see that there are 10 million User objects in memory

If the run parameter is changed to -xx :+DoEscapeAnalysis to start escape analysis and execute the program, you can see that the output memory allocation time is significantly reduced:

The time spent is:4 ms
Copy the code

Modify the running parameters again, lower the start and maximum memory space to 256m and close escape analysis. -xMS256m -xMS256m -xx: -doescapeAnalysis Output is as follows:

[GC (Allocation Failure) [PSYoungGen: 65536K->528K(76288K)] 65536K->536K(251392K), 0.0007652 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 66064K->480K(76288K)] 66072K->488K(251392K), 0.0006022 secs] [Times: user=0.00 sys=0.00, real=0.00Secs] takes:55 ms
Copy the code

You can see that GC is performed in program memory

If you open escape analysis -xx :+DoEscapeAnalysis, you can see that GC does not occur (GC does not occur in the stack) :

The time spent is:5 ms
Copy the code

10.2.2 Synchronous omission

  • The cost of thread synchronization is quite high, and the consequence of synchronization is reduced concurrency and performance
  • When a synchronized block is dynamically compiled, the JIT compiler can use escape analysis to determine whether the lock object used by the synchronized block can only be accessed by one thread and not published to other threads. If not, the JIT compiler unsynchronizes the code when it compiles the synchronized block. This can greatly improve concurrency and performance. This unsynchronization process is called synchronization elision, also known as lock elimination

Such as the following code:

public void f(a) {
    Object hollis = new Object();
    synchronized(hollis) { System.out.println(hollis); }}Copy the code

The hollis object is locked in the code, but the life of the hollis object is only in the f() method and is not accessed by other threads, so it is optimized for JIT compilation:

public void f(a) {
    Object hollis = new Object();
    System.out.println(hollis);
}
Copy the code

Note, however, that in the bytecode file of the f() method, we can still see the bytecode instructions for the use and exit of the monitor

 0 new #2 <java/lang/Object>
 3 dup
 4 invokespecial #1 <java/lang/Object.<init>>
 7 astore_1
 8 aload_1
 9 dup
10 astore_2
11 monitorenter
12 getstatic #3 <java/lang/System.out>
15 aload_1
16 invokevirtual #4 <java/io/PrintStream.println>
19 aload_2
20 monitorexit
21 goto 29 (+8)
24 astore_3
25 aload_2
26 monitorexit
27 aload_3
28 athrow
29 return
Copy the code

10.2.3 Separating objects or scalar Replacement

A Scalar is a quantity which cannot be broken down into smaller quantities. Primitive data types in Java are scalars

In contrast, data that can still be decomposed is called aggregates, and an object in Java is an Aggregate because it can be decomposed into other aggregates and scalars

In THE JIT stage, if an object is found not to be accessed by the outside world after escape analysis, then the OBJECT will be disassembled into several member variables contained in it to be replaced by JIT optimization. This process is called scalar substitution. Parameter -xx :+EliminateAllocations is on by default, allowing objects to be split and allocated on the stack

For example:

package com.nasuf.jvm;

public class ScalarReplace {

    public static class User {
        public int id;
        public String name;
    }

    public static void alloc(a) {
        User u = new User();    // No escape occurs, scalar substitution is performed
        u.id = 5;
        u.name = "nasuf";
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            alloc();
        }
        long end = System.currentTimeMillis();
        System.out.println("The time spent is:" + (end - start) + " ms"); }}Copy the code

The run parameters were -xms100m -XMx100m -xx :+DoEscapeAnalysis -xx :+PrintGC -xx :-EliminateAllocations, turn on escape analysis, turn off scalar substitution, at which User objects were still allocated to heap space. Output:

[GC (Allocation Failure)  25600K->504K(98304K), 0.0008521 secs]
[GC (Allocation Failure)  26104K->440K(98304K), 0.0006371 secs]
[GC (Allocation Failure)  26040K->472K(98304K), 0.0004948 secs]
[GC (Allocation Failure)  26072K->472K(98304K), 0.0004898 secs]
[GC (Allocation Failure)  26072K->424K(98304K), 0.0004289 secs]
[GC (Allocation Failure)  26024K->456K(101888K), 0.0005304 secs]
[GC (Allocation Failure)  33224K->352K(101888K), 0.0005369 secs]
[GC (Allocation Failure)  33120K->352K(100864K), 0.0003237Secs] takes:46 ms
Copy the code

You can see that the object memory allocation time is 46ms and GC is performed during the run. And if you turn on the scalar substitution -xx :+EliminateAllocations and execute again, the output is as follows:

The time spent is:4 ms
Copy the code

You can see that memory is not GC and the object allocation time is drastically reduced to 4ms. Because after scalar substitution is turned on, the method:

public static void alloc(a) {
    User u = new User();    // No escape occurs, scalar substitution is performed
    u.id = 5;
    u.name = "nasuf";
}
Copy the code

Is actually replaced with:

public static void alloc(a) {
    int id = 5;
    String name = "nasuf";
}
Copy the code

This eliminates the need for heap memory allocation, reducing GC and making stack allocation directly.

Note, however, that escape analysis can only be enabled if the JVM is startedserverMode, that is, add-serverParameters; This function is enabled by default for 64-bit VMSserverMode:

$ java -version
java version "1.8.0 comes with _202"
Java(TM) SE Runtime Environment (build 1.8. 0 _202-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)
Copy the code

10.3 Summary: Escape analysis is not mature

  • A paper on escape analysis was published in 1999, but it wasn’t implemented until JDK1.6, and the technology isn’t fully developed yet
  • The fundamental reason is that although scalar replacement, stack allocation and lock elimination can be done through escape analysis, escape analysis itself also requires a series of complex analysis, which is actually a relatively time-consuming process
  • An extreme example would be an escape analysis where no object is escape-free. Then the process of escape analysis is wasted
  • Although this technique is not very mature, it is also a very important method of just-in-time compiler optimization
  • Note that there is some argument that through escape analysis, the JVM allocates objects on the stack that will not escape, which is theoretically possible, but depends on the JVM designer’s choice. This is not done in the Oracle HotSpot JVM, as documented in escape analysis, so it is clear that all object instances are created on the heap
  • Most of the books are still based on previous versions of JDK7, and the JDK has changed a lot. The cache of intern strings and static variables used to be allocated to the persistent generation, which has been replaced by the metadata section. However, the INTERN string cache and static variables are not transferred to the metadata area, but are allocated directly on the heap, so this is also consistent with the previous conclusion: object instances are allocated on the heap