JVM-垃圾回收

垃圾回收算法

在这里插入图片描述

分代收集理论

当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几 块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。 比如在新生代中,每次收集都会有大量对象(近99%)死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可 以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选 择“标记-清除”或“标记-整理”算法进行垃圾收集。注意,“标记-清除”或“标记-整理”算法会比复制算法慢10倍以 上。

标记-复制算法

为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的 内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对 内存区间的一半进行回收。
在这里插入图片描述

标记-清除算法

算法分为“标记”和“清除”阶段:标记存活的对象, 统一回收所有未被标记的对象(一般选择这种);也可以反过来,标 记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象 。它是最基础的收集算法,比较简单,但是会带来 两个明显的问题:

  1. 效率问题 (如果需要标记的对象太多,效率不高)
  2. 空间问题(标记清除后会产生大量不连续的碎片)

在这里插入图片描述

标记-整理算法

根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回 收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
在这里插入图片描述

垃圾收集器

垃圾回收算法是理论,垃圾收集器是理论的具体实现;
在这里插入图片描述

Serial收集器

Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它 的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工 作的时候必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。
新生代采用复制算法,老年代采用标记-整理算法。

虚拟机的设计者们当然知道Stop The World带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短 (仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。 但是Serial收集器有没有优于其他垃圾收集器的地方呢?当然有,它简单而高效(与其他收集器的单线程相比)。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。
Serial Old收集器是Serial收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在JDK1.5 以及以前的版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案。 在这里插入图片描述

ParNew收集器

ParNew收集器应该算是Serial收集器的多线程版本,在垃圾收集阶段也会STW,只是由多个线程并发进行垃圾收集任务;
新生代采用复制算法,老年代采用标记-整理算法。只有ParNew能够与CMS配合使用
在这里插入图片描述

parallel 和 parallerOld

Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)。CMS等垃圾收集器的关注点更多的是用户线程的停 顿时间(提高用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。 Parallel 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,可以 选择把内存管理优化交给虚拟机去完成也是一个不错的选择。
新生代采用复制算法,老年代采用标记-整理算法。
Parallel Old收集器是Parallel Scavenge收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器(JDK8默认的新生代和老年代收集 器)。

在这里插入图片描述

CMS垃圾收集器

Cms垃圾收集器是以获取最短停顿时间为目的的垃圾收集器。是hostspot第一款真正意义上的并发收集器,首次实现用户线程与垃圾收集线程并发执行
主要的工作流程有以下几点:
1.初始标记:以GcRoots为起点标记直接引用的对象;
2.并发标记:从GcRoots关联的对象开始遍历整个引用链的过程,这个过程时间比较长,但是可以与用户线程并发执行。 也正是因为与用户线程并发执行,可能导致已经标记过的对象状态发生变动; 如 标记为垃圾的对象又建立起引用了(漏标),标记为非垃圾的对象失去引用了(夺标)
3.重新标记:重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对 象的标记记录,这个阶段 的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。
主要用到三 色标记里的增量更新算法(见下面详解)做重新标记。
4.并发清理:开启用户线程,同时GC线程开始对未标记的区域做清扫。 由于用户线程与GC线程并发执行,在这个阶段可能出现新创建的对象 此时这些对象都标记为黑色,也可能出现某些对象失去引用变为垃圾对象这种称为浮动垃圾,只能下次GC再去清理
5.并发重置:重置本次GC过程中的标记数据。
在这里插入图片描述
CMS垃圾收集器的主要优点是:并发收集,低停顿;
缺点是:1.与用户线程并发执行CPU资源紧张
2.基于标记-清除算法实现,会出现大量的空间碎片,当没有连续空间存储一个大对象时,只能触发Full GC。 可以通过参数设置是 否每次清理都整理碎片
3.在并发阶段会出现浮动垃圾,这部分垃圾只能下次GC再去清理了
4.由于是并发执行,在进行GC时需要预留部分空间给用户线程, 如果在未完成GC时,用户线程又产生了大量对象 触发FullGC,会产生Concurrent mode failure,CMS会STW 专心的做垃圾收集 并且垃圾收集器使用Serial Old;

CMS的相关核心参数

  1. -XX:+UseConcMarkSweepGC:启用cms
  2. -XX:ConcGCThreads:并发的GC线程数
  3. -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)
  4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一 次
  5. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)
  6. -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设 定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整
  7. -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,目的在于减少老年代对年轻代的引 用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在标记阶段
  8. -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW
  9. -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;

