Deep understanding of JVM memory allocation strategies

Posted by mimilaw123 on Sat, 21 Sep 2019 18:44:34 +0200

Understanding JVM memory allocation strategies

Three Principles + Guarantee Mechanism

There are three main principles and guarantee mechanisms for JVM memory allocation mechanism
Specifically as follows:

  • Priority assigned to eden area
  • Big object, directly into the older generation
  • Distribution of long-term survivors to older generations
  • Space Allocation Guarantee

Objects are assigned on Eden first

How do we verify that objects are assigned first on Eden?

Print memory allocation information

The first code is as follows:

public class A {    
    public static void main(String[] args) {
        byte[] b1 = new byte[4*1024*1024];
    }
}

The code is simple by creating a Byte array of 4 Mb in size.
Then we add virtual machine parameters to print garbage collection information while running.

-verbose:gc -XX:+PrintGCDetails

After we run it, the results are as follows.

Heap
PSYoungGen total 37888K, used 6718K [0x00000000d6000000, 0x00000000d8a00000, 0x0000000100000000)
eden space 32768K, 20% used [0x00000000d6000000,0x00000000d668f810,0x00000000d8000000)
from space 5120K, 0% used [0x00000000d8500000,0x00000000d8500000,0x00000000d8a00000)
to space 5120K, 0% used [0x00000000d8000000,0x00000000d8000000,0x00000000d8500000)
ParOldGen total 86016K, used 0K [0x0000000082000000, 0x0000000087400000, 0x00000000d6000000)
object space 86016K, 0% used [0x0000000082000000,0x0000000082000000,0x0000000087400000)
Metaspace used 2638K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 281K, capacity 386K, committed 512K, reserved 1048576K

Manually specify the collector

We can see that the Parallel Scavenge collector is used in the new generation
We can actually specify the virtual machine parameters to select the garbage collector.
For example, the following parameters:

-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC

The results are as follows:

Heap
def new generation total 38720K, used 6850K [0x0000000082000000, 0x0000000084a00000, 0x00000000ac000000)
eden space 34432K, 19% used [0x0000000082000000, 0x00000000826b0be8, 0x00000000841a0000)
from space 4288K, 0% used [0x00000000841a0000, 0x00000000841a0000, 0x00000000845d0000)
to space 4288K, 0% used [0x00000000845d0000, 0x00000000845d0000, 0x0000000084a00000)
tenured generation total 86016K, used 0K [0x00000000ac000000, 0x00000000b1400000, 0x0000000100000000)
the space 86016K, 0% used [0x00000000ac000000, 0x00000000ac000000, 0x00000000ac000200, 0x00000000b1400000)
Metaspace used 2637K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 281K, capacity 386K, committed 512K, reserved 1048576K

In fact, JDK does not default to a Parallel collector, but JDK adapts the garbage collector to various environments.

The code to view the environment is as follows:

java -version


Therefore, according to the server's environment, JDK uses a Paralled collector.

Serial collectors are mainly used on the client side.

Verification of eden allocation

We see that the eden area is 34432K now, using 19%, so can't we expand it 10 times?
Let's verify.

public class A {
    public static void main(String[] args) {
        byte[] b1 = new byte[40*1024*1024];
    }
}

The results are as follows:

Heap
def new generation total 38720K, used 2754K [0x0000000082000000, 0x0000000084a00000, 0x00000000ac000000)
eden space 34432K, 8% used [0x0000000082000000, 0x00000000822b0bd8, 0x00000000841a0000)
from space 4288K, 0% used [0x00000000841a0000, 0x00000000841a0000, 0x00000000845d0000)
to space 4288K, 0% used [0x00000000845d0000, 0x00000000845d0000, 0x0000000084a00000)
tenured generation total 86016K, used 40960K [0x00000000ac000000, 0x00000000b1400000, 0x0000000100000000)
the space 86016K, 47% used [0x00000000ac000000, 0x00000000ae800010, 0x00000000ae800200, 0x00000000b1400000)
Metaspace used 2637K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 281K, capacity 386K, committed 512K, reserved 1048576K

Obviously, we're still working, but eden The area did not increase, but the area in the older generation increased, which corresponds to the characteristics of direct distribution of large objects to the older generation.

