面试必问的JVM垃圾回收(二)

一、分代垃圾收集器:

二、常见垃圾回收算法:

1. 标记-清除算法

  标记-清除算法从根集合GC Roots出发,关于GC ROOTS可以参照作者上一篇文章,沿引用链进行扫描,对存活的对象进行标记,标记完毕后,扫描整个空间中未被标记的对象进行回收.标记-清除算法不需要进行对象的移动,只需要处理已经死亡的对象,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片,内存碎片过多可能会导致更快的一次垃圾回收,比如需要分配大对象的时候.

2.复制算法

  复制算法的提出是为了解决Mark-Sweep算法的问题。它开始时把堆中的内存按照容量大小分为相等的两块,程序从其中一块分配内存,当对象满了,基于复制算法的垃圾收集就从GC Roots中扫描活动对象,并将所有存活对象复制到另一块内存中,程序会在新的对象面中分配内存,然后把已经使用的这一块内存清理掉,这样极大的解决了内存碎片的问题,但是也暴露出极大的缺点,此算法导致内存付出了昂贵的代价,每次创建对象只能使用一半内存,如果存活对象较多的情况出现,复制算法的效率就会很低。

3.标记-整理算法

  也叫压缩算法,标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,会将所有的存活对象往另外一端内存中移动,并更新对应的指针,然后统一清除掉已使用内存的死亡对象。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。

4.分代收集算法

分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。

目前大部分垃圾收集器对于新生代都采取复制算法,因为新生代中每次垃圾回收都要回收大部分对象,需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。而由于老年代的特点是每次回收都只回收少量对象,一般使用的是标记-整理算法(压缩法)。

三、常用收集器采用何种算法?

  1. Serial收集器,采用复制算法,新生代单线程收集器,标记和清理都是单线程,优点是简单高效.可以通过-XX:+UseSerialGC指定。
  2. Serial Old收集器,采用标记整理算法老年代单线程收集器,Serial收集器的老年代版本。
  3. ParNew收集器采用复制算法,新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。
  4. Parallel Scavenge收集器,新生代收集器,采用复制算法,并行收集器,追求高吞吐量,高效利用CPU吞吐量一般为99%, 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景.可用-XX:+UseParallelGC指定,用-XX:ParallelGCThreads=2来指定线程数。
  5. Parallel Old收集器,采用复制算法,Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先。
  6. CMS(Concurrent Mark Sweep)收集器,采用标记清除算法,老年代收集器,高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu 追求高响应时间的选择。

四.常用垃圾收集器流程

CMS:

1.初始标记阶段(initial mark)stop-the-world,用于确定 GC Roots。

2.并发标记阶段(concurrent mark)标记 GC Roots 可达的所有存活对象,由于这个阶段应用程序同时也在运行,所以并发标记阶段结束后,并不能标记出所有的存活对象。

3.再次标记阶段(remark)为了解决第一次并发标记结束后被遗漏的存活对象,需要再次停顿应用程序,遍历在并发标记阶段应用程序修改的对象,标记出应用程序在这个期间的活对象,由于这次停顿比初始标记要长得多,所以会使用多线程并行执行来增加效率。

4.并发清理阶段(concurrent sweep)再次标记阶段结束后,保证所有存活对象都被标记完成,然后将回收垃圾对象所占空间。

下图示意了老年代中 串行、标记 -> 清理 -> 压缩收集器和 CMS 收集器的区别:

增量模式

CMS 收集器可以使用增量模式,在并发标记阶段,周期性地将自己的 CPU 时钟周期让出来给应用程序.这个功能适用于需要 CMS 的低延时,但是 CPU 核心较少的情况.

何时使用 CMS 收集器

适用于应用程序要求低停顿,同时能接受在垃圾收集阶段和垃圾收集线程一起共享 CPU 资源的场景.

使用 CMS 收集器

显示指定:-XX:+UseConcMarkSweepGC

如果需要增量模式:–XX:+CMSIncrementalModeoption

优点:

CMS 收集器降低了老年代收集时的停顿时间

缺点:

  1. CMS回收器采用的算法是Mark-Sweep,不会整理、压缩堆空间,使得经过CMS收集的堆会产生空间碎片. CMS不对堆空间整理压缩节约了垃圾回收的停顿时间,但也带来的堆空间的浪费。为了解决堆空间浪费问题,CMS回收器不再采用简单的指针指向一块可用堆空 间来为下次对象分配使用。它需要维护一个空闲列表,将所有的空闲区域连接起来,当分配空间时,需要寻找到一个可以容纳该对象的区域。显然,它比使用简单的指针碰撞成本要高。同时它也会加大年轻代垃圾收集的负载,因为年轻代中的对象如果要晋升到老年代中,需要老年代进行空间分配。
  2. 需要更多的CPU资源.为了让应用程序不停顿,CMS线程和应用程序线程并发执行,这样就需要有更多的CPU,单纯靠线程切 换是不靠谱的。并且,重新标记阶段,为空保证STW快速完成,也要用到更多的甚至所有的CPU资源.
  3. CMS的另一个缺点是它需要更大的堆空间。因为在并发标记阶段,程序还需要执行,所以需要留足够的空间给应用程序。另外,虽然收集器能保证在标记阶段识别出所有的存活对象,但是由于应用程序并发运行,所以刚刚标记的存活对象很可能立马成为垃圾,而且这部分由于已经被标记为存活对象,所以只能到下次老年代收集才会被清理,这部分垃圾称为 浮动垃圾.因为CMS标记阶段应用程序的线程还是在执行的,那么就会有堆空间继续分配的情况,为了保证在CMS回 收完堆之前还有空间分配给正在运行的应用程序,必须预留一部分空间.CMS不会在老年代满的时候才开始收集.它会尝试更早的开始收集,已 避免在回收完成之前,堆没有足够空间分配,默认当老年代使用68%的时候,CMS就开始收集了。 – XX:CMSInitiatingOccupancyFraction =n 来设置这个阀值。

G1收集器,ZGC收集器会在之后的专栏在详细剖析,欢迎关注!

 

关注我的公众号 :  宇哥996(id: java_zyh)   Java全栈技术,大厂面试题不定期分享

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值