深入jvm 07. 垃圾回收(二)垃圾收集器

垃圾收集器种类:
新生代GC:Serial、ParNew、Parallel Scavenge
老年代GC:Serial Old、Parallel Old、CMS
整堆GC:G1

组合关系:
在这里插入图片描述

Serial Old是CMS失败的后备预案;
jdk 8 将Serial+CMS、ParNer+Serial Old这两个组合声明为废弃,jdk 9 完全取消了这些组合的支持;
jdk 14 弃用Parallel Scavenge+Serial Old组合,并删除了CMS GC。

查看默认的垃圾收集器:
① -XX:+PrintCommandLineFlags 查看命令行相关参数(含收集器)
② 使用命令行指令:jinfo -flag 相关垃圾回收器参数 进程id

一、Serial收集器

Serial收集器是HotSpot中Client模式下的默认新生代垃圾收集器。采用复制算法、串行回收和"Stop-The-World"机制进行垃圾回收。Serial Old收集器,作用于老年代,也是单线程,会发生STW,使用标记-压缩算法。当使用 -XX:+UseSerialGC 参数时,等价于新生代使用Serial GC,老年代使用Serial Old GC。
在这里插入图片描述

优点:与其他收集器的单线程相比,简单高效。对于限定单个CPU的环境来说,Serial收集器不存在线程交互的开销,专心做垃圾收集工作。

缺点:是单线程的收集器,不仅意味着它只会使用一个CPU或一条收集线程去做垃圾收集,更重要的是它在垃圾收集时,必须暂停其他所有工作线程,直到垃圾收集结束。对于交互性强的应用,不适合用串行的收集器。

应用场景:在用户的桌面应用场景中,可用内存一般不大(几十MB到一两百MB),可以在较短时间内完成垃圾收集,只要不频繁发生,使用串行回收器是可以接受的。

二、ParNew收集器

ParNew收集器可以看作是Serial收集器的多线程版本,它采用并行回收的方式执行内存回收,也用了复制算法、"Stop-The-World"机制。是jvm在server模式下的新生代的默认垃圾收集器。
在这里插入图片描述

优点:在多CPU环境下,可以充分利用物理硬件资源,更快速地完成垃圾收集,提升吞吐量。

缺点:如果在单CPU场景下,ParNew收集器不如Serial收集器高效。因为Serial收集器是串行回收的,不需要频繁地做任务切换,可以避免多线程交互过程中的额外开销。

三、Parallel Scavenge 收集器

Parallel Scavenge 收集器作用于新生代,同样采用复制算法、并行回收和STW机制。与ParNew不同的是,Parallel Scavenge收集器的目标是达到可控制的吞吐量,因此也被成为吞吐量优先的收集器。通过高效地利用CPU时间,提升程序的吞吐量。

应用场景:适合在后台运算而无需太多交互的任务,比如服务器环境中的批量处理、订单处理、科学计算等应用程序。在吞吐量优先的应用场景中,使用 Parallel 和 Parallel Old 收集器(作用于老年代,也是并行回收,采用标记-压缩算法)的组合,在Server模式下回收性能不错。java 8 中默认为此收集器。

参数设置:
-XX:+UseParallelGC(新生代收集器) ,-XX:+UseParallelOldGC(老年代收集器)这两个参数只要开启一个,另一个自动开启。

-XX:ParallelGCThreads 设置新生代并行收集线程数,通常设置为与CPU数量相等的数值。这属于CPU密集型任务,设置的线程数太多,只会增加线程切换的成本。

-XX:MaxGCPauseMillis 设置GC最大停顿时间(STW时间),在服务端不适合把停顿时间设置过短,会影响整体的吞吐量。

-XX:GCTimeRatio 垃圾收集时间占总时间的比例,即衡量吞吐量的大小,取值范围(0, 100),默认99,即垃圾回收时间不超过1%。

-XX:+UseAdaptiveSizePolicy 设置自适应调节策略,该模式下,新生代的大小、Eden和Survivor的比例、晋升老年代的对象年龄等参数会自动调整,从而达到一个平衡点。在手动调优比较困难的场合,可以使用这种自适应方式。