So we'll reduce the size of each allocation appropriately.
Here we limit the size of the eden region
The parameters are as follows:

-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8

Here we limit the memory size to 20M
Eden size is 8M

Then we run our code:

The code is as follows:

public class A {
    public static void main(String[] args) {
        byte[] b1 = new byte[2*1024*1024];
        byte[] b2 = new byte[2*1024*1024];
        byte[] b3 = new byte[2*1024*1024];
        byte[] b4 = new byte[4*1024*1024];
        System.gc();
    }
}

The results are as follows:

[GC (Allocation Failure) [DefNew: 7129K->520K(9216K), 0.0053010 secs] 7129K->6664K(19456K), 0.0053739 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (System.gc()) [Tenured: 6144K->6144K(10240K), 0.0459449 secs] 10920K->10759K(19456K), [Metaspace: 2632K->2632K(1056768K)], 0.0496885 secs] [Times: user=0.00 sys=0.00, real=0.04 secs]
Heap
def new generation total 9216K, used 4779K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 58% used [0x00000000fec00000, 0x00000000ff0aad38, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
tenured generation total 10240K, used 6144K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 60% used [0x00000000ff600000, 0x00000000ffc00030, 0x00000000ffc00200, 0x0000000100000000)
Metaspace used 2638K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 281K, capacity 386K, committed 512K, reserved 1048576K

We can see that in eden Area 8192 K About 8 M
//That's the size of our b4

The original b1, b2, and B3 are 6M and are assigned to tenured generation.

The original Eden area is shown below, after allocation, b1, b2, and B3 are shown below.

At that time we found that we could no longer share.

When we checked the log, we had two GC occurrences.

[GC (Allocation Failure) [DefNew: 7129K->520K(9216K), 0.0053010 secs] 7129K->6664K(19456K), 0.0053739 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (System.gc()) [Tenured: 6144K->6144K(10240K), 0.0459449 secs] 10920K->10759K(19456K), [Metaspace: 2632K->2632K(1056768K)], 0.0496885 secs] [Times: user=0.00 sys=0.00, real=0.04 secs]

While in

[DefNew: 7129K->520K(9216K), 0.0053010 secs] 7129K->6664K(19456K), 0.0053739 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 

As we can see, the objects just allocated are not recycled.

The GC above is for the new generation.

The FullGC below is for older generations.

If we need to allocate another 4m of memory at this time, the virtual machine defaults to placing the original eden area where it can be placed, that is, in older generations

So this happens to us.

This is the whole process.Verify that the object has a current Eden zone recycle

Big Objects Directly Into Older Generations

Specify parameters for large objects.

-XX:PretenureSizeThreshold

Test code:

-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
public class A {
    private static int M = 1024*1024;
    public static void main(String[] args) {
        byte[] b1 = new byte[8*M];
    }
}

The results are as follows:

Heap
def new generation total 9216K, used 1149K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 14% used [0x00000000fec00000, 0x00000000fed1f718, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 8192K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 80% used [0x00000000ff600000, 0x00000000ffe00010, 0x00000000ffe00200, 0x0000000100000000)
Metaspace used 2637K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 281K, capacity 386K, committed 512K, reserved 1048576K

As we can see, the result number throws 8M directly into the older generation.
And when we changed it to 7M

Found 7M all thrown into eden.
If we set the parameters, we will see that the result has changed.

The parameters are as follows:

-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=6M

The results are as follows:

We will find that 7M has entered the older generation.

Long-term survival into the elderly

The parameters are as follows:

-XX:MaxTenuringThreshold

Every time it is recycled, if it is not recycled, the age of the object is + 1

If the object's age reaches the threshold, it will enter the older generation.

The exact test is the same as Max above.No more space.

Space Allocation Guarantee

The parameters are as follows:

-XX:+HandlePromotionFailure

The steps are as follows:

  • This ability is measured first before it can be assigned.
  • If this capability is available, then this parameter is a'+'which proves that the memory guarantee is turned on, otherwise the'-' is not turned on.

Summary:

JVM memory allocation strategies are not particularly complex. As long as you follow the virtual machine step by step, you can understand the mechanism of JVM memory allocation.

Topics: Java jvm JDK