美文网首页
JVM垃圾回收实践

JVM垃圾回收实践

作者: 得力小泡泡 | 来源:发表于2020-12-21 23:29 被阅读0次

例子1

package com.gc;

public class MyTest1 {
    public static void main(String[] args) {
        int size = 1024 * 1024;
        byte[] myAlloc1 = new byte[2 * size];
        byte[] myAlloc2 = new byte[2 * size];
        byte[] myAlloc3 = new byte[2 * size];

        System.out.println("hello world");
    }
}

设置vm参数

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

-verbose:gc ,表示会输出GC的详细日志
-Xms20M ,堆初始的大小
-Xmx20M ,堆最大的大小
上面两个参数会经常一起用,表示指定堆的容量大小。通常会指定一样的值是为了避免在JVM执行垃圾回收之后不会产生内存抖动的问题
-Xmn10M ,指定新生代的大小是10M
-XX:+PrintGCDetails ,表示打印GC的详细信息
-XX:SurvivorRatio=8,代表新生代的区域是8:1:1,也就是Eden空间和Survivor空间的占比是8:1。

输出

[GC (Allocation Failure) [PSYoungGen: 6445K->904K(9216K)] 6445K->5008K(19456K), 0.0035960 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
hello world
Heap
 PSYoungGen      total 9216K, used 3191K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 27% used [0x00000000ff600000,0x00000000ff83bf58,0x00000000ffe00000)
  from space 1024K, 88% used [0x00000000ffe00000,0x00000000ffee2020,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 4104K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 40% used [0x00000000fec00000,0x00000000ff002020,0x00000000ff600000)
 Metaspace       used 3467K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 378K, capacity 388K, committed 512K, reserved 1048576K
对日志输出进行逐个分析

1、(Allocation Failure):表示触发GC的原因,Eden空间不够,对象分配内存失败
2、[PSYoungGen: 6445K->904K(9216K)]:PS代表使用的是Parallel Scavenge收集器,YoungGen表示新生代,也就是在新生代是采用PS收集器进行垃圾回收。6445K代表执行垃圾回收之前新生代存活的对象所占据的空间为6445K,在执行垃圾回收之后新生代存活的对象所占据的空间为904k。9216代表的是新生代总容量是9216k,也就是9M(Eden + from space)
3、6445K->5008K(19456K),6445代表执行垃圾回收之前总的堆空间大小,5008K代表执行垃圾回收之后总的堆空间大小,19456K代表总的堆空间大小,也就是19M
4、[Times: user=0.00 sys=0.00, real=0.00 secs]
分别代表执行GC在用户空间用了0.00秒,在内核空间用了0.00秒,真正执行的空间为0.00秒
5、PSYoungGen:使用Parallel Scavenge垃圾收集器对新生代进行垃圾回收
6、ParOldGen :使用Parallel Old垃圾收集器对老年代进行垃圾回收

 PSYoungGen      total 9216K, used 3191K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 27% used [0x00000000ff600000,0x00000000ff83bf58,0x00000000ffe00000)
  from space 1024K, 88% used [0x00000000ffe00000,0x00000000ffee2020,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)

表示年轻代垃圾回收之后的情况,total表示新生代总容量是9216k,used表示存活对象所占据的空间是3191k,后面分别是eden,from,to的占据情况,存活对象占了自己空间的百分之多少
6、

ParOldGen       total 10240K, used 4104K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)

total表示老年代总容量是10240k,used表示存活对象所占据的空间是4104k。

让我们来计算一下这里的4104k是怎么来的
[PSYoungGen: 6445K->904K(9216K)] 6445 - 904 = 5541,新生代释放的空间容量
6445K->5008K(19456K),可以知道6445 - 5008 = 1437,总的堆空间释放的容量
5541 - 1437 = 4104k

那有个疑问:为啥总的堆空间释放的大小还不如新生代释放的大小呢?
这里需要注意:对于新生代的释放其实分为两种:一是真正的被回收的,二是没有真正被回收,而是该对象进升到老年代了,而堆空间的释放值代表是真正被释放的大小。
所以新生代释放的大小-总堆释放的大小则就是从新生代进升到老年代的容量大小。

例子2

设置vm参数

-verbose:gc 
-Xms20M
-Xmx20M
-Xmn10M
-XX:+PrintGCDetails
-XX:SurvivorRatio=8
package com.gc;

public class MyTest1 {
    public static void main(String[] args) {
        int size = 1024 * 1024;
        byte[] myAlloc1 = new byte[2 * size];
        byte[] myAlloc2 = new byte[3 * size];
        byte[] myAlloc3 = new byte[3 * size];
        byte[] myAlloc4 = new byte[3 * size];

        System.out.println("hello world");
    }
}

输出

[GC (Allocation Failure) [PSYoungGen: 7469K->888K(9216K)] 7469K->6016K(19456K), 0.0153944 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 888K->0K(9216K)] [ParOldGen: 5128K->5929K(10240K)] 6016K->5929K(19456K), [Metaspace: 3458K->3458K(1056768K)], 0.0181083 secs] [Times: user=0.00 sys=0.00, real=0.02 secs] 
hello world
Heap
 PSYoungGen      total 9216K, used 6384K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 77% used [0x00000000ff600000,0x00000000ffc3c2c8,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 5929K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 57% used [0x00000000fec00000,0x00000000ff1ca4e0,0x00000000ff600000)
 Metaspace       used 3466K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 378K, capacity 388K, committed 512K, reserved 1048576K

研究Full GC
1、Ergonomics是GC的一个原因,它产生的直接后果就是会出现Stop The World,简单STW这种情况,也就是业务线程会暂停一段时间,等它完成GC之后业务线程才会继续的恢复执行,所以,在日常的JVM的调优中应该尽最大努力避免出现Full GC的情况,因为它会导致新生代、老年代、元空间都会进行垃圾回收
2、ParOldGen: 5128K->5929K(10240K),老年代的存活对象不降反升,因为老年代在GC时能回收的对象不多,另外不可能是新生代的对象晋升到老年代了
3、[Metaspace: 3458K->3458K(1056768K)],元空间的大小不变,因为它是存放的一些元信息,不会经常变

例子3

设置vm参数

-verbose:gc 
-Xms20M
-Xmx20M
-Xmn10M
-XX:+PrintGCDetails
-XX:SurvivorRatio=8
package com.gc;

public class MyTest1 {
    public static void main(String[] args) {
        int size = 1024 * 1024;
        byte[] myAlloc1 = new byte[2 * size];
        byte[] myAlloc2 = new byte[3 * size];
        byte[] myAlloc3 = new byte[4 * size];
        byte[] myAlloc4 = new byte[4 * size];

        System.out.println("hello world");
    }
}

输出

hello world
Heap
 PSYoungGen      total 9216K, used 7633K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 93% used [0x00000000ff600000,0x00000000ffd74738,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 10240K, used 8192K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 80% used [0x00000000fec00000,0x00000000ff400020,0x00000000ff600000)
 Metaspace       used 3467K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 378K, capacity 388K, committed 512K, reserved 1048576K

对比例子2,例子3设置的空间大小比例子的大,反而没有进行full GC
原因:如果在新生代中已经无法容纳下一个新创建的对象的话,该新对象会直接在老年代来诞生

例子4

阈值和垃圾收集器类型对于对象分配的影响实战分析

超过某一定的空间值,会在老年代进行生成,而不会在新生代生成

相关文章

网友评论

      本文标题:JVM垃圾回收实践

      本文链接:https://www.haomeiwen.com/subject/tbslnktx.html