JVM series (6) runtime data area (heap)

JVM series (6) runtime data area (heap)

1. Overview of the core of the heap

  • There is only one heap memory for a JVM instance, and the heap is also the core area of Java memory management
  • The Java heap area is created when the JVM starts, and its space size is determined. It is the largest memory space managed by the JVM
    • The size of the heap memory can be adjusted

Code test 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 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 code

The two pieces of code are exactly the same, only the heap space set at runtime is different. After executing the two programs at the same time, open jvisualvm (you can execute the jvisualvm command directly in the terminal under mac). Double-click HeapDemo1 Process Viewer visual GC (When you first run jvisualvm, you can select Tools -> Plug-ins , plug-in installation-related visual GC)

You can see in the figure that for the HeapDemo1 process, the total size of the marked Eden, Survivor0, Survivor1 and Old Gen areas is 10m

And the heap HeadDemo2 process, the total size of each part is 20m:

  • The "Java Virtual Machine Specification" stipulates that the heap can be in a physically discontinuous memory space, but logically it should be regarded as continuous
  • All threads share the Java heap, where you can also divide the thread private buffer (Thread Local Allocation Buffer, TLAB)
  • The description of Java in the "Java Virtual Machine Specification" is: 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)
    • One point should be emphasized here: it should be that "almost" all object instances are allocated memory here-from the perspective of actual use
  • Arrays and objects may never be stored on the stack (except for "allocation on stack") , because a reference is stored in the stack frame, and this reference points to the location of the object or array in the heap
  • After the method ends, the objects in the heap will not be removed immediately, they will only be removed during garbage collection
  • Heap is the key area for GC to perform garbage collection

Code demo:

