美文网首页JVM学习JVM · Java虚拟机原理 · JVM上语言·框架· 生态系统JVM
GC(7)、读懂GC日志信息以及对象在堆中是怎么回事?

GC(7)、读懂GC日志信息以及对象在堆中是怎么回事?

作者: 编程界的小学生 | 来源:发表于2017-09-08 16:04 被阅读110次

    一、读懂GC日志信息
    先来看看日志信息

    Paste_Image.png
    一张图读懂每个日志的单词是什么意思 Paste_Image.png

    二、堆的划分

    Paste_Image.png

    三、每个区域讲解
    1、Eden区
    大多数情况下对象优先在Eden分配(意思就是大多数情况Eden区就是对象的出生地)。

    2、from/to
    当Eden区快要满的时候,会触发一次Minor GC,并会将还存活的对象放到from或to。from和to是等大小的,当其中一个快慢的时候就会将两个区域的数据对换,所以一定会有一个区域是空的。

    说明:
    上面提到了Minor GC,那么Minor GC和Full GC到底有什么区别?

    • 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为java对象大多都具备朝生夕灭的特性,所以Minor GC会非常频繁,一般回收速度比较快。

    • 老年代GC(Major GC / Full GC):指发生在老年代的GC,出现了Major GC,速度会非常慢,比MinorGC慢10倍以上。一般内存溢出都是因为Full GC,都是老年代的溢出。

    Demo
    运行参数
    -verbose:gc -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8
    这些参数在之前篇幅中都详细说过,意思是设置堆内存最小最大空间为20M,设置新生代大小10M,总共20M,意思老年代也是10M,设置新生代中Eden和一个Survivor(from或to)区空间比例是8:1(默认就是这个),并打印详细信息

    /**
     * @author TongWei.Chen 2017-09-08 15:11:53
     */
    public class HelloDefNew {
        private static final int _1MB = 1024 * 1024;
    
        public static void main(String[] args) {
            byte[] allocation1 = null;
            byte[] allocation2 = null;
            byte[] allocation3 = null;
            byte[] allocation4 = null;
    
            allocation1 = new byte[2 * _1MB];
            allocation2 = new byte[2 * _1MB];
            allocation3 = new byte[2 * _1MB];
            //下面这句会出现一次Minor GC,因为我们的参数是20 20 10,意味着新生代和老年代各占10M
            allocation4 = new byte[4 * _1MB];
    
        }
    
    }
    

    结果

    [GC (Allocation Failure) [PSYoungGen: 6424K->808K(9216K)] 6424K->4912K(19456K), 0.0054701 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    Heap
     PSYoungGen      total 9216K, used 7189K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
      eden space 8192K, 77% used [0x00000000ff600000,0x00000000ffc3b6f0,0x00000000ffe00000)
      from space 1024K, 78% used [0x00000000ffe00000,0x00000000ffeca020,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 3509K, capacity 4498K, committed 4864K, reserved 1056768K
      class space    used 389K, capacity 390K, committed 512K, reserved 1048576K```
    

    与我们预期的一样,发生了Minor GC。并且GC前新生代大小是6424K(因为我们new了6M的byte),GC后变成了808K。新生代堆总大小9216K(我们设置的10M),整个堆总大小19456K(我们设置的20M)。

    那么下面Heap开头的那一坨是什么意思呢?
    一张图搞定他

    Paste_Image.png

    3、老年代

    • 大对象直接进入老年代。所谓大对象就是大量连续内存空间的java对象(比如特别长的字符串或者数据,new byte[10*1024*1024];

    Demo
    运行参数
    -verbose:gc -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8

    /**
     * @author TongWei.Chen 2017-09-08 15:11:53
     */
    public class HelloDefNew {
        private static final int _1MB = 1024 * 1024;
    
        public static void main(String[] args) {
            byte[] allocation = null;
            allocation = new byte[9 * _1MB];
        }
    }
    

    结果

    Heap
     PSYoungGen      total 9216K, used 2492K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
      eden space 8192K, 30% used [0x00000000ff600000,0x00000000ff86f3a0,0x00000000ffe00000)
      from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
      to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
     ParOldGen       total 10240K, used 9216K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
      object space 10240K, 90% used [0x00000000fec00000,0x00000000ff500010,0x00000000ff600000)
     Metaspace       used 3509K, capacity 4498K, committed 4864K, reserved 1056768K
      class space    used 389K, capacity 390K, committed 512K, reserved 1048576K
    

    不难发现,老年代空间直接占用了90%。直接进入了年老代。

    • 长期存活的对象将进入老年代
      什么叫长期存活的对象?
      既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放到新生代,哪些对象应放在老年代中。
      虚拟机给每个对象定义了一个对象年龄(Age)计数器,若对象在Eden中出生并经历过第一次Minor GC后仍然存活,并且能被Survivor(from或to)容纳的话,将被移动到Survivor空间中,并且对象年龄设为1,对象每此经历过Minor GC后仍然在Survivor中存活的话,就将该对象的Age+1,那到底什么时候才转移动老年代?答案是Age等于15的时候会晋升到老年代中。(15的默认的,可以通过-XX:MaxTenuringThreshold设置)

    Demo1:-XX:MaxTenuringThreshold=1
    运行参数
    -verbose:gc -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1

    /**
     * @author TongWei.Chen 2017-09-08 15:46:29
     */
    public class HelloOldGen {
    
        private static final int _1MB = 1024 * 1024;
    
        public static void main(String[] args) {
            byte[] allocation1 = null;
            byte[] allocation2 = null;
            byte[] allocation3 = null;
            byte[] allocation4 = null;
    
            allocation1 = new byte[_1MB / 4];
            //什么时候进入老年代取决于-XX:MaxTenuringThreshold参数
            allocation2 = new byte[4 * _1MB];
            allocation3 = null;
            allocation4 = new byte[4 * _1MB];
        }
    
    }
    

    结果

    Heap
     PSYoungGen      total 9216K, used 6844K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
      eden space 8192K, 83% used [0x00000000ff600000,0x00000000ffcaf3c0,0x00000000ffe00000)
      from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
      to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
     ParOldGen       total 10240K, used 4096K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
      object space 10240K, 40% used [0x00000000fec00000,0x00000000ff000010,0x00000000ff600000)
     Metaspace       used 3509K, capacity 4498K, committed 4864K, reserved 1056768K
      class space    used 389K, capacity 390K, committed 512K, reserved 1048576K
    

    可以发现当设置为1的时候,第二次GC进入了老年代,新生代的Survivor区直接变为了0%已用。

    Demo2:-XX:MaxTenuringThreshold=15
    运行参数
    -verbose:gc -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1

    /**
     * @author TongWei.Chen 2017-09-08 15:46:29
     */
    public class HelloOldGen {
    
        private static final int _1MB = 1024 * 1024;
    
        public static void main(String[] args) {
            byte[] allocation1 = null;
            byte[] allocation2 = null;
            byte[] allocation3 = null;
            byte[] allocation4 = null;
    
            allocation1 = new byte[_1MB / 4];
            //什么时候进入老年代取决于-XX:MaxTenuringThreshold参数
            allocation2 = new byte[4 * _1MB];
            allocation3 = null;
            allocation4 = new byte[4 * _1MB];
        }
    
    }
    

    结果
    会发现Survivor的from区占用XX%的空间,并没有完全释放掉。

    动态对象年龄判定
    若在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

    4、永久代
    存放类信息。

    四、总结
    对象创建若不是大对象则直接进入Eden区,Eden区即将满了的时候会进入from/to区,默认经过几次GC后对象仍然存活的话会进入年老代,并清空from/to区。若是大对象则直接进入年老代。
    Full GC很影响性能,Full GC一般只发生在年老代。

    若有兴趣,欢迎来加入群,【Java初学者学习交流群】:458430385,此群有Java开发人员、UI设计人员和前端工程师。有问必答,共同探讨学习,一起进步!
    欢迎关注我的微信公众号【Java码农社区】,会定时推送各种干货:


    qrcode_for_gh_577b64e73701_258.jpg

    相关文章

      网友评论

        本文标题:GC(7)、读懂GC日志信息以及对象在堆中是怎么回事?

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