JVM GC整理

堆内存模型

几种GC

  • Minor/Young GC: 只收集 Young区的 GC
    每进行一次Young GC,Survivor区内活跃对象将加一岁,达到一定年龄将移入OLD区

  • Old GC: 只收集 Old区的 GC,只有垃圾收集器 CMS 的 concurrent collection 是这个模式

  • Full GC: 收集整个堆,包括新生代,老年代,永久代(JDK1.8及以后为 metaspace 元空间)等所有部分的模式

  • Mixed GC: 收集整个 Young Gen 以及部分 old gen 的 GC,只有垃圾收集器 G1 有这个模式

GC时会stop the world

1、避免无法彻底清理干净
2、GC的工作必须在一个能确保一致性的快照中进行

申请内存大致过程

1、创建对象后,将尝试在Eden申请一片内存
2、若Eden区内存空间足够,则申请结束
3、若Eden区内存空间不足,进行YoungGC(触发清理YOUNG区不活跃对象,活跃对象满足一定条件的晋升到OLD区)
4、若OLD的空间不足(触发阈值或新晋对象等行为导致不足)则触发FullGC
5、若FullGC后,Eden区仍不能满足申请的内存,则触发OOM(out of memory)

GC收集算法(GC收集器都是基于这些算法来实现)

复制(Copying)算法

YoungGC基本都是基于该算法,所以直接以YoungGC的过程来简述该算法

假定正在使用的是S0区,S1区是空闲的
将YOUNG区中活跃的对象复制到S1区(两个Survivor区交替使用,永远有一个是空的,留给下次复制)并清理Eden区和S0区的对象。若活跃对象大于S1区时,部分对象将会直接进入OLD区,部分仍留在S1区
缺点:浪费内存
优点:无内存碎片,对象存活率低时效率高于另外几种
适用:对象存活率低的

标记-清除(Mark-Sweep)算法

从根开始标记存活的对象,然后重新扫一遍将未标记的对象清除同时清除标记
缺点:
产生大量内存碎片,容易导致大内存对象无足够空间,提前触发GC
效率低(全堆对象遍历)stop the world的时间比较长

标记-整理(Mark-Compact)算法

从根开始标记存活的对象,然后整理到一端,其余清除
缺点:效率低(全堆对象遍历与内存整理)
优点:有效利用内存无碎片

年轻代一般适用复制算法,年老代一般适用标记-整理算法

GC的几种收集器

GC收集器-有连线的两种收集器可搭配使用

年轻代收集器

Serial

这是最早的新生代收集器,也是jdk1.5之前默认的收集器。它是基于复制算法实现的,单线程,需要stop the world,所以新生代不能太大,否则对于停顿来讲是比较影响交互响应的。
-XX:+UseSerialGC 使用该收集器

Parallel New

-XX:ParallelGCThreads=NNN 来指定 GC 线程数。 其默认值为CPU内核数。

这是对单线程的Serial的一种改进,ParNew收集器是并行的,在多CPU的场景下会有比串行收集器更好的性能,除此之外,实现算法跟Serial完全一样。需要注意的是,如果CPU数量为1个或2个,该种收集器的性能并不会比Serial要好,因为线程之间的切换会产生额外的开销

Parallel Scavenge

-XX:MaxGCPauseMillis
控制垃圾收集最大停顿时间(毫秒),收集器将尽力保证内存回收花费的时间不超过设定值(设置过小会导致新生代空间变小,导致GC更频繁,总GC时间反而变长、吞吐量下降)

-XX:GCTimeRatio
设置吞吐量大小(0-100),GC时间 <= 1 / (1 + 吞吐量) * 的系统运行花时间。(-XX:GCTimeRatio=99,表示GC时间不超过总时间的1%)

