Note source: Silicon Valley JVM complete tutorial, millions of playback, the peak of the entire network (Song Hongkang details Java virtual machine)

Update: gitee.com/vectorx/NOT…

Codechina.csdn.net/qq_35925558…

Github.com/uxiahnan/NO…

[TOC]

6. The heap

6.1. Core overview of the Heap

The heap is unique to a JVM process, that is, a process has only one JVM, but the process contains multiple threads that share the same heap space.

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. Is the largest chunk of memory managed by the JVM.

  • The size of heap memory is adjustable.

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 the Java heap as: all object instances and arrays should be allocated on the heap at run time. The heap is The run-time data area from which memory for all class instances and arrays is allocated.

Arrays and objects may never be stored 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 area where the Garbage Collection (GC) performs Garbage Collection.

6.1.1. Heap memory segmentation

The Java 7 and previous heap memory is logically divided into three parts: newborn + pension + permanent

  • Young Generation Space Young/New is divided into Eden area and Survivor area
  • Tenure Generation space Old/Tenure
  • Permanent Space Perm of the Permanent area

Java 8 and later heap memory is logically divided into three parts: newborn area + endowment area + metaspace

  • Young Generation Space Young/New is divided into Eden area and Survivor area
  • Tenure Generation space Old/Tenure
  • Meta Space Meta Space

Agreement: New area (generation) <=> young generation, pension area <=> old age area (generation), permanent area <=> permanent generation

6.1.2. Internal structure of heap Space (JDK7)

6.1.3. Internal structure of heap Space (JDK8)

6.2. Set the heap memory size to OOM

6.2.1. Setting heap space size

The Java heap area is used to store Java object instances, and the size of the heap is set at JVM startup with options “-xmx” and “-xms”.

  • “-xms” is used to indicate the starting memory of the heap area, equivalent to-XX:InitialHeapSize
  • “-xmx” is used to indicate the maximum memory of the heap, equivalent to-XX:MaxHeapSize

OutOfMemoryError is thrown when the size of memory in the heap exceeds the maximum memory specified by “-xmx”.

The -xms and -xmx parameters are typically set to the same value to improve performance by eliminating the need to re-delimit the heap size after the AVA garbage collection mechanism has cleaned up the heap.

By default

  • Initial memory size: physical computer memory size / 64
  • Maximum memory size: physical computer memory size / 4

6.2.2. OutOfMemory for example

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))); }}}Copy the code
Exception in thread "main" java.lang.OutofMemoryError: Java heap space
    at com.atguigu. java.Picture.<init>(OOMTest. java:25)
    at com.atguigu.java.O0MTest.main(OOMTest.java:16)
Copy the code

6.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
  • The other class of objects has a very long lifetime, which in extreme cases can be consistent with that of the JVM

The Java heap can be further subdivided into YoungGen and oldGen.

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

The following parameters are generally not adjusted in the development:

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

  • The default-XX:NewRatio=2, indicating that the Cenozoic generation accounts for 1, the old age accounts for 2, and the Cenozoic generation accounts for 1/3 of the whole heap
  • You can modify-XX:NewRatio=4, indicating that the Cenozoic generation accounts for 1, the old age accounts for 4, and the Cenozoic generation accounts for 1/5 of the whole heap

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 by selecting “-xx:SurvivorRatio”. For example – xx: SurvivorRatio = 8

Almost all Java objects are created in Eden. Most destruction of Java objects occurs in the new generation.

  • According to a study by IBM, 80% of the new generation of adults die young.

You can use the “-xmn” option to set the maximum memory size for the new generation. The default value is generally used.

6.4. Diagram the object assignment process

