怎么选择合适的垃圾回收器

目前为止还没有一款垃圾回收器是完美的,只有找到最适合自己的,下面分别介绍

 

垃圾收集器介绍

1.Serial (-XX:+UseSerialGC-XX:+UseSerialOldGC)

Serial 是串行垃圾收集器,最古老的垃圾收集器之一,单线程的执行是指它在垃圾收集的时候会暂停其他所有工作线程,也就是STW(Stop the word)直到收集完毕后,STW才结束

新生代使用的是复制算法,老年代使用的整理算法

STW的解释:https://blog.csdn.net/q85647842/article/details/106904816

 

2.Paraller Scavenge收集器(-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代))

Paraller Scavenge也就是Serial的多线程版本,除了使用多线程进行垃圾收集外,和Serial类似。它的收集线程是根据CPU核数相同,可以通过XX:ParallelGCThreads来指定线程数,但是一般不推荐

Paraller Scavenge 更关注的高效率的使用CPU,在最短的时间内让线程收集完毕,关注的是整个收集的时间比如它收集对象的时间在200ms,但是CMS整个收集过程花了500MS 但是CMS的STW的时间可能只有50MS。而CMS更关注用户的体验(STW的时间)

JDK1.8默认的新生代和老年代收集器

年轻代使用的是复制算法,老年代使用的整理算法

 

3.ParNew(-XX:+UseParNewGC)


ParNew收集器和Parallel收集器类似,但是它可以和CMS收集器来配合使用。小内存(8G之内)的机器都建议使用ParNew+CMS

新生代使用的是复制算法

4.CMS(-XX:+UseConcMarkSweepGC(old))

CMS(ConcurrentMarkSweep)收集器是一个获取最短回收停顿的STW时间为目标的收集器。它是HotSpot意义上第一款并发收集的垃圾收集器,它实现了让垃圾收集器和用户线程(基本上)同时工作.

初始标记:暂停所有线程STW 快速标记GC Roots. 速度很快

并发标记:以GC Roots的直接关联对象开始遍历整个对象树的过程,整个过程耗费时间很长,但是不影响用户线程。因为用户线程在执行,可能导致标记过的对象状态发生改变

重新标记:暂停所有线程STW 将标记过的 ,修正并发标记期间用户继续执行而导致标记产生变化的那一部分对象的标记记录。这个阶段的时间比初始标记时间长,但是远远比并发标记的时间段,主要用到三色标记中的增量更新算法

