jvm gc实践

gc实践

1.
编写代码如下

1
2
3
4
5
6
7
8
public static void main(String[] args) {
int size = 1024 * 1024 ;
byte[] alloc = new byte[2 * size] ;
byte[] alloc2 = new byte[2 * size] ;
byte[] alloc3 = new byte[2 * size] ;
byte[] alloc4 = new byte[2 * size] ;
System.out.println("hello world");
}

jvm参数配置:

vm option : 
-verbose:gc // 详细gc日志
-Xms20m // 堆初始大小
-Xmx20m // 堆最大值
-Xmn10m //新生代10m
-XX:+PrintGCDetails // 详细垃圾回收信息
-XX:SurvivorRatio=8 // eden 和 survivor 占比 为 8 : 1 : 1

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
[GC (Allocation Failure) [PSYoungGen: 6651K->480K(9216K)] 6651K->6632K(19456K), 0.0092862 secs] [Times: user=0.04 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 480K->0K(9216K)] [ParOldGen: 6152K->6434K(10240K)] 6632K->6434K(19456K), [Metaspace: 2642K->2642K(1056768K)], 0.0048153 secs] [Times: user=0.02 sys=0.01, real=0.01 secs]
hello world
Heap
PSYoungGen total 9216K, used 2374K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
eden space 8192K, 28% used [0x00000007bf600000,0x00000007bf851838,0x00000007bfe00000)
from space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
to space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
ParOldGen total 10240K, used 6434K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
object space 10240K, 62% used [0x00000007bec00000,0x00000007bf248a90,0x00000007bf600000)
Metaspace used 2650K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 286K, capacity 386K, committed 512K, reserved 1048576K

观测执行结果,我们发现,发生了一次minor gc,原因是 Allocation Failure

PSYoungGen : parallel scavenge 发生在新生代的gc 。

[PSYoungGen: 6651K->480K(9216K)] 6651K->6632K(19456K), 0.0092862 secs]
,minor gc 只会对新生代进行回收。

在年轻代gc前 存活对象占据的大小为6651 gc后为480 ,总容量 9216

gc前堆空间存活对象占据的大小为6651 执行后为6632 , 总容量为 19456

发生了一次 full gc : fullgc 会针对新生代,老年代,和元空间都进行gc 。

fullgc 应该尽量避免 。 老年代空间存对象反而增加了 。

  1. 直接在老年代去分配对象 。

    在第一个例子的基础上 ,增加jvm启动参数:

    -XX:PretenureSizeThreshold=3145728// 1024 * 1024 * 3
    -XX:+UseSerialGC
    设定当新创建的对象超过我们设置的阈值,那么就会直接在老年代去分配内存。
    这个参数要搭配 串行gc收集器才会起作用

代码:

1
2
3
4
public static void main(String[] args) {
int size = 1024 * 1024 ;
byte[] alloc = new byte[size * 4];
}

输出结果:

1
2
3
4
5
6
7
8
9
Heap
def new generation total 9216K, used 656K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
eden space 8192K, 8% used [0x00000007bec00000, 0x00000007beca4028, 0x00000007bf400000)
from space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
to space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
tenured generation total 10240K, used 5136K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
the space 10240K, 50% used [0x00000007bf600000, 0x00000007bfb04020, 0x00000007bfb04200, 0x00000007c0000000)
Metaspace used 2648K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 286K, capacity 386K, committed 512K, reserved 1048576K
设置可以晋升到老年代的对象最大存活年龄

-XX:MaxTenuringThreshold=5

-XX:+PrintTenuringDistribution

我们设置 最大的新生代年龄超过5 的时候就晋升到老年代,注意:(有可能2岁就晋升,取决于jvm的实现,但是最大不超过我们设置的5 )

在jvm中默认值为15 ,cms垃圾收集器中为6,G1中为15 (在jvm中使用4 bit 来标识年龄,1111 ,所以最大年龄为15)

经历过多次 minior GC 以后,存活的对象会在 from survivor 和 to survivor 中来回移动, 而且有一个前提是要有足够的控件来存放这些数据 。 在GC算法中会计算每个对象的年龄大小,当达到某个年后以后发现总大小已经超过 一个survivor空间的50% ,那么会调整阈值,而不是等到默认的年龄才完成晋升。

实战:MaxTenuringThreshold阈值动态调整
1
2
3
4
5
6
7
8
9
10
-verbose:gc
-Xmx200M
-Xmn50M
-XX:TargetSurvivorRatio=60
-XX:+PrintTenuringDistribution
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:MaxTenuringThreshold=3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test04 {
public static void main(String[] args) {
int size = 1024 * 1024 ;
byte[] alloc = new byte[512 * 1024];
byte[] alloc2 = new byte[512 * 1024];
gc();
System.out.println("1111");
gc();
System.out.println("2222");
gc();
System.out.println("3333");
gc();
System.out.println("4444");
gc();
System.out.println("55555");
}

static void gc() {
for (int i = 0; i < 30; i++) {
byte[] alloc = new byte[1024 * 1024];
}
}
}