五、CMS收集器(Concurrent-Mark-Sweep)

CMS收集器是 jdk 1.5 推出的强交互型垃圾收集器,是一款作用于老年代的并发收集器,第一次实现让垃圾收集器线程和用户线程同时工作。它的目标是尽可能缩短垃圾收集时用户线程的停顿时间。采用标记-清除算法,也有STW。

应用场景:比较重视服务响应速度的场景中,希望停顿时间最短,以给用户带来好的体验。

执行过程
初始标记:此阶段中,通过STW机制,标记处GC Roots能直接关联到的对象。因为直接关联的对象较小,所以这里速度很快。标记完成后,用户线程会恢复工作。

并发标记:从GC Roots的直接关联对象开始探索所有引用到的对象,这个过程耗时长但可以和用户线程并发工作。

重新标记:并发标记阶段中,用户线程继续运作,会导致一部分对象的标记记录发生变动,此阶段是为了修正变动的标记记录。本阶段停顿时间长于初始标记阶段,但远低于并发标记阶段。

并发清除:清理标记阶段判断为死亡的对象,释放内存。为了可以和用户线程并发运行,采用清除算法,不用移动对象,这样保证了用户线程能够继续执行不受影响。

注意点:由于在垃圾收集阶段用户线程并没有从头到尾停止运作(只中断两次较短时间),所以在使用CMS回收过程中,应该确保用户线程还有足够的内存可用。当堆内存使用率达到某个阈值时,就要开始回收了。如果CMS回收过程中,预留内存无法满足程序需要,就会出现一次"Concurrent Mode Failure",java虚拟机将临时启用Serial Old收集器来重新进行老年代收集,停顿时间将变长。

缺点
①会产生内存碎片。由于采用的是清除算法,所以会出现内存碎片问题。在无法分配大对象的情况下,不得不提前触发Full GC。

②CMS收集器对CPU资源非常敏感。在并发阶段,垃圾回收线程是和用户线程并发工作的,所以会导致占用了一部分CPU时间而使用户程序变慢,总吞吐量降低。

③CMS无法处理浮动垃圾。可能出现"Concurrent Mode Failure"而导致另一次 Full GC 的产生。在并发标记阶段,用户线程和垃圾收集线程是并发工作的,那么此时产新的垃圾对象,CMS是不能对它们进行标记的,只能等到下一次执行垃圾回收时才能释放这些空间。

参数设置
-XX:+UseConcMarkSweepGC 使用CMS收集器,开启后会默认将 -XX:+UseParNewGC 打开(无法配套使用Parallel Scavenge)

-XX:CMSInitiatingOccupanyFranction 设置堆内存使用率的阈值。jdk 6 及以上版本默认值为92%。如果内存增长比较缓慢,则可以设置较大的值,这样能够降低CMS触发的频率,从而改善应用程序的性能。而如果内存使用率增长很快,则应该降低这个阈值,避免触发老年代使用Serial Old收集器。

-XX:+UseCMSCompactAtFullCollection 用于指定在执行完Full GC后对内存空间进行压缩整理,避免内存碎片的产生。内存压缩无法并发执行,所以停顿时间会变更长。

-XX:+CMSFullFCsBeforeCompaction 设置执行多少次Full GC后对内存空间进行压缩。

-XX:ParallelCMSThreads 设置CMS的线程数。默认线程数为( ParrallelGCThreads+3)/4,ParrallelGCThreads是新生代并行收集的线程数。CMS线程数会影响到用户线程的执行性能。

垃圾收集器小结:
如果想最小化使用内存和并行开销,选择Serial GC;
如果想最大化吞吐量,选择Parallel GC;
如果想最小化停顿时间,选择CMS GC。

六、G1收集器

G1收集器,是一款并行回收器,它把堆内存分割为很多region(物理上不需要连续),使用不同的region来表示Eden、Survivor和老年代。G1 可以避免在整个堆进行垃圾回收,它可以根据各个Region里面垃圾堆积的价值大小,即回收所获得空间大小以及回收所需时间的经验值,在后台维护一个垃圾回收的优先列表。每次根据允许的收集时间,优先回收价值最大的Region。
在这里插入图片描述

