【JVM】垃圾回收的四大算法

GC垃圾回收

JVM大部分时候回收的都是新生代(伊甸区+幸存0区+幸存1区)。按照回收的区域可以分成两种类型:Minor GC和Full GC(MajorGC)。

  • Minor GC:只针对新生代区域的GC,大多数Java对象的存活率都不高,Minor GC非常频繁,回收速度快。
  • Full GC:发生在老年代的GC,经常会伴随至少一次的Minor GC(但不一定会),Full GC扫描的范围更广泛,Full GC的速度比Minor GC慢10倍以上。

 【JVM】垃圾回收的四大算法

GC四大算法

引用计数法

对于单个对象来说,当有引用发生,引用计数器就+1;当丢失引用,引用计数器就-1。当引用数减到0的时候,说明对象不再有用,被垃圾回收。引用计数法缺点是每次对对象赋值都要维护引用计数器,且计数器本身也有一定的消耗,难以处理引用循环(例如:对象双方互相引用,但实际上二者为空,此时双方引用都不为空)。JVM的实现一般不采用这种方式。

【JVM】垃圾回收的四大算法

复制算法

年轻代中使用的是Minor GC,这种Minor GC采用的是复制算法。复制的思想是将内存分为2快,每次只用其中一块,当这一块内存用完,就将或者的对象复制到另一块上面,复制算法不会产生内存碎片

HotSpot JVM中年轻代可以分成三个部分:Eden区、Survivor0区,Survivor1区,默认比例为8:1:1。Survivor的两个区在逻辑上可以视为from区和to区,每次GC后会交换from区和to区,在Eden区和from区满之前,to区始终是为空的区。如果to区也被填满了,所有对象移动到老年代。

【JVM】垃圾回收的四大算法

新创建的对象一般会被分配到伊甸区,经过一次Minor GC后,如果对象还存活,就会被移到Survivor区。from区的对象如果继续存活,且能够被另一块幸存区to区容纳,则使用复制算法将这些仍然存活的的对象复制到另一块幸存区to区中,然后清理使用过的Eden和from区(下一次分配就从to区开始,to区成为下一次GC的from区),且这些对象的年龄设置为1,以后对象在幸存区每经历一次Minor GC,对象的年龄就会+1,当对象的年龄到达某个阈值的时候,这些对象就会进入老年代。(阈值默认是15,可以通过-XX:MaxTenuringThreshhold来设定对象在新生代在存活的次数)。

这种算法的优点了不会产生内存碎片,缺点是浪费内存空间,在HotSpot虚拟机中8:1:1的比例下,可用内存为80%+10%,有10%的内存会被浪费掉。如果对象存活率很高,就需要将所有对象都复制一边,并重置引用地址。

标记清除(Mark-Sweep)

老年代一般是由标记清除 或者 标记清除和标记整理的混合实现的。

标记清除算法分为两个步骤,先标记出要回收的对象,然后统一回收这些对象。

【JVM】垃圾回收的四大算法

优点是节约内存空间,不需要额外空间。缺点是两次扫描,标记和清除的效率都不高,耗时严重。标记清除后会产生大量不连续的内存碎片。内存碎片会导致以后程序需要分配大对象的时候,找不到足够的连续内存,导致提前触发GC。

 标记整理(Mark-Compact)

和标记清除一样,先标记出要回收的对象,然后让存活对象都向一端移动,直接清理掉端边界 以外的内存。

【JVM】垃圾回收的四大算法

优点是没有内存碎片,缺点是效率不高,需要标记存活对象还要整理存活对象的引用地址,从效率上来说是不如复制算法的。

还有一种折衷的方案,将标记清除和标记整理算法相结合,一般直接标记清除,当GC达到一定次数的时候,进行一次标记整理,从而减少了移动对象的成本,又有处理内存碎片的步骤。

总结

效率排名:复制算法>标记清除>标记整理

内存整齐度:复制算法=标记整理>标记清理

内存利用率:标记整理=标记清理>复制算法

四种算法各有优劣,一般的JVM实现会采用分代收集算法,根据不同代所具有的不同特点使用不同的算法。

年轻代的特点是区域较小,对象存活率低,适合使用复制算法。复制算法的效率只和当前存活对象的大小有关,适用于年轻代的回收,内存利用率不高的问题HotSopt通过两个survivor的设计进行和缓解,新生代可用容量为80%+10%,只有10%的内存被浪费掉。

老年代的特点是区域较大,对象存活率高,适合使用标记清除/标记整理算法。