1.0 标记清除算法
1.1 回收步骤
-
标记(找出内存中需要回收的对象,并且把它们标记出来)
-
清除(清除掉被标记需要回收的对象,释放出对应的内存空间)
1.2 缺点
标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
(1)标记和清除两个过程都比较耗时,效率不高
(2)会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
标记过程:
清除过程:
1.3 标记清除算法的衍生规则之分配(动态分区分配策略)
1.3.1 首次适应算法(Fisrt-fit)
首次适应算法(Fisrt-fit)就是在遍历空闲链表的时候,一旦发现有大小等于需要的大小之后,就立即把该块分配给对象,并立即返回。(按找地址从小到大慢慢找)
性能最好。算法开销小,回收分区后,一般不需要对空闲分区队列重新排序
1.3.2 最佳适应算法(Best-fit)
最佳适应算法(Best-fit)就是在遍历空闲链表的时候,返回刚好等于需要大小的块
(按照容量从小到大排,然后找)
缺点:每次都从最小的分区进行分配,会留下越来越多的很小的难以利用的内存块,会产生很多的外部碎片。
优点:会有更多的大分区被保留下来,更能满足大进程需求
1.3.3 最差适应算法(Worst-fit)
最差适应算法(Worst-fit)就是在遍历空闲链表的时候,找出空闲链表中最大的分块,将其分割给申请的对象(能用就用,不能就报内存溢出),其目的就是使得分割后分块的最大化,以便下次好分配,不过这种分配算法很容易产生很多很小的分块,这些分块也不能被使用
缺点:优先使用大的,会导致后面没有大的用,有大的对象进来就不够了
优点:可以减少难以利用的小碎片
2.0 标记复制算法
2.1 流程
将内存划分为两块相等的区域,每次只使用其中一块。
当其中一块内存使用完了,就将还存活的对象复制到另外一块上面,然后把已经使用过的内存空间一次清除掉。
标记过程
复制过程
2.2 缺点
空间利用率降低(内存会变小,所以只会在很小的一块区域使用这个算法)
3.0 标记 - 整理算法
3.1.随机整理
对象的移动方式和它们初始的对象排列及引用关系无关
3.2线性整理
将具有关联关系的对象排列在一起
用的很少,无法解决内存碎片的问题
3.3滑动整理
将对象“滑动”到堆的一端,从而“挤出”垃圾,可以保持对象在堆中原有的顺序
全部挤到一起,剩下的内存全部干掉,无内存碎片
4.0 常见整理算法
4.1 双指针回收算法
整理前:两根指针分别位于内存的首尾段
因为位置移动先后,
第一次遍历移动位置但是并不更新标记
尾指针往前走,遇到可达(绿色的),和头指针的值进行交换,头指针向后移一位
第二次遍历更新标记(GC root指向位置,其实就是更新引用地址,原来指向的714,现在还是要指向714),头指针往前走,找更新过的位置
如何判断是否调换过位置,看索引号,一开始的索引号是递增的,如果出现一个大的在前面就说明他是调换过位置的
问题:如果对象大小不一样怎么办?
答:去用一张表记录下来,直到找到一个和他一样的区域才移过去
随机算法总结
任意顺序整理实现简单,且执行速度快,但任意顺序可能会将原本相邻的对象打乱到不同的高速缓存行或者是虚拟内存页中(理解为打乱到内存各个地方),会降低赋值器的局部性。 包括他只能处理固定大小的对象,一旦对象大小不固定,就会增加其他的逻辑。
4.2 Lisp2算法(滑动整理算法)
他是一个三指针算法,并且可以处理不同大小的对象。但是需要三次遍历,并且由于对象大小不一样,所以需要额外的空间存储,而不是直接移动。
end指针:用来做指针碰撞,让前面两个指针停止
free指针:后面有几个绿色的就跳到第几个
Scan指针:去找存活对象
先挪对象会产生覆盖,而移动的位置信息是放在对象里的(6-3,4-12),如果被覆盖了,移动的信息也会丢失(被覆盖的位置会丢失信息,因为本身被覆盖的位置也是要移动到其他位置的),所以算法的操作顺序是有规定的(需要最后
第一次遍历:Free指针是为了留位置,而Scan对象是为了找存活对象
第二次遍历:更新对象地址,修改root的引用地址
第三次遍历:移动对象
4.3 引线整理算法(滑动整理)
可以在不引入额外空间开销的情况下实现滑动整理,但需要2次遍历堆,且遍历成本较高
4.4 单次遍历算法(滑动整理)
单次遍历算法的重点在于提前记录我们需要转移的位置
关键词:偏移向量,标记向量以及内存索引号
会有一个额外的空间去记录(3-4移到1-2的位置,6移到3的位置)
4.6 总结
所有现代的标记-整理回收器均使用滑动整理,它不会改变对象的相对顺序,也就不会影响赋值器的空间局部性。复制式回收器甚至可以通过改变对象布局的方式,将对象与其父节点或者兄弟节点排列的更近以提高赋值器的空间局部性。
4.7 限制
整理算法的限制,如任意顺序算法只能处理单一大小的对象,或者针对大小不同的对象需要分批处理;整理过程需要2次或者3次遍历堆空间;对象头部可能需要一个额外的槽来保存迁移的信息。
分代收集算法
当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
分代收集三大假说
弱分代假说:绝大多数对象朝生夕死
强分代假说:活得越久的对象,也就是熬过很多次垃圾回收的对象是越来越难以消亡的
跨代引用假说
young区的对象有可能引用到老年区的对象。这样会产生垃圾该回收的没有回收,解决方法是在新生代建立一个记忆集,这个结构会把老年代分成小块小块,来记录老年代那块内存存在跨代引用。当gcroot扫描的时候只有包含跨代引用的内存加入扫描。
public class Person{ P void a(){ Son son = new Son(); } }
就比如说Person用了很久进入了老年区,但是Son是新生代区的,就发生了跨代引用
full GC = young GC + old GC + Meta Space GC