JVM垃圾收集器
7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明可以搭配使用。
Serial收集器
- JVM虚拟机运行在Client模式下的默认新生代收集器。
- 是一个单线程收集器,单线程即说明它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。
- 简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,因此获得最高的单线程收集效率
- 在Client场景下,分配给虚拟机管理的内存一般来说不会很大,收集几十兆甚至一两百兆的新生代停顿时间完全可以控制在几十毫秒最多一百多毫秒以内。
ParNew收集器
- 是Serial收集器的多线程版本
- 运行在Server模式下的虚拟机首选的新生代收集器
- 除Serial收集器外,目前只有它能与CMS收集器配合工作
- 由于存在线程交互的开销,ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果。随着CPU数量增加,默认开启的收集器线程数与CPU的数量相同,可以使用
-XX:ParallelGCThreads
参数来限制垃圾收集的线程数。
Parallel Scavenge收集器
- 多线程新生代收集器
- 基于复制算法实现
- 其它收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。吞吐量是指:CPU用于运行代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
Serial Old收集器
- 是Serial收集器的老年代版本,是一个单线程收集器
- 采用“标记-整理”算法
- Client模式下的虚拟机使用
- 在Server模式下,则它主要还有两大用途:一种用途是在JDK1.5以及之前版本中与Parallel Scavenge收集器搭配使用;另外一种用途是作为CMS收集器的后备预案,在并发收集发生
Concurrent Mode Failure
时使用。
Parallel Old收集器
- 是Parallel Scavenge收集器的老年代版本,是一个多线程收集器
- 采用“标记-整理”算法
- 新生代Parallel Scavenge收集器由于无法与CMS收集器配合工作,只能与Serial Old收集器配合工作,但该收集器在服务端性能差,使用了Parallel Scavenge收集器也未必能在整体应用上获得吞吐量最大化的效果,由于单线程的老年代收集无法充分利用服务器多CPU的处理能力,在老年代很大而且硬件比较高级的环境还不一定有
ParNew+CMS
的组合 - Parallel Old收集器的出现,在注重吞吐量以及CPU资源敏感的场合,优先考虑
Parallel Scacenge + Parallel Old
收集器。
CMS收集器
- 一种以获取最短回收停顿时间为目标的收集器
- 采用“标记-清除”算法
- 收集过程分为4步:
- 初始标记(CMS initial mark):仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,需要停顿
- 并发标记(CMS concurrent mark):进行RootsTracing过程,不需要停顿,耗时长
- 重新标记(CMS remark):为了修正并发标记期间因用户重新继续运作而导致标记产生的变动的哪一部分对象的标记记录,需要停顿
- 并发清除(CMS concurrent sweep):不需要停顿
由于整个过程中耗时最长的并发标记和并发清除过程收集器线程可以与用户线程一起工作,不需要停顿。
- 存在的缺点:
- CMS收集器对CPU资源非常敏感,它虽然不会导致用户线程停顿,但会因为占用一部分线程(CPU资源)而导致应用程序变慢,总吞吐量降低。
- 无法处理浮动垃圾,可能出现
Concurrent Mode Failure
失败而导致另一次Full GC的产生。浮动垃圾是指由于CMS并发清理阶段用户线程还在运行着,伴随着程序运行自然会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在打次收集中处理掉他们,只好留待下一次GC时再清理掉。CMS运行期间预留的内存无法满足程序需要,就会出现一次Concurrent Mode Failure
失败,则将启动后备预案:临时请用Serial Old收集器重新进行老年代的垃圾收集,造成停顿时间加长,性能下降。 - 基于“标记-清除”算法,会有大量的空间碎片产生,往往会出现老年代还有很大空间剩余,但无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。
G1收集器
- 面向服务端应用的垃圾收集器
- 具备特点:
- 并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短停顿时间,部分收集器原需要停顿执行GC动作,G1收集器可以通过并发的方式让Java程序继续执行。
- 分代收集:与其它收集器一样,分代概念在G1中依然得到保留。可以不需要其它收集器配合就能独立管理整个GC堆。
- 空间整合:从整体来看是基于”标记-整理“算法实现的收集器,从局部(两个Region之间)上来看是基于”复制“算法实现,但无论如何这两种算法都意味着G1运作期间不会产生内存空间碎片。
- 可预测的停顿:降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间判断内,消耗在垃圾收集上的时间不得超过N毫秒。
- G1收集器,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不在物理隔离,它们都是一部分Region的结合。
- G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需要的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。
- 每个Region都有一个与之对应的Remembered Set,用来检查Reference引用的对象是否处于不同的Region之中,从而保证不对全堆扫描也不会有遗漏。
- 不维护Remembered Set,将分为下面4个步骤
- 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并修改TEMS(Next Top at Mark Start)的值,让下一阶段用户程序并发允许时,能够正确可用Region中创建新对象,该阶段需要停顿,但耗时很短。
- 并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,找出存活的对象,该阶段比较耗时,但可与用户程序并发执行。
- 最终标记(Final Marking):为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这端时间对象变化记录在线程Reembered Set Logs里面,并把Remembered Set Logs的数据合并到Remembered Set中,该阶段需要停顿线程,但是可并行执行。
- 筛选回收(Live Data Counting Evacuation):首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。