垃圾收集器
如果说收集算法是内存回收的方法论,那么垃圾收集器则是内存回收的具体实现。Java虚拟机规范中对垃圾收集器应该如何实现没有任何规定。这里讨论的收集器是基于JDK1.7 Update 14 之后的HotSpot虚拟机。这个虚拟机包括的收集器如下图1.1:
图1.1
图1.1展示了7种作用于不同分代的收集器。如果两个收集器之间有连线,表示它们可以搭配使用。Serial、ParNew、Parallel Scavenge 是新生代收集器,CMS 、Serial Old 、Parallel Old 是老生代收集器。G1跨越新生代老生代。 这里不是为了挑出一个最好的收集器。目前为止,没有一个最好的收集器,更没有万能的收集器。我们只能根据具体应用选择最适合的收集器。
一.Serial收集器
特点: 1.采用复制算法
2.单线程的收集器,只会使用一个CPU或一条收集线程去完成垃圾收集工作。
3.进行垃圾收集时,必须暂停其他的所有工作线程,知道它收集结束。Stop The World(STW)
优点:1.简单而高效(与其他收集器的单线程比),对于单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然获得最高的单线程收集效率。
2:对于运行在Client模式下的虚拟机是最好的一个选择。
二.ParNew收集器
特点:1.Serial收集器的多线程版本。复制算法、Stop The World、对象分配规则、回收规则和Serial一样。
2.除Serial收集器外,目前只有踏能与CMS收集器配合工作。
ParNew/Serial Old收集器运行示意图
三.Parallel Scavenge收集器
特点:1.复制算法。并行的多线程收集器。
2.与ParNew收集器的不同在于,Parallel Scavenge收集器的目的是达到一个可控制的吞吐量。
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。
四.Serial Old收集器
特点:1.是Serial收集器的老年代版本。单线程收集器,使用 标记-整理的算法。
2.Client模式下的虚拟机使用。
3.用途:1与Parallel Scavenge收集器搭配使用. 2作为CMS收集器的后备预案。
五.Parallel Old
特点:1.Parallel Old是Parallel Scavenge收集器的老年代版本。使用多线程和标记-整理算法。
2.在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加 Parallel Old 收集器。
六.CMS收集器
CMS收集器(Concurrent Mark Sweep)的目标就是获取最短回收停顿时间。在注重服务器的响应速度,希望停顿时间最短,则CMS收集器是比较好的选择。
整个执行过程分为以下4个步骤:
- 初始标记
- 并发标记
- 重新标记
- 并发清除
初始标记和重新标记这两个步骤仍然需要暂停Java执行线程,初始标记只是标记GC Roots能够关联到的对象,并发标记就是执行GC Roots Tracing的过程,而重新标记就是为了修正并发标记期间因用户程序执行而导致标记发生变动使得标记错误的记录。其执行过程如下:
CMS的优点很明显:并发收集、低停顿(由于进行垃圾收集的时间主要耗在并发标记与并发清除这两个过程,虽然初始标记和重新标记仍然需要暂停用户线程,但是从总体上看,这部分占用的时间相比其他两个步骤很小,所以可以认为是低停顿的)。
尽管如此,CMS收集器的缺点也是很明显的:
- 对CPU资源太敏感,这点可以这么理解,虽然在并发标记阶段用户线程没有暂停,但是由于收集器占用了一部分CPU资源,导致程序的响应速度变慢
- CMS收集器无法处理浮动垃圾。所谓的“浮动垃圾”,就是在并发标记阶段,由于用户程序在运行,那么自然就会有新的垃圾产生,这部分垃圾被标记过后,CMS无法在当次集中处理它们(为什么?原因在于CMS是以获取最短停顿时间为目标的,自然不可能在一次垃圾处理过程中花费太多时间),只好在下一次GC的时候处理。这部分未处理的垃圾就称为“浮动垃圾”
- 由于CMS收集器是基于“标记-清除”算法的,前面说过这个算法会导致大量的空间碎片的产生,一旦空间碎片过多,大对象就没办法给其分配内存,那么即使内存还有剩余空间容纳这个大对象,但是却没有连续的足够大的空间放下这个对象,所以虚拟机就会触发一次Full GC(这个后面还会提到)这个问题的解决是通过控制参数-XX:+UseCMSCompactAtFullCollection,用于在CMS垃圾收集器顶不住要进行FullGC的时候开启空间碎片的合并整理过程。
七.G1收集器
G1(Garbage-First)收集器是现今收集器技术的最新成果之一,之前一直处于实验阶段,直到jdk7u4之后,才正式作为商用的收集器。
与前几个收集器相比,G1收集器有以下特点:
- 并行与并发
- 分代收集(仍然保留了分代的概念)
- 空间整合(整体上属于“标记-整理”算法,不会导致空间碎片)
- 可预测的停顿(比CMS更先进的地方在于能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒)
此外,G1收集器将Java堆划分为多个大小相等的Region(独立区域),新生代与老年代都是一部分Region的集合,G1的收集范围则是这一个个Region
G1的工作过程如下:
- 初始标记(Initial Marking)
- 并发标记(Concurrent Marking)
- 最终标记(Final Marking)
- 筛选回收(Live Data Counting and Evacuation)
初始标记阶段仅仅只是标记一下GC Roots能够直接关联的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段的用户程序并发运行的时候,能在正确可用的Region中创建对象,这个阶段需要暂停线程。并发标记阶段从GC Roots进行可达性分析,找出存活的对象,这个阶段食欲用户线程并发执行的。最终标记阶段则是修正在并发标记阶段因为用户程序的并发执行而导致标记产生变动的那一部分记录,这部分记录被保存在Remembered Set Logs中,最终标记阶段再把Logs中的记录合并到Remembered Set中,这个阶段是并行执行的,仍然需要暂停用户线程。最后在筛选阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间制定回收计划。整个执行过成功如下:
垃圾收集器常用参数总结