垃圾收集算法

1分代收集理论

当前商业虚拟机的垃圾收集器,大多数都遵循了“分代收集”的理论进行设计,分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则,它建立在两个分代假说之上:

1)弱分代假说:绝大多数对象都是朝生夕灭。

2)强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡。

1.1 java堆划分不同的区域

将回收对象依据其年龄(即熬过垃圾收集过程的次数)分配到不同的区域之中存储。一个区域中大多数对象都是朝生夕灭,难以熬过垃圾收集过程的话,把它们集中放在一起,每次回收只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量空间;如果剩下的都是难以消亡的对象,虚拟机便可以使用较低的频率来回收这个区域。

Java堆至少被划分为新生代(Young Generation)和老年代(Old Generation)这两个区域。

1.2 跨代引用假说

新生代中的对象完全有可能被老年代所引用,为了找出该区域中的存活对象,不得不在固定GC Roots之外,再额外遍历整个老年代所有对象。但这样会给内存带来很大的性能负担。

跨代引用假说:跨代引用相对于同代引用来说仅占少数。

根据这个理论,只需在新生代上建立一个全局的数据结构(“记忆集”),该结构把老年代划分成若干小块,标志出老年代的哪块内存会存在跨代引用。此后当发生新生代收集(Minor GC)时,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots进行扫描。

1.2.1 记忆集

字节精度

每个记录精确到一个机器字长(处理器的寻址位数),该字节包含跨代指针。

对象精度

每个记录精确到一个对象,该对象里有字段含有跨代指针。

卡精度

每个记录精确到一块内存区域,该区域内有对象含有跨代指针。

表 记忆集的三种实现类型

卡精度也称为卡表,是记忆集最常用的实现方式。

垃圾收集算法_第1张图片

图 卡表实现原理

卡表是一个字节类型的数组,数组中每个元素对于着一块固定大小的内存区域叫做卡页,卡页大小通常设置为2的N次幂,默认是512字节。当卡页中有一个或多个对象存在跨代引用时,卡表对于的数组元素被标记为1,否则为0。

2 清除-标记算法

最早出现也是最基础的算法:算法分为“标记”和“清除”两个阶段。首先标记出所有需要回收的对象,标记完成后,统一回收掉所有被标记的对象。

2.1 算法缺点

1)执行效率不稳定。如果Java堆中含大量对象,而且大部分是需要被回收的,这时需要大量的标记和清除工作。这两个过程的执行效率都随对象数量增长而降低。

2)内存空间碎片化问题,清除之后会产生大量不连续的内存碎片。

垃圾收集算法_第2张图片

图 “标记-清除”算法图解

3 标记-复制算法

“半区复制”,将可用的内存按容量划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了,就将还存活的对象复制到另外一块上面,再把已使用过的内存一次清理掉。

垃圾收集算法_第3张图片

图 “半区复制算法图解”

    这种算法不用考虑用空间碎片的复杂情况,实现简单,运行高效。

3.1 算法缺点

1)如果内存中多数对象都是存活的,这种算法将会产生大量的内存复制的开销。

2)将内存缩小为原来的一半,造成空间浪费。

3.2 Appel式回收

把新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次内存分配只使用Eden和其中一块Survivor。发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另一块Survivor中,然后清理掉Eden和已用过的那块Survivor空间。

HotSpot虚拟机默认Eden和Survivor的大小比例是8:1。

无法保证每次回收都只有不多于10%的对象存活,Appel式回收就需要其他内存区域(实际上大多数是老年代)进行分配担保。

垃圾收集算法_第4张图片

图 Appel式内容溢出后进入老年代过程

1)当新生代触发Minor GC时,将存活下来的对象复制到Survivor中,但是此时存活下来的对象数量超过10%;

2)将溢出的对象复制到老年代;

3)清除Eden和已用过的那块Survivor空间。

4 标记-整理算法

标记和上述一样,然后让所有存活的对象都向内存空间一端移动,最后直接清理掉边界以外的内存。

垃圾收集算法_第5张图片

图 标记-整理算法

你可能感兴趣的:(JVM,jvm,java,算法)