垃圾回收的底层算法

三色标记,采用黑色,灰色,白色 进行对象的标记。黑色表示这个对象的所有引用都标记过了,灰色表示这个对象还有未标记的引用,白色代表未扫描过,也就是垃圾对象;

增量更新 和 原始快照都可以来解决漏标的问题;

卡表,卡页可以来解决跨代引用的问题; 跨代引用,老年代的对象需要引用新生代的对象,或新生代的对象需要引用老年代的对象;
如:A对象包含b属性, A对象处于老年代 b属性引用的对象处于新生代,就存在跨代引用问题。

三色标记

public class ThreeSign {


    public static void main(String[] args) {
        //初始标记
        A a=new A();

        //并发标记开始---
        D d=a.b.d;
        a.b.d=null;
        //并发标记结束---
        a.d=d;
    }


    static class A{
        private B b=new B();
        private D d=null;
    }

    static class B{
        private C c=new C();
        private D d=new D();
    }

    static class C{

    }

    static class D{

    }
}

拿上面这段代码来画一套图;
在这里插入图片描述
漏标会导致被引用的对象被当成垃圾误删除,这是严重bug,必须解决,有两种解决方案:
增量更新(Incremental Update) 和原始快照(Snapshot At The Beginning,SATB)

增量更新就是当黑色对象插入新的指向白色对象的引用关系时, 就将这个新插入的引用记录下来, 等并发扫描结束之 后, 再将这些记录过的引用关系中的黑色对象为根, 重新扫描一次。 这可以简化理解为, 黑色对象一旦新插入了指向 白色对象的引用之后, 它就变回灰色对象了。
原始快照就是当灰色对象要删除指向白色对象的引用关系时, 就将这个要删除的引用记录下来, 在并发扫描结束之后, 再将这些记录过的引用关系中的灰色对象为根, 重新扫描一次,这样就能扫描到白色的对象,将白色对象直接标记为黑 色(目的就是让这种对象在本轮gc清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾) 以上无论是对引用关系记录的插入还是删除, 虚拟机的记录操作都是通过写屏障实现的。
所谓的写屏障,其实就是指在赋值操作前后,加入一些处理(可以参考AOP的概念):

记忆集与卡表

在分代收集理论下,进行YoungGC时可能存在跨代引用的问题,如在老年代中某个对象的一个属性 引用了年轻代的对象,那么在进行youngGC时如何判断这个年轻代对象是否为垃圾对象? 总不能把老年代也扫描一遍吧? 由此引入记忆集的概念,卡表就是记忆集的具体实现。 可以理解成将老年代分成一个个卡页,每个卡页中如果存在老年代对象中的属性引用年轻代的对象,就将卡页标记一下,年轻代维护卡表对应着老年代中的每一个卡页,在进行youngGC的时候只需要判断卡表中哪一个卡页被标记了,单独去扫描卡页内的对象即可;

跨代引用

在这里插入图片描述

G1垃圾收集器

G1收集器是一款以可控的STW时间 来获取最多的回收内存的收集器,适合用于操作核心比较多,内存较大(>8G)的情况
在这里插入图片描述
G1 将java堆划分成一个个Region,每个Region大小必须是2的N次幂(限制为1M-32M),默认有2048个Region,那么内存可以是2G-64G。
G1 存在逻辑上的分区概念如:Eden,Survivor,Old,Humongous(大对象),不再保持物理上的隔阂 它们之间可以不是连续的,G1将大于Region 一半的对象会放在Humongous;
G1 中每个Region并不是固定的逻辑分区 可能通过youngGC,MiexdGC,FullGC 后分区的角色就改变了;
注意:之前提到的对象优先进入Eden,youngGc时 依据对象年龄,动态年龄判断这些规则依然适用;
G1 的工作流程:
1.初始标记,与CMS相同
2.并发标记,与CMS相同
3.最终标记,与CMS相同也是来处理并发标记阶段的漏标问题,但是这里采用的解决方案是原始快照
4.筛选回收,会STW。 这里的STW是可控的,一般为200MS,G1收集器会计算出在200MS内一个最大的收益,会优先回收收益比较大的Region

