美文网首页
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