要想了解Java虚拟机的垃圾收集算法就要知道分代收集理论,因为当前大多数商用垃圾收集算法都是基于分代收集理论进行的。
分代收集理论:
概念
- 弱分代假说:绝大多数对象都是朝生夕灭的
- 强分代假说:熬过越多次垃圾回收过程的对象就越难以消灭
- 跨代引用假说:跨代引用相对于同代引用占了极少数(对象不是孤立的,存在跨代引用由于存在该问题所以引出了)
由于第三条假说的存在,在新生代衍生出了一个新的数据结构:记忆集-Remembered Set
记忆集:
这个结构会将老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用
Java基于分代收集理论主要的目的思想就是将不同情况的对象进行区分,从而引出了Java堆的不同区域(新生代、老年代),从而兼顾了垃圾收集时间和内存空间的有效利用;从而衍生出 Minor GC、Major GC、Full GC的各种回收类型,针对不同区域不同回收类型发展出了不同的垃圾回收算法(标记-复制,标记-清除,标记-整理)。
不同区域
- Partial GC:部分收集
- Minor GC/Young GC:新生代收集
- Major GC/Old GC:老年代的垃圾收集;只有CMS收集器才会单独进行老年代的垃圾收集行为;
- 请注意“Major GC”这个说法现在有点混淆,在不同资料上常有不同所指,读者需按上下文区分到底是指老年代的收集还是整堆收集。
- Mixed GC:混合收集,指的是整个新生代和部分老年代的垃圾收集,目前只有G1垃圾收集器有这种行为
- Full GC:整堆收集,收集这个Java堆和方法区的垃圾收集
垃圾收集算法
标记-清除算法
最早、最基础的算法标记-复制算法(Mark-Sweep),主要有两个过程标记、和清除的过程。 缺点:
- 执行效率不稳定,标记和清除的两个动作的执行效率随着对象的数量增长而降低
- 空间碎片化比较严重,导致大对象无法找到足够的连续空间,从而出发下一次垃圾收集动作
标记-复制算法
主要是为了解决标记-清除算法面对大量回收对象执行效率低的问题,所以提出了半区复制的概念,将内存空间一分为两个大小相等的空间。
由于其缺点就是浪费空间比较严重,所以完全没有必要按照1:1的比例来划分新生代的内存空间。所以在1989年提出了一个更优的**半区复制分代策略**
,现在被称为Appel式回收。HotSpot虚拟机的Serial、ParNew等新生代收集器均采用的是这种方式设计的内存布局。Appel回收的具体做法就是将新生代划分为一个Eden和两个Survivor空间,当垃圾收集发生时便从Eden区和其中一个Survivor中存活的对象复制到另一块Survivor区。
HotSpot虚拟机默认Eden和Survivor的比例是8:1(HotSpot 中的这种分代方式从最初就是这种布局)
但是这么设计也有一个弊端,那就是当垃圾收集开始时万一有超过10%的对象存活那么就会发生内存不够的问题,所以Appel式回收通过逃生门
来应对这种比较罕见的情况;当Survivor空间不足以容纳垃圾收集后存活的对象就需要借助其它内存区域(绝大多数都是老年代)进行分配担保
。缺点:
- 会产生大量的内存间复制的开销,还有就是浪费空间,如果不想浪费50%的空间就需要分配担保。(老年代一般不能直接选用这种算法)
标记-整理算法
故名思义,其中的标记和标记-清除算法中的标记是一样的。但是后续操作不是对可回收对象进行直接的清理,标记-整理算法而是将所有存活的对象向内存的一侧进行移动,然后清理掉边界以外的可回收对象。所以标记-清除算法和标记-整理算法两者的区别就是:前者是非移动式的回收算法,后者是移动式算法
。从而也导致了是否移动回收后存活的对象是一项优缺点并存的决策
。 如果移动对象,在老年代中如果垃圾收集后存在大量的存活对象,然而移动存活对象并且必须更新所有引用这些的地方是一个极为负重的操作。并且最为致命的是这种操作必须要**暂停用户线程(暂停用户线程、暂停用户线程、暂停用户线程重要的事情说三遍)**才能进行,这种停顿被称为Stop The World