Java虚拟机-垃圾收集算法
分代收集理论
当前商业虚假你寄的垃圾收集器,大多数遵循了“分代收集”的理论,它建立在两个分代假说上:
弱分代假说:绝大数对象都是朝生夕灭的。
强分代假说:熬过越多次垃圾收集过程的对象就越难消亡。
这两个分代假说共同奠定了多款常用垃圾收集器的一致的设计原则:收集器应将Java堆划分出不同区域,然后将回收对象依据其年龄(即熬过垃圾收集过程次数)分配到不同区域中存储。
在Java堆划分出不同区域之后,垃圾收集器才能每次只回收其中某一个或者某些部分区域------因而才有了“Minor GC”、“Major GC”、“Full GC”这样的回收类型划分;也才能够针对不同区域安排与里面存储对象存亡特征相匹配的垃圾收集算法:“标记-复制算法”、“标记-清除算法”、“标记-整理算法”。
把分代收集理论具体放到现在商用虚拟机里,设计者一般至少会把Java堆划分为新生代和老年代两个区域。
跨分代假说
分代收集并非只是简单的划分一下内存区域那么容易,他至少存在着一个困难:对象不是孤立的,对象间会存在跨代引用(在新生代中“不可达”的对象,可能被老年代中的对象引用,反之亦然)。
跨代引用假说:跨代引用相对于同代引用来说仅占少数。
这其实是可以根据前两条假说推出的隐含推论:存在相互引用关系的两个对象,是应该倾向于同时消亡的。(由于老年代引用对象难以消亡,其引用的新生代对象也同样难以消亡,进而年龄增长,新对象会进入老年代,这种跨代引用关系就消失了)。
依据这条假说,我们就不需要为了少量的跨代引用去扫描整个老年代,也不必专门记录每个对象是否存在及存在哪些跨代引用。只需要建立一个全局的数据结构(记忆集, Remembered Set),这个结构把老年代划分成若干小块,标识出拿一块内存会存在跨代引用。此后发生Minor GC时,会将这些区域加入到GC Roots扫描。
部分收集(Partial GC):指不是完整收集整个Java堆的垃圾收集。
新生代收集(Minor GC/Young GC):目标只是新生代的垃圾收集。
老年代收集(Major GC/Old GC):目标只是老年代的垃圾收集。
目前只有CMS收集器会有单独收集老年代的行为。
混合收集(Mixed GC):指收集整个新生代和部分老年代的垃圾收集。
目前只有G1收集器会有这种行为。
整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集。

垃圾收集算法
标记-清除算法(Mark-Sweep)
算法分为标记和清除两个阶段,在标记完成后,统一回收掉所有被标记(或未被标记)的对象。标记过程就是判定对象是否“死亡”的过程。
它主要有两个缺点:执行效率不稳定、内存空间碎片化。

标记-复制算法
将内存分为大小相等的两块,每次只使用其中的一块,当一块内存用完了,就将还存活带着的对象复制到另外一块上,再把已使用过的内存空间一次性清理掉。
这样实现简单,运行高效,但代价是将可用内存缩小为原来的一半,空间浪费太多。
Andrew Appel针对具备“朝生夕灭”特点的对象(新生代中的对象有98%熬不过第一轮收集),提出了一种更优化的半区复制分代策略,现在称为“Appel式回收”。具体做法是:将新生代分为一块较大的Eden和两块较小的Survivor空间(8:1:1),每次分配内存只用Eden空间和其中一块Survivor空间。发生垃圾收集时,将Eden和Survivor中还存活的对象复制到另一块Survivor中,然后清除掉Eden和已经用过的那块Survivor。Appel式回收还有针对极端情况(存活的对象超过了Survivor的内存大小)的安全设计,当Survivor不能再存放更多对象时就需要其他区域进行分配担保(实际上大多为老年代)。

标记-整理算法(Mark-Compact)
“标记-复制算法”在对象存活率较高时就需要进行更多的复制操作,效率会将会降低。针对老年代的存亡特征,提出了另一种针对性的算法---“标记-整理算法”。标记-整理算法也分为标记和整理两个步骤,标记过程和“标记-清除算法”一样,但后续步骤不不是直接对可回收对象进行清理,而是让所有存活的对象都向一边移动,然后直接清理掉边界以外的内存。
“标记-清除算法”与“标记-整理算法”的本质差异在于前者是一种非移动式的回收算法,而后者是移动式的。
是否移动存活的对象是一项优缺点并存的风险决策:如果移动对象,尤其是老年代这种有大量对象存活的区域,移动这些存活对象会是一种负重极大的操作,并且移动对象的操作必须全程暂停用户应用程序才能进行(最新的ZGC和Shenandoah收集器使用读屏障技术实现了整理过程与用户线程并发执行);如不移动对象,就会导致内存空间碎片化的问题,只能依赖于更加复杂的内存分配器和内存访问器来解决。
基于以上两点,是否一定对象都存在弊端。有一种“和稀泥式”的解决方案可以不在内存分配和访问上增加太多额外负担,做法是让虚拟机平时多数采用“标记-清除算法”,容忍内存碎片的存在,知道内存空间碎片化程度已经影响到对象分配时,再采用“标记-整理算法”一次,以获得规整的空间。CMS收集器就是采用的这种办法。