G1 面向服务端应用,主要针对配备多核CPU及大容量内存的机器,极高概率满足GC停顿时间的同时,还兼具高吞吐量的性能特征。jdk 9 以后的默认垃圾回收器,jdk 8 中通过 -XX:+UseG1GC开启。

使用G1时,java堆会被划分成2048个大小相同的region,每个region的大小根据堆的实际大小决定,一般在1~32MB之间且为2的N次幂。每个region可以根据需要扮演Eden、Survivor或者Old区,图中空白的region表示未使用的内存空间。

对于堆中的大对象,默认直接被分配到老年代,但如果是短期存在的大对象,就会对垃圾收集器造成负面影响。因此,G1 收集器新增了一种新的内存区域Humongous,主要用来存储大对象,如果对象大小超过1.5个region,就放入Humongous区。如果1个H区也放不下这个对象,则会寻找连续的H区来分配。G1的大多数行为都把H区作为老年代的一部分来看待。

每个region对应一个记忆集,用来处理一个region中的对象被其他region中对象引用的情况,以避免全堆扫描。

G1的两种GC模式:新生代GC和混合GC
1)新生代GC
当Eden区用尽时开始新生代回收过程。此阶段会暂停所有应用程序线程,启动多线程执行回收。
在这里插入图片描述

扫描存活对象
除static变量指向的对象,以及局部变量等,还包含记忆集中的对象,作为GC Roots。

处理记忆集
识别被老年代对象指向的Eden中的对象,这些对象是存活对象。

复制对象
这个阶段,对象树被遍历。Eden区存活的对象被复制到空的区域作为新的Survivor,原Survivor区中存活的对象如果年龄到达未达阈值,则年龄加1后复制到新Survivor区,达到阈值则被复制到Old区。如果Survivor区空间不够,Eden中的部分对象会直接晋升到老年代。

处理引用
处理Soft、Weak、Phantom、Final、JNI Weak等引用。

2)混合GC(新生代GC+老年代标记)
当应用运行开始时,堆内存可用空间还比较大,只会在年轻代满时,触发年轻代收集;随着老年代内存增长,当到达IHOP阈值-XX:InitiatingHeapOccupancyPercent(老年代占整堆比,默认45%)时,G1开始着手准备收集老年代空间,过程如下:
初始标记:标记GC Roots直接关联的对象,这个阶段是STW的,但是耗时很短,而且是借用Minor GC的时候同步完成的,所以这个阶段实际并没有额外的停顿。

并发标记:从GC Roots开始进行可达性分析,递归扫描整个堆的对象图,找出要回收的对象。耗时较长,但可以跟用户线程并发执行。

重新标记:处理并发阶段结束后扔遗留下来的最后少量的SATB(Snapshot At The Beginning)记录,原始快照SATB用来解决并发扫描时对象消失的问题,它记录了并发阶段删除对象引用关系的操作,可以简单地理解为在并发标记阶段,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来搜索。是STW的,但耗时较短。

筛选回收:更新region的统计数据,对各个region的回收价值和成本进行排序,根据用户期望的停顿时间来制定回收计划。此阶段识别到的所有空闲分区,即发现无存活对象的分区直接回收,无需等待下次收集周期。是STW的。

随后G1并不会马上开始一次混合收集,而是让应用线程先运行一段时间,等待触发一次年轻代收集。在接下来的年轻代收集时,将会有老年代分区加入到回收集中,即触发混合回收

与CMS相比,G1的优点:
①、region之间采用复制算法,但整体实际上可以看作是标记-压缩算法,这样不会产生内存碎片。
②、可以精确的控制停顿时间。能让使用者明确指定一个长度为M毫秒的时间片内,消耗在垃圾回收上的时间不超过 N 毫秒。

与CMS相比,G1的弱点:
G1为了垃圾收集产生的内存占用、以及程序运行时的额外执行负载都比CMS高。因此,在小内存应用上,CMS优于G1;大内存应用上,G1优于CMS。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值