JVM的垃圾收集算法
标记清除算法
(1)、标记:找出内存中可达对象进行标记
(2)、清除:清除掉未被标记的回收对象,释放出对应的内存空间
缺点:
标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另外一次垃圾收集动作
(1)、标记和清除两个过程都比较耗时,效率不高
(2)、会产生大量的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发一次垃圾收集动作
标记过程
清除过程
标记清除算法–动态分区分配策略
- 首次适应算法
首次适应算法就是在遍历空闲链表的时候,一旦发现有大小等于需要的大小之后,就立即把该块分配给对象,并立即返回。 - 最佳适应算法
最佳适应算法的就是在遍历空闲链表的时候,返回刚好等于需要大小的块 - 最差适应算法
最差适应算法就是在遍历空闲链表的时候,找出空闲链表中最大的分块,将其分割给申请的对象,其目的就是使得分割后分块的最大化,一边下次好分配,不过这种分配算法很容易产生很多很小的分块,这些分块也不能被使用
标记复制算法
将内存划分为两个相等的区域,每次只是用其中一块
当其中一块内存使用完了,就将还存活的对象复制到另外一块内存,然后把已经使用过的内存空间一次清除掉
缺点: 空间利用率降低
标记过程
复制过程
标记整理(压缩)算法
标记过程
清除过程
整理过程
标记整理算法分配
- 随机整理
对象的移动方式和他们初始的对象排列及引用关系无关 - 线性整理
将具有关联关系的对象排列在一起,但是会造成空间碎片 - 滑动整理
将对象滑动到堆的一端,从而清除垃圾,可以保持对象在堆中原有的顺序
几种典型的整理算法 - 双指针回收算法、
实现简单且速度快,但会打乱对象的原有布局,属于随机整理 - Lisp2算法(滑动整理算法)
需要在对象头用一个额外的槽来保存迁移完的地址 - 引线整理算法
可以在不引入额外空间开销的情况下实现滑动整理,但需要2次遍历堆,且遍历成本较高 - 单次遍历算法
滑动回收,实时计算出对象的转发地址而不需要额外的开销
双指针整理算法
-
整理前:两根指针分别位于内存的首尾段
-
第一次遍历:移动位置但是并不更新标记
-
第二次遍历:更新标记
随机整理算法总结:
随机整理算法实现简单,且执行速度快,但随机整理可能会将原本相邻的对象打乱到不同的内存地址中,会降低赋值器的局部性,包括它只能处理固定大小的对象,一旦对象大小不固定,就会增加其他的逻辑
线性整理总结:
线性整理算法使用的很少,线性整理会对相关的对象进行整理,整理成一块块小区域,无法避免内存碎片
Lisp2整理算法
- 整理前:他是一个三指针算法,并且可以处理不同大小的对象,但是需要三次遍历,并且由于对象大小不一样,所以需要额外的空间存储,而不是直接移动
- 第一次遍历:Free指针是为了留位置,而Scan对象是为了找存活对象
- 第二次遍历:更新对象地址
- 第三次遍历:移动对象
单次遍历算法
单次遍历算法的重点在于提前记录我们需要转移的位置
总结:
所有现代的标记-整理均使用的是滑动整理,他不会改变对象的相对顺序,也就不会影响赋值器的空间局部性,复制式回收器甚至可以通过改变对象布局的方式,将对象与其父节点或者兄弟节点排列的更近以提高赋值器的空间局部性
限制:
1、整理算法的限制,如随机算法只能处理单一大小的对象,或者针对大小不同的对象需要分批处理;
2、整理过程需要2-3次遍历堆空间
3、对象头部可能需要一个额外的槽来保存迁移的信息
分代收集算法
当前主流商业JMV的收集收集器,大多数都遵循了分代收集的理论进行设计,这里需要解释一下,分代收集算法其实只是一种理论,一套指导方针,一套符合大多数程序运行实际情况的经验法则,它建立在几个分代假说之上
分代回收三大假说
- 弱分代假说:绝大多数对象朝生夕死
- 强分代假说:活得越久的对象,也就是熬过很多次垃圾回收的对象是越来越难以消亡的
- 跨代引用假说:就是新生代对象引用老年代对象,又或者老年代对象引用新生代的对象