package com.nasuf.jvm; public class SimpleHeap { private int id; public SimpleHeap ( int id) { this .id = id; } public void show () { 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 code

1.1 Memory breakdown

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

  • Java7 and before, the heap memory is logically divided into three parts: newborn area + senior care area + permanent area
    • Young Generation Space (Young/New)
      • It is divided into Eden area and Survivor area
    • Tenure Generation Space (Old/Tenure)
    • Permanent Space (Perm)
  • Java8 and later, the heap memory is logically divided into three parts: newborn area + senior care area + meta space
    • Young Generation Space (Young/New)
      • It is divided into Eden area and Survivor area
    • Tenure Generation Space (Old/Tenure)
    • Meta Space (Meta)

Convention: Newborn area = New generation = Young generation, Senior care area = Senior area = Old generation, Permanent area = Permanent generation

If the above code is executed with parameters

-XX:+PrintGCDetails
, You can see the following information 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 287 K , capacity 386 K , committed 512 K , reserved 1048576 K Copy code

If you switch to JDK7, the following information will be output:

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 ) 21504K Space Object, 12 is % Used [ 0x00000007f9980000 , 0x00000007f9c182c0 , 0x00000007fae80000 ) copy the code

2. Set the heap memory size and OOM

  • The Java heap area is used to store Java object instances, so the size of the heap is already set when the JVM starts, and you can select
    -Xmx
    with
    -Xms
    To set up
    • -Xms
      Used to represent the starting memory of the heap area (ie young generation + old generation), equivalent to
      -XX:InitialHeapSize
      • -X
        Is the operating parameter of jvm
      • ms
        : memory start
    • -Xmx
      Used to represent the maximum memory of the heap area (that is, the young generation + the old generation), which is equivalent to
      -XX:MaxHeapSize
      • mx
        : memory max
  • Once the memory size in the heap area exceeds the maximum memory specified by -Xmx, it will throw
    OutOfMemoryError
    abnormal
  • Usually the two parameters -Xms and -Xmx are configured with the same value, the purpose is to be able to calculate the size of the heap area after the Java garbage collection mechanism cleans up the heap area, thereby improving performance
  • by default
    • Initial memory size: physical computer memory size
      /64
    • Maximum memory size: physical computer memory size
      /4

Code demonstration (output initial memory settings):

package com.nasuf.jvm; public class HeapSpaceInitial { public static void main (String [] args) { //return the Java virtual machine heap total memory Long initialMemory = Runtime.getRuntime () totalMemory The ()/. 1024/1024 ; //Returns the Java Virtual Machine The maximum amount of heap memory 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( "The system memory size is:" + initialMemory * 64.0/1024 + "G" ); System.out.println( "The system memory size is:" + maxMemory * 4.0/1024 + "G" ); } } Copy code

The output is as follows:

-Xms: 245M -Xmx: 3641M The system memory size is: 15. 3125G The system memory size is: 14. A 22265625G copy the code
package com.nasuf.jvm; public class HeapSpaceInitial { public static void main (String [] args) { //return the Java virtual machine heap total memory Long initialMemory = Runtime.getRuntime () totalMemory The ()/. 1024/1024 ; //Returns the Java Virtual Machine The maximum amount of heap memory 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 code

Manually set parameters:

-Xms600m -Xmx600m
After output initialMemory and maxMemory:

-Xms: 575M -Xmx: 575M Copy 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 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

Calculation

(S0C + S1C + EC + OC)/1024 = 600m
That is our initial setting 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 by
575m
The calculated system memory size will also be smaller than the actual system memory size.

In addition, use

-XX:+PrintGCDetails
Parameter operation can also check the memory situation:

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 287 K , Capacity 386 K , committed 512 K , Reserved 1048576 K duplicated code

Note: Among them

PSYoungGen
for
179200K
, The calculation method is also
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 code

Operating parameters:

-Xms600m -Xmx600m
. Use after execution
jvisualvm
middle
Visual GC
Plug-in view memory situation:

can be seen

Old
The area is constantly being filled, until it is filled, no further GC is possible, 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 ) com.nasuf.jvm.OOMTest.main AT (OOMTest.java: 15 ) Copy the code

From the sampler, we can further view the object situation in memory:

3. Young generation and old generation

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

    • One type is instantaneous objects with a short life cycle, and the creation and demise of such objects are very rapid
    • The life cycle of a class of objects is very long, and in some extreme cases it can be consistent with the life cycle of the JVM
  • If the Java heap area is further subdivided, it can be divided into young generation (YoungGen) and old generation (OleGen)

  • Among them, the young generation can be divided into Eden space, Survivor0 space and Survivor1 space (sometimes called from area, to area)

    Note: The following parameters are generally not called during development:

  • Configure the proportion of the young generation and the old generation in the heap structure

    • default
      -XX:NewRatio=2
      , Which means that the new generation accounted for
      1
      , The old age accounts for
      2
      , The new generation occupies the entire heap space
      1/3
    • Can be modified
      -XX:NewRatio=4
      , Which means that the new generation accounted for
      1
      , The old age accounts for
      4
      , The new generation accounts for the entire heap
      1/5
  • In HotSpot, the default ratio of Eden space and the other two Survivor spaces is

    8:1:1

  • Of course, developers can pass the option

    -XX:SurvivorRatio
    To adjust this space ratio. such as
    -XX:SurvivorRatio=8

  • Almost all (not necessarily) Java objects are new in the Eden area (if an object is too large, it may directly enter the old age)

  • Most of the destruction of Java objects takes place in the new generation

    • IBM's special research shows that 80% of the objects in the new generation are living and dying.
  • Available options

    -Xmn
    Set the maximum memory size of the young generation

    • This parameter generally uses the default value; if set at the same time
      -XX:NewRatio
      , Then it will be
      -Xmn
      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 code

Execute the above code, the parameters are

-Xms600m -Xmx600m
;by
jvisualvm
middle
Visual GC
The plug-in can see the following information:

By default, the proportion of young generation and old generation is

1:2

Or you can pass

jinfo -flag
Command to view NewRatio information:

$ jps 13490 Launcher 13491 EdenSurvivorTest 12420 13765 Jps 13213 Main $ jinfo -flag NewRatio 13491 -XX:NewRatio=2 Copy code

From the figure above, it can also be observed that the ratio of Eden:Survivor0:Survivor1 is

6:1:1
Instead of
8:1:1
, This is because of the JVM's memory allocation adaptive mechanism. But if we add
-XX:-UseAdaptiveSizePolicy
To cancel
UseAdaptiveSizePolicy
Mechanism and execute the code, it can be found that it will not take effect. The effective way is to specify
-XX:SurvivorRatio=8
To set the ratio of Eden to Survivor in the new generation:

4. Graphical object allocation process

4.1 Overview of the object allocation process

Allocating memory for new objects is a very rigorous and complex task. JVM designers not only need to consider how and where to allocate memory, but also because the memory allocation algorithm is closely related to the memory reclamation algorithm, they also need to consider whether memory fragments will be generated in the memory space after the GC has performed the memory reclamation.

  • The new object is placed in the Eden area first, this area has a size limit
  • When the space in the Eden area is full, the program needs to create objects again, and the garbage collector of the JVM will garbage collect the Eden area (
    Young GC/Minor GC
    ), destroy the objects in the Eden area that are no longer referenced by other objects, and then load the new objects and put them in the Eden area
    • Note: Young GC will not be triggered when the Survivor area is full, but this does not mean that there is no garbage collection in the Survivor area-when the Young GC occurs, the Eden area and the Survivor area will be garbage collected at the same time
  • Then move the remaining objects in the Eden area to Survivor0 area
  • If you start garbage collection again, the ones that survived last time will be placed in Survivor0 area, if not collected, they will be placed in Survivor1 area
  • If you go through garbage collection again, it will be put back into Survivor0 area at this time, and then go to Survivor1 area
  • When do you go to the old age? You can set the number of times
    -XX:MaxTenuringThreshold=<N>
    , The default is
    15
    Times
  • In the old age, it is relatively leisurely. When the memory is insufficient in the old age, the GC is triggered again:
    Major GC
    , To clean up the memory of the old generation
  • If the old generation performs Major GC and finds that the object still cannot be saved, it will generate
    OOM
    abnormal

4.2 Graphical process

summary:

  • For Survivor0, Survivor1 area: there is exchange after copying, whoever is empty is To
  • Regarding garbage collection: Frequently collected in the young generation, rarely collected in the old generation, almost not collected in the permanent generation/meta space

Code test

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 code

When the program throws an OOM exception:

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

by

jvisualvm
View the waveform diagram of each GC memory as follows:

5. Minor GC, Major GC and Full GC

When JVM performs GC, it does not always reclaim the above three memory areas (new generation, old generation, method area, or meta space) together. Most of the time, the reclaiming refers to the new generation.

For the implementation of HotSpot VM, the GC in it is divided into two types according to the recovery area: one is partial collection (Partial GC) and the other is full collection (Full GC)

  • Partial collection: Garbage collection is not a complete collection of the entire Java heap, which is divided into:
    • Young GC (Minor GC/Young GC): Garbage collection only in the young generation (Eden/S0/S1)
    • Old age collection (Major GC/Old GC): just garbage collection in the old age
      • Currently, only CMS GC will collect the old generation separately
      • Note that in many cases Major GC will be confused with Full GC, and it is necessary to distinguish whether it is collection in the old generation or whole heap collection.
    • Mixed collection (Mixed GC): Collect garbage collection of the entire young generation and part of the old generation
      • Currently, only G1 GC will have this behavior
  • Full GC: Collect garbage collection of the entire Java heap and method area

5.1 The Young Generation Minor GC Trigger Mechanism

  • When the young generation space is insufficient, the Minor GC will be triggered. The young generation full here refers to the Eden area being full, and the Survivor area full will not trigger the GC (Minor GC will clean up the memory of the young generation each time)
  • Because most Java objects have the characteristics of life and death, Minor GC is very frequent, and generally the recovery speed is relatively fast. This definition is clear and easy to understand
  • Minor GC will trigger
    STM (Stop The World)
    , Suspend the threads of other users, wait until the garbage collection is over, the user threads will resume running

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

  • Refers to the GC that occurred in the old age. When the object disappears from the old age, we say that Major GC or Full GC has occurred
  • The emergence of Major GC is often accompanied by at least one Minor GC (but not absolute, there is a direct Major GC strategy selection process in the collection strategy of the Parallel Scavenge collector)
    • That is, when the space in the old generation is insufficient, it will first try to trigger the Minor GC, if the space is not enough later, the Major GC will be triggered
  • The speed of Major GC is generally slower than Minor GC
    10
    Times more, STW time is longer
  • If the memory is not enough after Major GC, throw OOM

5.3 Full GC trigger mechanism

There are five situations that trigger the execution of Full GC:

  • transfer
    System.gc()
    When, the system recommends the implementation of Full GC, but not necessarily
  • Insufficient space in the old age
  • Insufficient method area
  • The average size of the old generation after passing the Minor GC is greater than the available memory of the old generation
  • When copying from Eden area, Survivor0 (From), area to Survivor1 (To) area, the size of the object is larger than the available memory of To Space, then the object is transferred to the old generation, and the available memory of the old generation is less than the size of the object

Note: Full GC is to be avoided as much as possible during development or tuning, so the pause time will be shorter

5.4 GC log analysis

Code test

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 traversals is:" + i); } } } Copy 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->0 K (2560K) ] [ParOldGen: 6647K->5472 K (7168K) ] 9103K->5472 K (9728K) , [Metaspace: 2652K->2652 K (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->0 K (2560K) ] [ParOldGen: 5472K->5447 K (7168K) ] 5472K->5447 K (9728K) , [Metaspace: 2652K->2652 K (1056768K) ) ], 0.0039904 secs] [Times: user = 0.00 sys= 0.00 , real= 0.00 secs] The number of traversal 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 290 K , capacity 386 K , committed 512 K , reserved 1048576 K java . lang . OutOfMemoryError : Java heap space at java . util . Arrays . copyOfRange ( Arrays . java :3664) at java . lang . String .< init > ( String . the Java:207) at java . Lang . StringBuilder . ToString ( StringBuilder . Java :407) at com . Nasuf . Jvm . GCTest . Main ( GCTest . Java :18) Process Finished with Exit code 0 Copy the code

can be seen:

  • A Full GC must be executed before the OOM exception is thrown
  • [GC (Allocation Failure) [PSYoungGen: 2013K->496K(2560K)] 2013K->1055K(9728K), 0.0008150 secs]
    in,
    2013K
    Refers to the size occupied by the new generation before the GC is executed,
    496K
    Refers to the size occupied by the new generation after performing GC,
    2560K
    Refers to the total size of the new generation; the latter
    2013K
    Refers to the size of the heap space occupied before the GC (here is the same as the size occupied by the young generation before the GC is executed, because when the GC is executed for the first time, the old generation does not have any data, the size of the heap space occupied at this time is Size occupied by the Cenozoic), and
    1055K
    Is the size of the heap space occupied after the GC is executed,
    9728K
    Is the size of the entire heap space (that is, the set operating parameters
    -Xms9m -Xmx9m
    )

6. Heap space generation idea

Why do we need to divide the Java heap into generations? Doesn't it work properly regardless of generations?

  • After research, different objects have different life cycles, 70%-99% of objects are temporary objects
    • Cenozoic: Eden, two Survivors (also known as from/to, S0/S1) of the same size, and to is always empty
    • Old generation: store objects that survived many GCs in the new generation
  • In fact, it is completely possible to not divide the generation, the only reason for generation is to optimize the GC performance. If there is no generation, then all the objects are in one place, it is like shutting all the people in a school in a classroom. During the GC, it is necessary to find out which objects are useless, so that all areas of the heap will be scanned. Many objects live and die. If you are generating generations, put the newly created object in a certain place. When GC, first reclaim the object area that is stored for life and death. This will free up a lot of objects. Big room out

7. Memory allocation strategy

If the object was born in Eden and survived the first Minor GC, and can be accommodated by Survivor, it will be moved to the Survivor space, and the age of the object will be set to

1
. Each time an object survives a Minor GC in the Survivor area, its age increases by 1, when its age increases to a certain level (the default is
15
, In fact, each JVM, each GC is different), will be promoted to the old age.

The age threshold for the object to be promoted to the old age can be selected through the option

-XX:MaxTenuringThreshold
To set

The principles of object allocation for different age groups are as follows :

  • Priority allocation to the Eden area

  • Large objects are directly assigned to the old generation

    • Try to avoid too many large objects in the program

    Code test

    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 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 315 K , Capacity 392 K , committed 512 K , Reserved 1048576 K duplicated code

    can be seen

    ParOldGen total 40960K, used 20480K
    , 20m objects are directly allocated to the old generation

  • Long-lived objects are allocated to the old generation

  • Dynamic object age judgment

    • If the total size of all objects of the same age in the Survivor area is greater than half of the Survivor space, objects with an age greater than or equal to this age can directly enter the old age without waiting
      MaxTenuringThreshold
      Age required in
  • Space allocation guarantee

    • -XX:HandlePromotionFailure

8. TLAB

Why is there TLAB (Thread Local Allocation Buffer)

  • The heap area is a thread shared area, any thread can access the shared data in the heap area
  • Since the creation of object instances is very frequent in the JVM, it is not thread-safe to divide the memory space from the heap area in a concurrent environment
  • In order to avoid multiple threads operating the same address, it is necessary to use mechanisms such as locking, which in turn affects the allocation speed

What is TLAB?

  • From the perspective of memory model rather than garbage collection, continue to divide the Eden area. JVM allocates a private cache area for each thread, which is contained in the Eden space
  • When multiple threads allocate memory at the same time, the use of TLAB can avoid a series of non-thread safety issues, and at the same time can improve the throughput of memory allocation, so we can call this memory allocation method a fast allocation strategy
  • All known JVMs derived from OpenJDK provide the design of TLAB

TLAB's further explanation:

  • Although not all object instances can successfully allocate memory in TLAB, JVM is indeed the first choice for TLAB memory allocation

  • In the program, developers can select

    -XX:UseTLAB
    Set whether to open TLAB space

    Code verification TLAB is on:

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

    After the program is started, execute it on the command line:

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

    Output

    -XX:+UseTLAB
    Indicates that TLAB is turned on by default

  • By default, TLAB memory space is very small, only takes up the entire space of Eden

    1%
    , Of course we can pass the option
    -XX:TLABWasteTargetPercent
    Set the percentage of Eden space occupied by TLAB space

  • Once the object fails to allocate memory in the TLAB space , the JVM will try to ensure the atomicity of data operations by using the locking mechanism , thereby directly allocating the memory in the Eden space

The whole process of object allocation:

9. Summary of parameter settings for heap space

Official website description https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

  • -XX:+PrintFlagsInitial
    View the default initial values of all parameters
  • -XX:+PrintFlagsFinal
    View the final value of all parameters (there may be modifications, not the initial value)
  • -Xms
    Initial heap space memory (default is the physical memory
    1/64
    )
  • -Xmx
    Maximum heap space memory (default is physical memory
    1/4
    )
  • -Xmn
    Set the size of the young generation (initial value and maximum value)
  • -XX:NewRatio
    Configure the proportion of the young generation and the old generation in the heap structure
  • -XX:SurvivorRatio
    Set the ratio of Eden and S0/S1 space in the new generation
  • -XX:MaxTenuringThreshold
    Set the maximum age of Cenozoic garbage
  • -XX:+PrintGCDetails
    Output detailed GC processing log
    • Print brief GC information:
      • -XX:+PrintGC
      • -verbose:gc
  • -XX:HandlePromotionFailure
    Whether to set up a space allocation guarantee. Before the occurrence of Minor GC, the virtual machine checks whether the maximum available contiguous space in the old generation is greater than the total space of all objects in the new generation
    • If it is greater than, then the Minor GC is safe
    • If it is less, then the virtual machine view
      -XX:HandlePromotionFailure
      Whether the setting value allows guarantee failure
      • in case
        HandlePromotionFailure=true
        , Then it will continue to check whether the maximum available continuous space in the old age is greater than the average size of the objects promoted to the old age
        • If it is greater than, try a Minor GC, but this time Minor GC is still risky
        • If it is less than, then perform a Full GC instead
      • in case
        HandlePromotionFailure=false
        , Then perform a Full GC instead
    After JDK6 Update24 (JDK7),
    HandlePromotionFailure
    The parameters will no longer affect the space allocation guarantee strategy of the virtual machine. Observe the source code changes in OpenJDK, although the source code is also defined
    HandlePromotionFailure
    Parameter, but it is no longer used in the code. The rule after JDK6 Update24 is that as long as the continuous space of the old generation is greater than the total size of the new generation object or the average size of previous promotions, Minor GC will be performed, otherwise Full GC will be performed

10. Escape analysis

There is a description of Java heap memory in "In-depth Understanding of the Java Virtual Machine":

With the development of JIT compilers and the gradual maturity of escape analysis techniques , on- stack allocation and scalar replacement optimization techniques will cause some subtle changes. All objects are allocated to the heap and gradually become less "absolute".

In the Java virtual machine, objects are allocated memory in the Java heap, which is a common sense. However, there is a special case, that is, if it is found after Escape Analysis that an object has no escape method, it may be optimized to be allocated on the stack . In this way, there is no need to allocate memory on the heap, and there is no need for garbage collection. This is also the most common off-heap storage technology

In addition, the aforementioned TaoBaoVM based on OpenJDK deep customization, in which the innovative GCID (GC Invisible Heap) technology implements off-heap, moves Java objects with a long life cycle from the heap to outside of the heap, and GC cannot manage the internal GCIH Java objects in order to achieve the purpose of reducing the frequency of GC recycling and improving the efficiency of GC recycling

10.1 Overview

  • How to allocate objects on the heap to the stack requires the use of escape analysis methods
  • This is a cross-function global data flow analysis algorithm that can effectively reduce the synchronization load and memory heap allocation pressure in Java programs
  • Through escape analysis, the Java HotSpot compiler can analyze the scope of use of a new object reference, so as to decide whether to allocate this object to the heap
  • The basic behavior of escape analysis is the dynamic scope of the analysis object
    • When an object is defined in a method, the object is only used in the method memory, it is considered that no escape has occurred
    • When an object is defined in a method, it is referenced by an external method, it is considered to have escaped. For example, as a call parameter passed to other places

Code example

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

Objects that have not escaped can be allocated on the stack. As the method execution ends, the stack space is removed

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

If the above code wants StringBuffer sb not to 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 code

More escape analysis code examples

package com.nasuf.jvm; /** * Escape analysis * * How to quickly determine whether an escape has occurred depends on whether the object entity of new may be called outside the method */ public class EscapeAnalysis { public EscapeAnalysis obj; /** * The method returns the EscapeAnalysis object, and escape occurs * @return */ public EscapeAnalysis getInstance () { return obj == null ? new EscapeAnalysis(): obj; } /** * Assign values to member attributes and escape occurs */ public void setObj () { this .obj = new EscapeAnalysis(); } /** * Object scope is only valid in the current method, no escape occurs */ public void useEscapeAnalysis () { EscapeAnalysis e = new EscapeAnalysis(); } /** * Quoting the value of a member variable, escape occurs * Note: This example focuses on the object entity of new in the getInstance() method, not the variable e */ public void useEscapeAnalysis2 () { EscapeAnalysis e = getInstance(); //getInstance().xxx() will also escape } } Copy code

parameter settings:

  • After the JDK 6u23 version, the escape analysis has been enabled by default in HotSpot
  • If you are using an earlier version, developers can pass:
    • Options
      -XX:+DoEscapeAnalysis
      Explicitly enable escape analysis
    • Pass option
      -XX:+PrintEscapeAnalysis
      View the screening results of escape analysis

Conclusion: If local variables can be used in development, don t define them outside the method

10.2 Code optimization

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

  • Allocated on the stack. Convert heap allocation into stack allocation. If an object is allocated in a subroutine, so that the pointer to the object will never escape, the object may be a candidate for stack allocation instead of heap allocation
  • Synchronization is omitted. If an object is found to be accessed only from one thread, then the operation of this object can be ignored for synchronization
  • Separate objects or scalar replacements. Some objects may not need to exist as a continuous memory structure to be accessed, so part (or all) of the object may not be stored in the memory, but stored in the CPU register

10.2.1 Allocation on the stack

  • According to the results of escape analysis during compilation, the JIT compiler found that if an object has no escape method, it may be optimized for allocation on the stack. After the allocation is completed, execution continues in the call stack, and finally the thread ends, the stack space is reclaimed, and the local variable object is also reclaimed. This eliminates the need for garbage collection
  • Common escape scenarios
    • Assign values to member variables, method return values, and pass by 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(); } //View execution time long end = System.currentTimeMillis(); System.out.println( "The time spent is:" + (end-start) + "ms" ); //In order to check the number of objects in the heap memory, thread sleep try { Thread.sleep( 1000000 ); } catch (InterruptedException e) { e.printStackTrace(); } } private static void alloc () { //No escape occurred User user = new User(); } static class User {} } Copy code

At this time, the operating parameter is 1G of memory space (large enough), and the escape analysis is turned off

-Xmx1G -Xms1G -XX:-DoEscapeAnalysis
, Output:

Time spent: 86 msCopy code

use

jvisualvm
The sampler in the memory -> memory to view the state of the objects in memory as follows:

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

If the operating parameters are modified to

-XX:+DoEscapeAnalysis
Open the escape analysis and execute the program, you can see that the time for output memory allocation is greatly reduced:

The time taken is: 4 msCopy code

Modify the operating parameters again to reduce the initial and maximum memory space to

256m
And turn off escape analysis
-Xms256m -Xms256m -XX:-DoEscapeAnalysis
The 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.00 secs] Time spent: 55 msCopy code

You can see that the program memory has executed GC

And if you turn on the escape analysis

-XX:+DoEscapeAnalysis
, You can see that GC did not occur (GC does not occur on the stack):

The time taken is: 5 msCopy code

10.2.2 Synchronous omission

  • The cost of thread synchronization is quite high, the consequence of synchronization is to reduce concurrency and performance
  • When dynamically compiling the synchronization block, the JIT compiler can use escape analysis to determine whether the lock object used by the synchronization block can only be accessed by one thread and not released to other threads . If not, then the JIT compiler will cancel the synchronization of this part of the code when compiling this synchronization block. This can greatly improve concurrency and performance. This process of canceling synchronization is called synchronization omission, or lock elimination

Such as the following code:

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

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

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

But note that in the bytecode file of the f() method, we can still see the bytecode instructions related to 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 the monitorexit 27 aload_3 28 athrow 29 return duplicated code

10.2.3 Separated objects or scalar replacement

Scalar refers to data that cannot be broken down into smaller data. The primitive data type in Java is a scalar

In contrast, the data that can be decomposed is called Aggregate . The object in Java is the aggregate, because it can be decomposed into other aggregates and scalars.

In the JIT phase, if it is found that an object cannot be accessed by the outside world after escape analysis, then after JIT optimization, the object will be disassembled into several member variables contained therein instead. This process is scalar substitution . Parameters

-XX:+EliminateAllocations
Open by default, allowing objects to be scattered and allocated on the stack

For example, the following code:

package com.nasuf.jvm; public class ScalarReplace { public static class User { public int id; public String name; } public static void alloc () { User u = new User(); //No escape occurs, perform scalar replacement 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 code

The operating parameters are

-Xms100m -Xmx100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations
, Turn on escape analysis and turn off scalar replacement. At this time, the User object is still allocated to the 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.0003237 secs] Time spent is: 46 MS copy the code

You can see that the object memory allocation time is

46ms
At the same time, GC was performed during operation. And if you turn on scalar replacement
-XX:+EliminateAllocations
Execute again, the output is as follows:

The time taken is: 4 msCopy code

It can be seen that the memory is not GC, and the object allocation time is greatly shortened to

4ms
. Because after scalar substitution is turned on, the method:

public static void alloc () { User u = new User(); //No escape occurs, perform scalar replacement u.id = 5 ; u.name = "nasuf" ; } Copy code

Is actually replaced by:

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

This eliminates the need for heap memory allocation, reduces GC, and directly allocates on the stack.

But it should be noted that the premise of the escape analysis is to start the JVM

server
Mode, i.e. add
-server
Parameters; and the 64-bit virtual machine is turned on by default
server
mode:

$ java -version java version "1.8.0_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)

10.3

  • 1999 JDK1.6
  • JVM , , JVM Oracle HotSpot JVM
  • JDK7 JDK intern intern