并发清理:用户线程与回收线程同时执行,将未标记的的区域进行清扫 这个过程如果有对象新增直接标记为黑色(三色标记https://mp.csdn.net/console/editor/html/107300400

并发重置:重置本次GC过程中的标记数据

优先:可以在回收的时候用户线程不受影响

缺点:

  • 对CPU资源敏感,会和服务抢资源
  • 浮动垃圾,在并发标记和并发清理的时候产生垃圾,这种浮动垃圾只能等到下一次GC的时候才清除
  • 使用的是标记清除算法,会产生大量空间碎片,当然可以通过参数XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做整理
  • 执行过程中的不确定性,可能在垃圾回收的时候还没有回收完,又触发了Full GC 特别容易在并发标记和并发清理的时候出现,这个时候就会STW 由单线程的Serial old 垃圾回收器回收

5.G1 -XX:+UseG1GC

G1将Java堆划分成了多个Region,JVM最多可以拥有2048个Region。一般Region大小等于堆大小除以2048,比如堆大小是4096M那么每个Region等于2M.也可以使用参数修改-XX:G1HeapRegionSize一般推荐使用默认的

G1还是保留了年轻代和老年代的概念,但是不再物理隔离了,它们都是(不连续的)Region集合

年轻代默认占比是百分之5(可配置),最多达到百分之60(可配置),默认8:1:1

一个Region可能现在是年轻代但是进行垃圾回收之后,可能会变成老年代

G1还有个专门放大对象的叫做Humongous,一个对象超过了Region的百分之50就算大对象了(2M/2),如果一个对象太大,可以用多个Humongous来存放。Humongous是专门用来存放短期大对象的,不用进入老年代,节约老年代的空间,避免老年代不够的GC开销

GC的收集步骤

  • 初始标记:暂停所有线程,记录所有RC Roots直接引用对象,速度很快
  • 并发标记:与CMS并发标记相同
  • 最终标记:与CMS的重新标记相同
  • 筛选回收:筛选回收首先会对Region的成本和价值进行排序,根据用户期望GC停顿时间(可以用JVM参数-XX:MaxGCPauseMillis指定)来指定回收时间。比如现在老年代有1000个Region需要回收,但是回收时间设定只有200ms,那么通过之前回收成本计算,可能需要回收800个Region需要200ms,那么就只能回收这800个Region,尽量把GC时间控制在我们设定的时间范围内。G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region,比如一个Region花200ms能回收10M垃圾对象,还有个花200ms回收20M垃圾对象,在时间有限的情况下,G1会回收后者,这种Region划分内存空间以及优先级的区域回收方式,保证了G1收集器在最大程度收集垃圾对象。这个时间怎么计算的?其实主要就是计算一个Region复制的时间有多少个对象需要复制,因为复制占了绝大的时间

 

4G选择parNew+CMS 8G以上选择G1 

如果是内存小的CMS可能还会快一点,因为G1有很多复杂算法 占用大量内存。如果内存大 那么它的停顿时间的优势就发挥出来了。

思考一下 如果每秒几十万并发的系统?如果还用4核8G的机器我能扛得住吗?

答:扛不住,每个消息1kb,几十万的消息=每秒几百兆 ,根据内存模型 不用一秒Eden区就放满了,直接移到老年代(因为Form/To区域不够存放),每几秒甚至每秒就要做一次Full GC。这个时候处理那个消息还没有处理完 还没有标记释放不了空间, 这个时候下一秒的消息又来了,OOM。

kafka推荐使用32G甚至64G内存,充分发挥kafaka或rocketMQ这种消息中间件。这些中间件基于JVM运行

G1垃圾收集分类

Young GC:Young GC 并不是说现有的Eden区满了马上回收,G1会计算大概回收年轻代需要多久,如果远远小于-XX:MaxGCPauseMills那么就会增加年轻代的Region继续给新对象存放,不会马上做Young GC ,直到下一次Eden区放满,G1回收时间进阶参数-XX:MaxGCPauseMills,那么就会触发Young GC

Mixed GC :不是FullgGC,老年代占有率达到参数(-XX:InitiatingHeapOccupancyPercent)设定的值则触发,会回收所有的年轻代和部分Old区域(根据期待回收的时间来确定Old区域垃圾收集的优先顺序),以及大对象区域。正常情况下G1先做Mixed GC 然后用的是复制算法,需要把各个存活对象拷贝到空的Region区域中,如果发现没有足够的空的Region那么这个时候会触发FullGC

Full GC:停止所有系统程序,开启单线程进行标记,清除,整理压缩(类似于Serial ),好空闲出来一批Region来供给下一次MixedGC使用,这个过程非常耗时

怎么解决?

扩大内存

内存的问题随着扩大解决了 但是又引发了一个另外的问题

如果使用ParNew+CMS组合的话 ,现在开始回收年轻代,但是因为你是几十个G的年轻代,那么整个过程都是在STW,这个时间就会很长了 

这个时候就需要用到G1了,设置-XX:MaxGCPauseMills为50ms(G1计算现在Eden区回收大概多少时间当接近这个时间会触发YoungGC),假设50ms可以回收3-4G内存,50ms的卡顿完全可以接受并且用户毫无感知,整个系统可以在一边处理一边收集垃圾

G1就是适合这种大机器的JVM运行,可以完美解决收集回收垃圾时间过程的问题

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值