For the new object allocate memory is a very precise and complicated task, the JVM’s designers not only need to consider how to allocate memory, where is the distribution problems, and because the memory allocation algorithm is closely related to the memory recovery algorithm, so you also need to consider the GC to perform after the memory recovery will produce memory fragments in the memory space.

  1. The new object is placed in the Garden of Eden first. This area has a size limit.

  2. When Eden fills up and the program needs to create objects, the JVM’s garbage collector will MinorGC Eden garden, destroying objects in Eden garden that are no longer referenced by other objects. Load a new object and place it in the Garden of Eden

  3. Then move the remaining objects in Eden to Survivor 0.

  4. If garbage collection is triggered again, the last surviving item will be placed in Survivor 0, if not collected, it will be placed in Survivor 1.

  5. If the garbage collection is repeated, it will be put back into Survivor 0 and then go to Survivor 1.

  6. When can WE go to the nursing home? You can set the number of times. The default is 15.

    • Parameters can be set:-Xx:MaxTenuringThreshold= Nset
  7. In the retirement area, relatively leisurely. When the memory of the endowment area is insufficient, GC: Major GC is triggered again to clean the memory of the endowment area

  8. If a Major GC is performed in the endowment area and the object cannot be saved, an OOM exception will be generated.

    java.lang.OutofMemoryError: Java heap space
    Copy the code

The flow chart

conclusion

  • Summary for survivor S0, S1: swap after copy, who is empty and who is to
  • About garbage collection: frequently collected in newborn areas, rarely collected in old age, almost no longer collected in permanent generation and meta-space

Common tuning tools (detailed in the next JVM article: Performance Monitoring and Tuning)

  • The JDK command line
  • Eclipse:Memory Analyzer Tool
  • Jconsole
  • VisualVM
  • Jprofiler
  • Java Flight Recorder
  • GCViewer
  • GC Easy

6.5. Minor GC, MajorGC, Full GC

JVM GC does not always collect the above three memory regions together, but most of the time it collects the new generation.

In the implementation of Hotspot VM, there are two types of GC according to the collection region: Partial GC and FullGC.

  • Partial collection: Garbage collection that does not collect the entire Java heap. Which are divided into:
    • Minor GC/Young GC: This is just garbage collection for the new generation
    • Major GC (Old GC) : This is just garbage collection from Old years.
      • Currently, only the CMSGC has a separate collection of old ages.
      • Note that there are many times when the Major GC is confused with the Full GC, and you need to be specific about whether it is old age or whole heap.
    • MixedGC: Collects garbage collections from the whole 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.

6.5.1. Trigger conditions for the simplest generational GC strategy

Triggering mechanism of Minor GC

  • MinorGC is triggered when the young generation runs out of space, and when the young generation is full it means Eden is full, and when Survivor is full it does not trigger GC. (Each Minor GC cleans up the memory of the young generation.)

  • Since Most Java objects are ephemeral, Minor GC is very frequent and generally fast. This definition is clear and easy to understand.

  • The Minor GC raises the STW and pauses the other user’s threads until the garbage collection is complete

Old GC (Major GC/Full GC) trigger mechanism

  • Refers to a GC that occurs in the old age. When an object disappears from the old age, we say that a “Major GC” or “Full GC” occurs

  • The Major Gc occurs, often accompanied by at least one Minor Gc (but not always, the Paralle1 Scavenge avenge strategy is used to select MajorGC directly).

    • That is, when the old decade runs out of space, the Minor Gc is first attempted. If there is not enough space after that, the Major GC is triggered
  • Major GC is typically 10 times slower than Minor GC and STW is longer

  • If there is not enough memory after Major GC, use OOM

Full GC trigger mechanism (more on that later) :

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

  1. When system.gc () is called, Full GC is recommended, but not necessarily executed
  2. There is not enough space in the old era
  3. Method area space is insufficient
  4. The average size entering the old age after passing the Minor GC is greater than the memory available for the old age
  5. If the size of an object is larger than the available memory of To Space when the Eden or Survivor space0 (From Space) zone is copied To survivor space1 (To Space) zone, the object is migrated To the old age and the available memory of the old age is smaller than the size of the object

Note: Full GC is something to avoid in development or tuning. This will shorten the time

6.6. Generation idea of heap space

