如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器则内存回收的具体实现。Java虚拟机规范中对垃圾收集应该如何实现并没有任何规定,因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器都可能会有很大的差别,并且一般都会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的垃圾收集器。这里讨论的收集器基于Sun HotSpot虚拟机1.6版本Update 22,这个虚拟机包含的所有收集器如图: 图中展示了7种不同分代的收集器,如果这两个收集器之间存在连线,就说明他们可以搭配使用。
Serial收集器
新生代收集器,采用复制算法,是最基本、历史最悠久的收集器,Jdk1.3.1之前虚拟机新生代收集的唯一选择。单线程收集器,在进行垃圾收集时,必须暂停其他所有工作线程(Stop The World),直到它收集结束。虚拟机运行在Client模式下的默认新生代收集器。单CPU的情况下由于没有线程交互开销,可以获得最高的单线程收集效率。
ParNew收集器
新生代收集器,采用复制算法,Serial收集器的多线程版本,除了使用多线程,其余行为与Serial完全一致(包括所有控制参数[-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等]、收集算法、Stop The World、对象分配规则、回收策略) ,是除了Serial之外唯一能与CMS配合使用的新生代收集器。ParNew也是使用-XX:UseConcMarkSweepGC选项后的默认新生代收集器,也可通过使用-XX:+UseParNewGC选项强制使用它。使用该收集器时默认开启收集线程数量与CPU个数相同,可使用-XX:ParallelGCThread参数限制线程数。
Parallel Scavenge收集器
新生代收集器,使用复制算法、并行的多线程收集器。其他收集器关注点是尽可能缩短垃圾收集时间,Parallel Scavenge的目标却是:达到一个可控的吞吐量。
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
停顿时间短适合与用户交互的程序,良好的响应速度能提升用户的体验;而高吞吐量则可以最高效率地利用CPU时间,尽快地完成运算任务,主要适合在后台运算而不需要太多交互的任务。
Parallel Scavenge提供了两个参数用于精确控制吞吐量:
- -XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间,参数允许的是一个大于0的毫秒数,收集器将尽力保证内存回收花费的时间不超过设定值。
- -XX:GCTimeRatio:直接设置吞吐量大小,参数的值应当是一个大于0小于100的整数。也就是垃圾收集时间占总时间的比率,相当于是吞吐量的倒数。
另外还有一个参数值得关注:-XX:+UseAdaptiveSizePolicy,这是一个开关参数,当参数打开后不需要手工指定新生代大小、Eden与Surivor区的比例等参数细节了,虚拟机会根据当前系统的运行情况动态调整这些参数以提供最合适的停顿时间或最大吞吐量。
Serial Old收集器
Serial的老年代版本,单线程收集器,使用“标记-整理”算法。Client模式运行的虚拟机的默认老年代收集器。主要有两大用途:
- Jdk1.5及之前版本与Parallel Scavenge搭配使用。
- 作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure的时候使用。
Parallel Old收集器
Parallel Scavenge的老年代版本,使用多线程和“标记-整理”算法。Parallel Scavenge加Parallel Old的组合终于成为“吞吐量优先”收集名副其实的组合。
CMS收集器
Concurrent Mark Sweep,是一种以获得最短回收停顿时间为目标的收集器。基于“标记-清除”算法实现。整个收集过程分为4个步骤:
- 初始标记(CMS initial mark):仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快。
- 并发标记(CMS Concurrent mark):进行GC Roots Tracing的过程
- 重新标记(CMS remark):为了修正并发标期间,因用户程序继续运行导致标记产生变动的那一部分对象的标记记录,停顿时间一般会比标记阶段稍长一些,但远比并发标记的时间短
- 并发清除(CMS concurrent sweep)
由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行的。
CMS收集器主要有以下三个缺点:
- CMS对于CPU资源敏感,CMS默认启动的回收线程数是(CPU数量+3)/4。
- 无法处理浮动垃圾(并行过程中新产生的垃圾):CMS需预留空间提供并发收集时程序运作使用,默认设置下为老年代达使用率达到68%时触发,可通过参数(-XX:CMSInitiatingOccupancyFraction)调整,不建议设置太高,因为太高会产生ConcurrentModeFailure而导致Serial Old收集而拉长停顿时间。
- 由于采用“标记-清除”算法会产生内存碎片,碎片过多时将给大对象带来很大机率的分配失败,而不得不提前触发一次Full GC。可通过-XX:+UseCMSCompactAtFullCollection用于“享受”完Full GC后额外免费附送一次碎片整理,但会增长停顿时间。-XX:CMSFullGCsBeforeCompaction设置执行完多少次不压缩Full GC之后紧跟着来一次带压缩的。
G1收集器
基于“标记-整理”算法实现,可非常精确的控制停顿时间,极力避免完全区域垃圾收集,划分多个独立区域(固定大小),并跟踪这些区域垃圾段义和程序,在后台维护一个优先列表,每次根据允许的时间收集垃圾最多的区域。
垃圾收集器参数
参数 | 描述 |
UseSerialGC | 虚拟机运行在Client模式下的默认值,打开此开关后,使用Serial+Serial Old的收集器组合进行内存回收。 |
UseParNewGC | 打开此开关后,使用ParNew+Serial Old的收集器组合进行内存回收。 |
UseConcMarkSweepGC | 打开此开关后,使用ParNew+CMS+Serial Old的收集器组合进行内存回收。Serial Old收集器将作为CMS收集器出现Concurrent Mode Failure失败后的后备收集器使用。 |
UseParallelGC | 虚拟机运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge+Serial Old(PS MarkSweep)的收集器器组合进行内存回收。 |
UseParallelOldGC | 打开此开关后使用Parallel Scavenge+Parallel Old的收集器组合进行内存回收 |
SurvivorRatio | 新生代中Eden区域与Survivor区域的容量比值,默认为8,代表Eden:Survivor=8:1 |
PretenureSizeThreshold | 直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配 |
MaxTenuringThreshold | 晋升到老年代的对象年龄。每个对象在坚持过一次Minor GC之后,年龄就加1,当超过这个参数值时就进入老年代 |
UseAdaptiveSizePolicy | 动态调整Java堆中各个区域的大小以及进行老个代的年龄。 |
HandlePromotionFailure | 是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个Eden和Survivor区的所有对象都存活的极端情况。 |
ParallelGCThreads | 设置并行GC进行内存回收的线程数 |
GCTimeRatio | GC时间占总时间的比率,默认值为99,即允许1%r GC收集时间。仅在使用Parallel Scavenge收集器时生效 |
MaxGCPauseMillis | 设置GC的最大停顿时间。仅在使用Parallel Scavenge收集器时生效 |
CMSInitiatingOccupancyFration | 设置CMS收集器在老年代空间被使用多少后触发垃圾收集。默认值为68%,仅在CMS收集器时生效 |
UseCMSCompactAtFullCollection | 设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片整理。仅在使用CMS收集器时生效 |
CMSFullGCsBeforeCompaction | 设置CMS收集器在进行若干次垃圾收集后再启动一次内存碎片整理。仅在使用CMS收集器时生效 |