G1的回收算法主要采用复制算法,回收时将标记为存活的对象转移到其他Region 再将当前Region清空,那么被转移的Region就成了新的逻辑分区。
在这里插入图片描述

G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region(这也就是它的名字 Garbage-First的由来),比如一个Region只有10M垃圾,另外一个Region有20M垃圾,在回收时间有限情况下,G1当然会优先选择后面这个Region回收。这种使用Region划分内存空间以及有优先级的区域回收 方式,保证了G1收集器在有限时间内可以尽可能高的收集效率。

G1收集器具有以下特点:
并发收集:
可控的停顿时间:
不会产生空间碎片:

G1的垃圾回收分类:

以往的垃圾回收概念只有YoungGC,FullGC,但是G1增加了一种MixedGC
YoungGc: 当Eden区放满之后并不会立马进行YoungGc,而是比较本次youngGC的预估时间与设置的预期STW时间, 如果预估时间 小于 STW时间很多,那么将未使用的Region转为Eden区继续存放,只有当Eden区内的回收时间与STW差不多时,才会进行youngGC;
MixedGC:当Old区的Region已经占用了所有Region的某个比例(-XX:InitiatingHeapOccupancyPercent 依据这个值设定),进行新生代,老年代,大对象区 的回收;采用复制算法,在可控的时间内将存活对象复制到空的Region中,并清理垃圾对象。 注意:如果这时没有足够的Region来存储存活对象将触发FullGC
FullGC: 类似用Serial进行回收,STW 单线程专心做垃圾回收;

G1收集器参数设置

-XX:+UseG1GC:使用G1收集器
-XX:ParallelGCThreads:指定GC工作的线程数量
-XX:G1HeapRegionSize:指定分区大小(1MB~32MB,且必须是2的N次幂),默认将整堆划分为2048个分区
-XX:MaxGCPauseMillis:目标暂停时间(默认200ms)
-XX:G1NewSizePercent:新生代内存初始空间(默认整堆5%)
-XX:G1MaxNewSizePercent:新生代内存最大空间
-XX:TargetSurvivorRatio:Survivor区的填充容量(默认50%),Survivor区域里的一批对象(年龄1+年龄2+年龄n的多个 年龄对象)总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代;
-XX:MaxTenuringThreshold:最大年龄阈值(默认15)
-XX:InitiatingHeapOccupancyPercent:老年代占用空间达到整堆内存阈值(默认45%),则执行新生代和老年代的混合 收集(MixedGC),比如我们之前说的堆默认有2048个region,如果有接近1000个region都是老年代的region,则可能 就要触发MixedGC了
-XX:G1MixedGCLiveThresholdPercent(默认85%) region中的存活对象低于这个值时才会回收该region,如果超过这 个值,存活对象过多,回收的的意义不大

G1垃圾收集器优化建议

假设参数 -XX:MaxGCPauseMills 设置的值很大 导致YoungGC不能触发,年轻代可能都占用了堆内存的60%了,此时才触发年轻代gc。
那么存活下来的对象可能就会很多,此时就会导致Survivor区域放不下那么多的对象,就会进入老年代中。
或者是你年轻代gc过后,存活下来的对象过多,导致进入Survivor区域后触发了动态年龄判定规则,达到了Survivor 区域的50%,也会快速导致一些对象进入老年代中。

什么场景适合使用G1

1.内存大于8G时
2.重视用户体验,需要严格控制STW的时间
3.堆内存存活对象占用50%以上

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值