美文网首页
JVM GC分析

JVM GC分析

作者: 星冉子 | 来源:发表于2019-09-29 20:26 被阅读0次

    GC简介

    GC(垃圾回收)是针对Java堆内存,Java堆是被所有线程共享的一块内存区域,所有对象实例和数组都在堆上进行内存分配。为了进行高效的垃圾回收,虚拟机把堆内存划分成新生代、老年代和永久代3个区域。

    堆内存

    新生代:由 Eden 与 Survivor Space(S0,S1)构成,大小通过-Xmn参数指定,Eden 与 Survivor Space 的内存大小比例默认为8:1,可以通过-XX:SurvivorRatio 参数指定,比如新生代为10M 时,Eden分配8M,S0和S1各分配1M。

    Eden:大多数情况下,对象在Eden中分配,当Eden没有足够空间时,会触发一次Minor GC,虚拟机提供了-XX:+PrintGCDetails参数,告诉虚拟机在发生垃圾回收时打印内存回收日志。

    Survivor:意思为幸存者,是新生代和老年代的缓冲区域。当新生代发生GC(Minor GC)时,会将存活的对象移动到S0内存区域,并清空Eden区域,当再次发生Minor GC时,将Eden和S0中存活的对象移动到S1内存区域。

    存活对象会反复在S0和S1之间移动,当对象从Eden移动到Survivor或者在Survivor之间移动时,对象的GC年龄自动累加,当GC年龄超过默认阈值15时,会将该对象移动到老年代,可以通过参数-XX:MaxTenuringThreshold 对GC年龄的阈值进行设置。

    老年代:老年代的空间大小即-Xmx 与-Xmn 两个参数之差,用于存放经过几次Minor GC之后依旧存活的对象。当老年代的空间不足时,会触发Major GC/Full GC,速度一般比Minor GC慢10倍以上。

    永久代:JDK8已不存在。

    GC 是一个“停止所有处理(stop the world)”的事件,它意味着除了 GC 线程自身外,其他所有执行线程都将处于挂起状态。


    GC触发条件

    Minor GC/Young GC:从年轻代空间(包括 Eden 和 Survivor 区域)回收内存;

    触发条件:当Eden区满时,触发Minor GC。 

    Major GC/Full GC:一般指对老年代GC(整个堆GC);由于出现Full GC的时候经常伴随至少一次的Minor GC,而许多 Major GC 是由 Minor GC 触发的,所以不用太区分;

    触发条件

    1. 调用System.gc时,系统建议执行Full GC,但是不必然执行;

    2. 老年代空间不足;

    3. 方法区空间不足;

    4. 通过Minor GC后进入老年代的平均大小大于老年代的可用内存;

    5. 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小;

     对象进入老年代的常见的4个时机:

    1. 躲过15次gc,达到15次高龄之后进入老年代;

    2. 动态年龄判定规则,如果Survivor区域内年龄1+年龄2+年龄3+年龄n的对象总和大于Survivor区的50%,此时年龄n以上的对象会进入老年代,不一定要达到15岁;

    3.如果一次Young GC后存活对象太多无法进入Survivor区,此时直接进入老年代;

    4.大对象直接进入老年代;

    GC算法

    判断对象是否存活算法:GC动作发生之前,需要确定堆内存中哪些对象是存活的,一般有两种方法:引用计数法和可达性分析法。

    引用计数法:在对象上添加一个引用计数器,每当有一个对象引用它时,计数器加1,当使用完该对象时,计数器减1,计数器值为0的对象表示不可能再被使用。引用计数法实现简单,判定高效,但不能解决对象之间相互引用的问题。

    可达性分析法:通过一系列称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,搜索路径称为 “引用链”,当一个对象到 GC Roots 没有任何引用链时,意味着该对象可以被回收。以下对象可作为GC Roots:本地变量表中引用的对象(局部变量)、方法区中静态变量引用的对象(static成员变量)、方法区中常量引用的对象(static final常量)、Native方法引用的对象;

    不存活的垃圾对象收集算法:标记-清除、复制和标记-整理。

    标记-清除算法:对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收;(缺点:由于直接回收不存活对象,会造成内存碎片);

    标记-整理算法:基于标记-清除算法,清除后移动存活的对象;(可解决内存碎片)(年老代默认使用

    复制算法:将内存分成大小相等的两块区域,每次使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另一块区域上,然后对该块进行内存回收;(年轻代默认使用

    GC收集器

    Oracle Hotspot JVM中实现了多种垃圾收集器,针对不同的年龄代内存中的对象的生存周期和应用程序的特点,实现了多款垃圾收集器。

    Serial/SerialOld:单线程GC收集器包括Serial和SerialOld这两款收集器,分别用于年轻代和老年代的垃圾收集工作。后来,随着CPU多核的普及,为了更好了利用多核的优势,开发了ParNew收集器,这款收集器是Serial收集器的多线程版本。

    Parallel Scavenge/ParNew/ParallelOld:多线程收集器还包括Parallel Scavenge、ParNew和ParallelOld收集器,这两款也分别用于年轻代和老年代的垃圾收集工作,不同的是,它们是两款可以利用多核优势的多线程收集器。

    CMS:相对来说更加复杂的还有CMS收集器。这款收集器,在运行的时候会分多个阶段进行垃圾收集,而且在一些阶段是可以和应用线程并行运行的,提高了这款收集器的收集效率。

    G1:其中最先进的收集器,要数G1这款收集器了。这款收集器是当前最新发布的收集器,是一款面向服务端垃圾收集器。

    年轻代收集器:Serial收集器、ParNew收集器以及Parallel Scavenge收集器。

    老年代收集:Serial Old收集器、Parallel Old收集器以及CMS收集器。

    查看GC日志

    查看方法

    设置 JVM 参数为,使得控制台或文件能够显示 GC 相关的日志信息;

    -XX:+PrintGC :打印GC详情;

    -XX:+PrintGCDetails :打印GC更详细的信息;

    -Xloggc:[path] :设置gc日志位置;


    输出样例:

    GC日志

    监控GC信息

    监控方法:

    使用jstat命令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控;

    Jstat –class <pid>:显示加载class的数量,及所占空间等信息;

    Jstat –gc <pid>:显示gc相关的堆信息,查看gc的次数,及时间;

    Jstat -gcutil <pid>:统计gc信息;

    Jstat –gccause <pid>:统计gc信息(同gcutil),同时显示最后一次或当前正在发生的垃圾回收的诱因;

    Jstat –gc –h2 <pid> 3000 10:每2次打印一次表头,每3秒输出一次结果,一共输出10次;

    输出样例:

    控制台输出

    一些术语的中文解释:

    S0C:年轻代中第一个survivor(幸存区)的容量 (字节)

    S1C:年轻代中第二个survivor(幸存区)的容量 (字节)

    S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节)

    S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节)

    EC:年轻代中Eden(伊甸园)的容量 (字节)

    EU:年轻代中Eden(伊甸园)目前已使用空间 (字节)

    OC:Old代的容量 (字节)

    OU:Old代目前已使用空间 (字节)

    PC:Perm(持久代)的容量 (字节)

    PU:Perm(持久代)目前已使用空间 (字节)

    YGC:从应用程序启动到采样时年轻代中gc次数

    YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)

    FGC:从应用程序启动到采样时old代(全gc)gc次数

    FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)

    GCT:从应用程序启动到采样时gc用的总时间(s)

    NGCMN:年轻代(young)中初始化(最小)的大小 (字节)

    NGCMX:年轻代(young)的最大容量 (字节)

    NGC:年轻代(young)中当前的容量 (字节)

    OGCMN:old代中初始化(最小)的大小 (字节)

    OGCMX:old代的最大容量 (字节)

    OGC:old代当前新生成的容量 (字节)

    PGCMN:perm代中初始化(最小)的大小 (字节)

    PGCMX:perm代的最大容量 (字节)

    PGC:perm代当前新生成的容量 (字节)

    S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比

    S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比

    E:年轻代中Eden(伊甸园)已使用的占当前容量百分比

    O:old代已使用的占当前容量百分比

    P:perm代已使用的占当前容量百分比

    S0CMX:年轻代中第一个survivor(幸存区)的最大容量 (字节)

    S1CMX :年轻代中第二个survivor(幸存区)的最大容量 (字节)

    ECMX:年轻代中Eden(伊甸园)的最大容量 (字节)

    DSS:当前需要survivor(幸存区)的容量 (字节)(Eden区已满)

    TT: 持有次数限制

    MTT : 最大持有次数限制

    GC优化

    如何看待GC优化:

    1. 并非所有基于Java的应用都需要进行GC优化;

    2. GC优化是最后的手段

    3. 开发过程中需要关注一些小事情,避免"养成气候"之后难以驾驭,如使用StringBuilder或StringBuffer代替String等;

    4. 有些场景我们也无能为力,比如XML和JSON的解析会消耗大量的内存,但是又不太可能不使用XML和JSON,只能任由内存被消耗;

    5. 如果在几次参数调整后内存使用情况有所改善,你就可以进行GC优化了;

    如何优化:

    1. 监控GC并分析是否需要优化,比如GC时间太长超过几秒,会导致服务器请求超时,则可以考虑优化,如果GC时间都很短则没有必要;

    2. 调整影响GC的JVM参数,如内存大小、内存大小比例、垃圾收集器类型等;

    3. 重复第1步,监控并分析结果,如果不满意则停止优化;

    相关文章

      网友评论

          本文标题:JVM GC分析

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