JVM常见垃圾收集算法
- 标记-清除算法
- 复制算法
- 标记-整理(标记压缩)算法
- 分代收集算法
- 新生代和老年代
- 分代收集算法工作机制
- 面试题:为什么分代收集算法把堆分成年轻代和老年代?
标记-清除算法
最基础的算法,分标记和清除两个阶段:首先标记处所需要回收的对象,在标记完成后统一回收所有被标记的对象。
它有两点不足:
- 一个效率问题,标记和清除过程都效率不高;
- 一个是空间问题,标记清除之后会产生大量不连续的内存碎片(类似于我们电脑的磁盘碎片),空间碎片太多导致需要分配大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。
复制算法
为了解决效率问题,出现了“复制”算法,他将可用内存按容量划分为大小相等的两块,每次只需要使用其中一块。当一块内存用完了,将还存活的对象复制到另一块上面,然后再把刚刚用完的内存空间一次清理掉。这样就解决了内存碎片问题,但是代价就是可以用内容就缩小为原来的一半。
标记-整理(标记压缩)算法
复制算法在对象存活率较高时就会进行频繁的复制操作,效率将降低。因此又有了标记-整理算法,标记过程同标记-清除算法,但是在后续步骤不是直接对对象进行清理,而是让所有存活的对象都向一侧移动,然后直接清理掉端边界以外的内存。这样的话效率就会降低
分代收集算法
分代收集是将内存划分成了新生代和老年代。分配的依据是对象的生存周期,或者说经历过的 GC 次数。对象创建时,一般在新生代申请内存,当经历一次 GC 之后如果对还存活,那么对象的年龄 +1。当年龄超过一定值(默认是 15,可以通过参数 -XX:MaxTenuringThreshold 来设定)后,如果对象还存活,那么该对象会进入老年代。
新生代和老年代
在Java8时,堆被分为两份:新生代和老年代【1:2】
- 对于新生代,内部又被分为三个区域
- 伊甸区Eden,新生的对象都被分配到Eden区
- 幸存者区survivor(分成from和to)Eden区,from区,to区分配比例【8:1:1】
分代收集算法工作机制
-
新创建的对象,都会先分配到Eden区,
-
当伊甸区内存不足时,标记伊甸园与from(现阶段没有)的存活对象,将存活对象采用复制算法复制到to区域中,复制完毕后,伊甸园区和from区内存得到释放
-
经过一段时间后伊甸园区的内存又出现不足,标记Eden区域和to区域存活的对象。
-
将Eden区域和to区域存活的对象,复制到from区,
-
当幸存区对象熬过几次回收(最多15次),晋升到老年代
在Java堆划分出不同的区域之后, 垃圾收集器才可以每次只回收其中某一个或者某些部分的区域 ——因而才有了“Minor GC”“Major GC”“Full GC”这样的回收类型的划分; 也才能够针对不同的区域安排与里面存储对象存亡特征相匹配的垃圾收集算法——因而发展出了“标记-复制算法”“标记-清除算 法”“标记-整理算法”等针对性的垃圾收集算法。
- 新生代收集(Minor GC/Young GC): 指目标只是新生代的垃圾收集,暂停时间短(STW)。
- 混合收集(Mixed GC): 指目标是收集整个新生代以及部分老年代的垃圾收集。 目前只有G1收集器会有这种行为。
- 整堆收集(Full GC) :指整个新生代和整个老年代的垃圾收集,暂停时间长(STW)尽力避免Full GC
面试题:为什么分代收集算法把堆分成年轻代和老年代?
- 系统中的大部分对象,都是创建出来之后很快就不再使用,等待被回收,比如用户获取订单数据,订单数据返给用户之后就可以释放了
- 老年代中会存放长期存放存活的对象,比如Spring的大部分Bean对象,在程序启动之后就不会被回收了
在虚拟机的默认设置中,新生代大小要远小于老年代的大小