-XX:+UseAdaptiveSizePolicy
自适应策略开关开关参数,不需要手工指定(-Xmn,-XX:SurvivorRatio、-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量

采用的也是复制算法,它与前两种收集器最大的区别是,它关注的是吞吐量而不是延迟。也被称为是吞吐量优先的收集器。其中,吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
主要使用场景:主要适合在后台运算而不是太多交互的任务,高吞吐量则可以最高效率的利用CPU时间,尽快的完成程序的运算任务

年老代收集器

Serial Old

这个是jdk1.2以前的默认收集器,使用的是【标记-整理】算法,单线程,stop the world

Parallel Old

Parallel Scavenge的老年代版本,使用的是【标记-整理】算法。目前它跟Parallel Scavenge的配合是吞吐量优先场景的优先选择。

CMS

-XX:+UseCMSCompactAtFullCollection,应用于在FULL GC后再进行一个碎片整理过程
-XX:+CMSFullGCsBeforeCompaction,多少次不压缩的full gc后来一次带压缩的
-XX:+UseCMSInitiatingOccupancyOnly=true,使用手动指定回收百分比
-XX:CMSInitiatingOccupancyFraction=92,对内存占用率达到92%提前GC(因为CMS产生浮动垃圾)

CMS,Concurrent Mark Sweep,这是一款真正的并发收集器。前面的都是讲的并行。使用的是【标记-清除】算法
收集过程:
1、初始标记,stop the world,只做GC Root可达性的初始标记
2、并发标记,并发执行,进行GCRoots Tracing(可达性分析)过程,时间很长
3、重新标记,stop the world,标记第二步中变动的对象,时间较长
4、并发清除,并发执行,回收内存空间

缺点:
内存碎片,提前触发一次full gc
产生浮动垃圾,由于清除是并发的,这时用户产生的垃圾无法在本次收集过程中收集掉

收集器 G1

参考:
详文
官方
详文

-XX:+UseG1GC,使用G1收集器
-XX:G1HeapRegionSize,一个Region的大小2的幂
-XX:MaxGCPauseMillis,期望GC停顿时间(毫秒)
-XX:ParallelGCThreads,并行STW的线程数(与逻辑处理器的数量相同)
-XX:ConcGCThreads,并行标记的线程数(ParallelGCThreads的1/4)
-XX:InitiatingHeapOccupancyPercent=60,YoungGC后当整个堆占用超过60%时,就会触发并发GC周期
-XX:G1HeapWastePercent=10,并发标记后可回收的垃圾占比,小于则不回收,10%
-XX:G1MixedGCCountTarget=8,一个周期内触发Mixed GC最大次数,8次
-XX:G1NewSizePercent=35,新生代比例下限,35%
-XX:G1MaxNewSizePercent=60,新生代比例上限,60%
-XX:MetaspaceSize=256m

G1收集器将内存划分了多个区域(Region)
区域的类型有Eden,Survivor,Old,Humongous,除了Humongous外,其余和之前介绍的一样意思
Humongous:用于存放巨型对象(超过Region大小的50%),若一个存不下将找多个连续的Humongous一起。一开始直接存在Old的空间,但G1有优化会在YoungGC对其进行回收(从1.8的某个版本开始)

  • YoungGC
    回收Eden、Survivor、Humongous
    由于会回收Humongous,所以Old的内存空间会有减少,但并不是回收了Old对象
    调整eden与old的占比

  • MixedGC
    每当YoungGC后会根据JVM参数(IHOP等)判断当前是否需要开始进行MixedGC

    MixedGC主要可以分为两个阶段:
    1、全局并发标记(global concurrent marking)

    全局并发标记又可以进一步细分成下面几个步骤:

    • 初始标记(initial mark,STW)。它标记了从GC Root开始直接可达的对象。初始标记阶段借用young GC的暂停,因而没有额外的、单独的暂停阶段。
    • 并发标记(Concurrent Marking)。这个阶段从GC Root开始对heap中的对象标记,标记线程与应用程序线程并行执行,并且收集各个Region的存活对象信息。过程中还会扫描上文中提到的SATB write barrier所记录下的引用。
    • 最终标记(Remark,STW)。标记那些在并发标记阶段发生变化的对象,将被回收。
    • 清除垃圾(Cleanup,部分STW)。这个阶段如果发现完全没有活对象的region就会将其整体回收到可分配region列表中。 清除空Region。

    2、拷贝存活对象(Evacuation)

    Evacuation阶段是全暂停的。它负责把一部分region里的活对象拷贝到空region里去(并行拷贝),然后回收原本的region的空间。Evacuation阶段可以自由选择任意多个region来独立收集构成收集集合(collection set,简称CSet),CSet集合中Region的选定依赖于上文中提到的停顿预测模型,该阶段并不evacuate所有有活对象的region,只选择收益高的少量region来evacuate,这种暂停的开销就可以(在一定范围内)可控

  • 错误的设置将会导致无法按期望的进行MixedGC,从而导致FullGC的出现

注意IHOP、G1MaxNewSizePercent、G1NewSizePercent的关系
eden的占比要小于1-IHOP才能触发MixedGC
(G1NewSizePercent ~ G1MaxNewSizePercent) < 1 - IHOP

已知1
    mixedGC触发条件:
        current heap / total heap > IHOP 
        (current eden + current old) / total heap > IHOP
    mixedGC前总是会触发YoungGC:
        current eden = 0

    综上满足该条件时会触发mixedGC:
        current old / total heap > IHOP

已知2
    年老代 = 年老代占比 * 总堆
        total old = (1 - total eden percent) * total heap


假设期望年老代使用占比达到 A 时触发mixedGC,则
    current old = total old * A

将 已知2 代入 假设 得
    current old = (1 - total eden percent) * total heap * A

再代入 已知1 得
    (1 - total eden percent) * total heap * A / total heap > IHOP

简化得
    IHOP < (1 - total eden percent) * A

total eden percent 取值范围: G1NewSizePercent ~ G1MaxNewSizePercent
为保证能正常mixedGC,total eden percent 取 G1MaxNewSizePercent

最终
    IHOP < (1 - G1MaxNewSizePercent) * A

GC日志

-Xloggc:./tmp/gc-%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps
-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-Xloggc:../logs/gc.log 日志文件的输出路径

%t 后缀格式生成的时间戳格式为 YYYY-MM-DD_HH-MM-SS。因此,生成的日志文件名看起来像这样 gc-2019-01-29_20-41-47.log

你可能感兴趣的:(JVM GC整理)