大厂面试真题:GC 算法有多少种?各自优缺点是什么?年轻代和年老代选择哪种算法最优?
首先回顾一个图,也就是.class文件的类加载过程,以及线程执行、java内存模型图。看过系列1、2、3三篇文章的同学,大脑先回顾一遍类加器类型、双亲委派机制、如何打破双亲委派机制、GC内存划分、各个区域存放什么数据、以及给你一段代码要知道具体如何执行,对象什么时候被回收的逻辑。有道是,磨刀不误砍柴工,温故而知新,巩固基础,修炼JVM调优进阶核心技术。
然后堆内存空间区域划分,在上一篇文章系列2以及说过,具体如下图,再回顾一下,年轻代分了Eden区和S1、S2区,堆内存里除了年轻代,其他都是老年代空间。
今天重点讲的就是堆内存的GC算法以及GC处理过程,一步步来,希望本系列(预计100篇)结题的时候,有缘刷到的同学都有所收获,都能做JVM线上问题排查和JVM调优。
一、年轻代GC 复制算法
我们知道,当一个新对象要存到堆内存里,这里暂且不说大对象,对象都是优先放到年轻代的Eden区。那随着时间推移,如下图Eden区满了,怎么办?答案:那肯定是要发生YGC,也就是Minor GC。
YGC采用的是什么GC算法?
答: 复制算法。
那为什么选这个算法?
答:稍等,后文解答。
复制算法核心逻辑是:将GC发现有GC Roots也就是还存活的对象,统一挪到S区。
1.1 复制算法的三个阶段
1、标记阶段
从GC Roots集合开始,标记还存活被引用的对象;
2、转移阶段
即把存活对象复制到新的内存地址上。
3、重定位阶段
因为复制转移导致对象的地址发生了变化,在这个重定位阶段,要把所有指向对象的旧地址的指针调整为新内存地址。
1.2 举例具体YGC过程
1、最开始,所有新对象在Eden区,发生YGC后,将存活的对象放到S1区;
2、再发生YGC,就把Eden区和S1区的存活对象,放到S2区。新对象继续放Eden区;
3、再发生YGC,就把Eden区和S2的存活对象,放到S1区。新对象继续放Eden区;
2和3反复。
如下图,当YGC结束后,Eden区就变干净,S区的1或2会存放存活对象,而另一个S区也是干净的。
1.3 复制算法有什么优点?
实现简单,另外由于没有标记和清除过程,执行效率高(毕竟存活的对象总是很少),而且复制对象到S区,使得内存空间总是连续的,没有空间碎片,那内存碎片问题就解决了。另外通过Eden+S1+S2三个区,而且是8:1:1的空间比例,内存利用率也很高,高达90%,仅仅浪费10%,相比传统复制算法只分两个区(5:5占比划分),内存利用率大幅提升。
1.4 复制算法的缺点是什么?
唯一缺点,就是没有100%利用内存,会有一些浪费。目前年轻代的这种分区复制算法,以及非常优秀,仅浪费10%左右内存,这个参数可以根据实际情况进行JVM调优。
看完复制算法的优缺点,这回就知道年轻代为什么适合用复制算法了:核心是年轻代每次GC后存活的对象很少,用复制算法效率高。
1.4 年轻代对应的垃圾回收器有哪些?
年轻代对应的GC回收器有Serial 垃圾回收器、ParNew垃圾回收器。
二、老年代GC 标记-整理算法
老年代的GC,也就是FullGC,那FullGC什么时候会触发呢?
一般就是当YGC之后发现存活对象很多,S区放不下了,或者触发某些规则,必须放老年代那边。这时候会去检查老年代还有多少空闲空间,如果发现老年代那边的空闲空间放不下,那就要先发生一个FullGC腾空间。
老年代FullGC主流采用的是标记-整理算法,核心原理大概是这样:
把老年代里的存活对象标记处理,然后清理那些已经可以GC回收的对象。最后把相关存活对象归集整理放在一篇连续内存区域,避免有内存碎片。
老年代的这个标记回收算法,是非常慢的,效率比年轻代回收慢一个数量级。
老年代对应的GC回收器有Serial Old垃圾回收器、CMS垃圾回收器。
2.1 标记-整理算法过程具体说说
1、标记(Marking)
与其他算法标记类型,从根对象开始,标记出所有存活的对象。
2、整理(Compacting)
在标记清除可以被回收的对象后,将所有存活的对象向一片连续内存移动,这样可以消除内存碎片提高老年代的内存使用率。
3、更新引用
在整理过程中,由于对象位置有挪动修改,所以对象内部的引用也需要进行同步更新,以指向移动后的对象地址。
发现这个过程和复制算法是差不多的,核心差异在整理那,不过那细讲也要写一篇来说,后续出文再讲。
2.2 标记整理算法的优缺点是什么?
优点:解决了标记-清理算法存在的内存碎片问题,使得内存空间更加紧凑,提高了内存利用率。
缺点:由于需要对存活对象进行移动,这会导致一定的性能开销,降低了GC效率。
2.3 老年代为什么不用复制算法?
老年代的标记整理算法竟然效率这么低,为啥不该用复制算法呢?
这是因为老年代的对象存活率会较高,每次FGC,很可能有超过50%对象还会存活。如果存活的对象多,那复制操作的效率也一样会变低。同样,有一个的标记清除算法也可以应用在老年代中,但是它效率和标记整理算法差不多,也不高,且在内存回收后容易产生大量内存碎片。因此标记整理算法(Mark-Compact)算法,是老年代较优的选择。
本文仅重点介绍分区复制算法、还有标记整理算法,除了这两个还有标记-清除算法(Mark-Sweep)、分代收集算法(Generational Collection)。这里不再赘述。
推荐阅读:
1、JVM进阶调优系列(3)堆内存的对象什么时候被回收?
2、JVM进阶调优系列(2)字节面试:JVM内存区域怎么划分,分别有什么用?
3、JVM进阶调优系列(1)类加载器原理一文讲透
4、JAVA并发编程系列(13)Future、FutureTa sk异步小王子