Why is the Java heap generational? Can’t it work without generations?

According to research, the life cycle of different objects is different. 70-99% of objects are temporary objects.

  • New generation: Eden and two survivor (also known as FROM /to, S0 / S1) of the same size, 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. A lot of objects are dead, so if you put the new object in a certain place, when GC is the first time to collect the “dead” object area, this will free up a lot of space.

6.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 MinorGC in a survivor zone, its age increases by one year, and when it reaches a certain age (15 by default, which varies from JVM 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:

  • Eden is assigned priority
  • Large objects are allocated directly to the old age (try to avoid too many large objects in the program)
  • Long-lived objects are assigned to the old age
  • Dynamic object age judgment: 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 this age can enter the old age directly without waitingMaxTenuringThresholdIn the required age.
  • Space allocation guarantee:-XX:HandlePromotionFailure

6.8. Allocating memory for objects: TLAB

6.8.1. Why is TLAB (Thread Local Allocation Buffer) available?

  • 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.

6.8.2. What is 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.

  • As far as I know, all JVMS derived from OpenJDK provide a TLAB design.

6.8.3. Further explanation of TLAB

  • 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 the TLAB space with the option “-xx :UseTLAB”.

  • By default, TLAB memory space is very small, only accounts for 1% of the whole Eden space and, of course, we can through the option set TLAB space “- XX: TLABWasteTargetPercent” Eden space occupied the percentage of 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.

6.9. Summary: Parameter Settings for heap space

Website address: docs.oracle.com/javase/8/do…

// The details of the parameters will be covered in the next JVM article: Performance Monitoring and Tuning
-XX:+PrintFlagsInitial  // Check the default initial values of all parameters
-XX:+PrintFlagsFinal  // Check the final values of all parameters (subject to change, no longer the initial values)
-Xms  // Initial heap memory (default: 1/64 of physical memory)
-Xmx  // Maximum heap memory (default: 1/4 of physical memory)
-Xmn  // Set the new generation size. (Initial and maximum values)
-XX:NewRatio  // Configure the ratio of new generation and old age in the heap structure
-XX:SurvivorRatio  // Set the ratio between Eden and S0/S1 space in the new generation
-XX:MaxTenuringThreshold  // Set the maximum age of garbage generation
-XX:+PrintGCDetails // Prints verbose GC processing logs
-xx: +PrintGC ② -verbose :gc- XX: HandlePromotionFalilure:// Whether to set the space allocation guarantee
Copy the code

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 the value 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, the HandlePromotionFailure parameter does not affect the virtual machine space allocation guarantee policy. But it is no longer used in code. After JDK6 Update 24, Minor GC will be performed whenever the contiguously large space of the old generation is larger than the total size of the new generation or the average size of the previous promotions, otherwise FullGC will be performed.

6.X. Is the heap the only option for allocating objects?

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

As JIT compile-time advances and escape analysis techniques mature, on-stack allocation and scalar replacement optimization techniques will lead to subtle changes in how all objects allocated to the heap 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. This eliminates the need to allocate memory on the heap and 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 GCIH (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.

6.X.1. Overview of Escape analysis

How to allocate objects on the heap to the stack requires the use of 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 how far a new object’s references are used to determine whether to allocate the object to the heap.

The basic behavior of escape analysis is analyzing object dynamic scope:

  • When an object is defined in a method and is used only inside the method, 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.

For example 1

public void my_method(a) {
    V v = new V();
    // use v
    / /...
    v = null;
}
Copy the code

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

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 StringBuffer SB not to escape, 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

For example 2

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 is only valid in the current method, no escape */ occurs
    public void useEscapeAnalysis(a) {
        EscapeAnalysis e = new EscapeAnalysis();
    }

    /** * references the value of a member variable, and escapes */
    public void useEscapeAnalysis2(a) { EscapeAnalysis e = getInstance(); }}Copy the code

Parameter Settings

Escape analysis is enabled by default in HotSpot after JDK 6U23

If you are using an earlier version, you can use:

  • Option”-XX:+DoEscapeAnalysis“Explicitly turn on escape analysis
  • By selecting”-XX:+PrintEscapeAnalysis“Check the results of the escape analysis

Conclusion: If you can use local variables in your development, do not use definitions outside the method.

6.x. 2. Escape analysis: 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 the object never escape, the object may be a candidate for stack allocation, not heap allocation

Synchronous elision: If an object is found to be accessed by only one thread, then operations on the object can be performed without synchronization.

Separated objects or scalar substitutions: Some objects may be accessible without needing to exist as a contiguous memory structure, so parts (or all) of the object may not be stored in memory, but in CPU registers.

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.

Common on-stack allocation scenario

In the escape analysis, it was shown. Member variable assignment, method return value, instance reference pass.

Synchronous omit

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.

For example,

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

The hellis object is locked in the code, but the life cycle of the hellis object is only in the f() method and is not accessed by other threads, so it will be optimized out during JIT compilation.

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

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 be decomposed is called aggregates. Objects in Java are aggregates because they 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.

For example,

public static void main(String args[]) {
    alloc();
}
private static void alloc(a) {
    Point point = new Point(1.2);
    System.out.println("point.x" + point.x + "; point.y" + point.y);
}
class Point {
    private int x;
    private int y;
}
Copy the code

The code above, after the scalar substitution, will become

private static void alloc(a) {
    int x = 1;
    int y = 2;
    System.out.println("point.x = " + x + "; point.y=" + y);
}
Copy the code

As you can see, the aggregate Point is replaced by two scalars after the escape analysis and it is found that it does not escape. So what’s good about scalar substitution? This can greatly reduce the footprint of the heap. Because once you don’t need to create objects, you don’t need to allocate heap memory anymore. Scalar substitution provides a good basis for on-stack allocation.

Scalar replacement parameter Settings

Parameter -xx :EliminateAllocations: Turns on scalar substitutions (on by default), allowing objects to be split and allocated to the stack.

The code above alloc 100 million times in the main function. Call for object creation, and since the User object instance takes up about 16 bytes of space, the total allocated space is nearly 1.5GB. If the heap space is less than this, GC is bound to occur. Run the above code with the following parameters:

-server -Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
Copy the code

Set the parameters as follows:

  • parameter-server: Starts Server mode, because escape analysis can only be enabled in Server mode.
  • parameter-XX:+DoEscapeAnalysis: Enable escape analysis
  • parameter-Xmx10m: Specifies a maximum heap size of 10MB
  • parameter-XX:+PrintGC: Will print the Gc log
  • parameter-XX:+EliminateAllocations: Enables scalar substitution (on by default), which allows objects to be split and allocated on the stack. For example, if an object has two fields, id and name, these two fields will be allocated as two independent local variables

6.X.3. Escape Analysis 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 there is no guarantee that the performance cost of escape analysis will be higher than its cost. Scalar substitution, stack allocation, and lock elimination can be done after escape analysis. However, 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 tool in real-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. As far as I know, 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.

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, instead of moving the intern string cache and static variables to the metadata area, they are allocated directly on the heap, so this is also consistent with the previous point: object instances are allocated on the heap.

The summary of this chapter

The young generation is the region where objects are born, grow, and die, where an object is created, applied, and finally collected by the garbage collector to end its life.

Older generations of objects with long life cycles are usually Java objects copied from survivor zones. Of course, there are special cases where we know that ordinary objects will be assigned to tLabs; If the object is large, the JVM will try to allocate it directly elsewhere in Eden. If the object is too large to find enough contiguous free space in the new generation, the JVM allocates it directly to the old generation. When GC occurs only in the young generation, the act of reclaiming objects from the young generation is called MinorGc.

When GC occurs in an older age, it is called MajorGc or FullGC. In general, MinorGc occurs much more frequently than MajorGC, meaning that garbage collection will occur much less frequently in older generations than in younger ones.