一、垃圾回收算法
1、标记–清除算法
标记–清除(Mark-Sweep)算法,分为标记和清除两个阶段,首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,这是最基础的收集算法,后续很多算法都是基于这种思想进行设计的。
标记–清除算法主要的不足有两点:一个是效率的问题,标记和清除的操作效率都比较低;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序的运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不触发另一次GC操作的动作。
2、复制算法
复制算法将内存按容量分为大小相等的两块,每次只使用其中的一块。当这一块内存用完了,就将还存活的对象复制到另外一个内存上面,然后再把刚才使用过的内存空间一次性清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时就不用考虑内存碎片的问题,只需要移动堆顶的指针,按顺序分配内存即可,原理比较简单,运行高效。
但是这个代价是把内存一分为二,每次只能用一半的内存,比较浪费资源,而且如果对象的存活率较高时,会有较多的复制操作,效率会变得更低,所以在老年代一般去采取这种算法。
3、标记–整理算法
标记–整理算法包括标记和整理两个阶段,标记的过程和标记–清除算法中的操作是一样的,但是标记完成后不是直接对对象进行清理,而是让所有存活的对象向一边移动,然后直接清除掉端边界以外的内存,这样也不会有内存碎片产生,也不用浪费掉一部分内存。
4、分代收集算法
当前的商业虚拟机的垃圾收集都是采用分代收集算法,这种算法根据对象存活周期的不同将内存划分为几个块。一般会分为新生代和老年代,然后根据各个年代的特点采用最适当的收集算法。
在新生代中,每次进行垃圾收集时只会有少量的对象存活,所以可以采用复制算法,只需要付出复制少量对象的代价就可以完成一次收集。而老年代中的对象存活率较高,没有额外的空间进行分配担保,就必须使用“标记–清理”或者“标记整理”算法来进行回收。
二、垃圾收集器
1、Serial收集器
Serial收集器是最基本、发展历史最悠久的收集器。这个收集器是个单线程的收集器,只会使用一个CPU或一个手机线程去完成垃圾收集的工作,而且收集时,必须暂停其他所有的工作线程,直到垃圾回收结束。“Stop The World”,在用户不可见的情况下把用户所有的工作线程全部停掉,是让用户难以接受的。但是在单个CPU的环境下,Serial收集器由于没有线程交叉的开销,所以有着非常高的单线程收集效率。
2、ParNew收集器
ParNew收集器其实是Serial收集器的多线程版本,这两种收集器共用了很多的代码。ParNew收集器是除了Serial收集器外唯一一款能与CMS收集器配合工作的。在单线程环境下比Serial收集器效率低,在多线程下比Serial收集器效率高。
3、Parallel Scavenge收集器
Parallel Scavenge收集器是一个新生代收集器,也是使用的复制算法,但是与ParNew收集器有区别。
Parallel Scavenge收集器与其他收集器的关注点不同,其他比如CMS收集器关注的是如何缩短进行垃圾收集时用户线程的停顿的时间,而Parallel Scavenge收集器的关注点是达到一个可控制的吞吐量,吞吐量是CPU用于运行用户代码的时间与CPU总耗时的比值。停顿的时间越短适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。
Parallel Scavenge收集器提供两个参数来精确控制吞吐量,分别是控制最大垃圾收集停顿时间的 -XX:MaxGCPauseMillis 参数和直接设置吞吐量大小的 -XX:GCTimeRatio参数。
MaxGCPauseMillis参数允许的值是一个大于0的毫秒数,收集器将尽可能的保证内存的回收时间不会超过设定的值。但是并不是把这个数值设置的越小回收速度就会越快,这个停顿时间的缩短是牺牲吞吐量和新生代的空间换来的。系统把新生代调小一点,收集的速度也快一些,也导致垃圾回收的频率高了一些。
MaxGCPauseMillis参数值应当是一个大于0小于100的整数,也就是垃圾收集时间占总时间的比率。
Parallel Scavenge收集还提供了一个-XX:+UseAdaptiveSizePolicy参数。这个参数打开以后就不用再手动指定新生代大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会有自适应调整策略。
4、CMS收集器
CMS(Concurrent Mark Sweep)收集器是以获取最短回收停顿时间为目标的收集器。是基于标记-整理算法实现的,整个过程包含四个步骤,初始标记、并发标记、重新标记、并发清除。
初始标记和重新标记两个步骤仍需要“Stop The World”。初始标记仅仅是标记一下GC Roots能直接关联到的对象,速度非常快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。由于整个步骤中最耗时的并发标记和并发清除都可以和用户线程并行工作,所以整体来说CMS收集器停顿的时间是非常短暂的。
CMS收集器的三个缺点:
a、对CPU的资源非常敏感。因为CMS收集器会抢占一部分线程进行并发标记和清除,会导致程序变慢,吞吐量降低。
b、无法处理浮动垃圾。由于收集器在并发清除的过程中程序还是运行,自然就会伴随着新的垃圾产生,而这一部分垃圾出现在标记过程之后,所以此次回收无法清除这些垃圾,只能等到下一次GC再清理掉,这部分垃圾就叫浮动垃圾。
c、因为CMS收集器的实现原理是标记-清除算法,所以也会存在垃圾碎片的问题。
5、G1收集器
G1收集器是当今收集器技术发展最前沿的成果。G1收集器有如下几个特点:
a、并行与并发,G1能够充分利用多CPU、多核的硬件优势来缩短垃圾回收时的停顿时间。
b、分代收集,能够独立管理整个GC堆。
c、空间整合,原理类似于标记-整理算法和复制算法的结合,不会产生空间碎片。
d、可预测的停顿,可以建立一个可预测的停顿模型。
G1收集器的运作步骤大致可分为初始标记、并发标记、最终标记和筛选回收四个阶段。前两个阶段跟CMS类似,最终标记是为了修正在并发标记期间因程序继续运作而导致标记产生变动的那一部分记录。最后的筛选阶段为根据回收价值和成本排序,根据用户所期望的GC停顿时间来制定回收计划,也可并发执行。