文章目录
- 前言
- 1、新生代垃圾收集器
- 1.1、Serial
- 1.2、ParNew
- 1.3、Parallel Scavenge
- 2、老年代垃圾收集器
- 2.1、Serial Old
- 2.2、Parallel Old
- 2.3、CMS(Concurrent Mark Sweep)
- 3、全堆垃圾收集器
- 3.1、Garbage First(G1)
前言
参考资料:《深入理解Java虚拟机》
垃圾收集器是GC的实践者,目前常用经典的垃圾收集器有7款,如下图所示:
收集器之间的连线说明可以搭配使用,JDK9标识代表在该版本官方已经不支持配合使用了!
接下来逐一介绍这几款垃圾收集器。
1、新生代垃圾收集器
1.1、Serial
顾名思义,单线程的垃圾收集器;这里的单线程不仅仅说明它只使用一个GC线程进行垃圾收集,更重要的是它进行GC时,其他的工作线程必须暂停,直到GC线程工作结束,这种现象就是大名鼎鼎的STW(Stop The World)
,这种现象对很多应用来说是不可接受的!
Serial/Serial Old收集器运行示意图:
1.2、ParNew
ParNew收集器实质是Serial收集器的多线程并行版本。
可以认为ParNew除了同时使用多线程进行GC外,其余行为和Serial收集器完全一致(控制参数、STW、回收策略等),ParNew/Serial Old运行示意图如下:
CMS出现巩固了ParNew的地位,因为新生代只有Serial和ParNew能与CMS配合使用。不过好景不长,更先进的G1全堆垃圾收集器干掉了这对苦命鸳鸯,从JDK9开始,官方希望G1彻底取代这对组合。
ParNew在单核CPU中比Serial效果更差,因为需要频繁的上下文切换;ParNew默认开启的GC线程数与CPU核数相同,可以使用-XX:ParallelGCThreads
参数限制GC的线程数。
1.3、Parallel Scavenge
与前两款新生代收集器一样,Parallel Scavenge同样采用标记-复制算法;该收集器还支持多个GC线程并行(看起来和ParNew没啥区别),接下来我们探讨一下它到底有什么特别之处。
该收集器专注于吞吐量;吞吐量 = CPU运行用户代码时间 / 运行用户代码时间 + 运行GC时间,高吞吐量可以最高效率利用CPU资源,尽快完成程序运算任务,主要适合在后台运算而不需要太多交互的分析任务。
该收集器通过两个参数精确控制吞吐量:
- -XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间(通过牺牲新生代空间和吞吐量实现)
- -XX:GCTimeRatio:直接设置吞吐量大小(默认值99,尽可能保证应用程序执行时间为收集器执行时间的99倍,也就是收集器的时间消耗不超过总运行时间的1%)
相比于ParNew,该收集器最重要的特性自适应调节策略,通过参数-XX:+UseAdaptiveSizePolicy
控制,参数激活后,就不需要手动指定新生代、Eden和Survivor区比例、晋升老年代对象大小等细节参数了,JVM会根据当前系统的运行情况收集性能监控信息,动态调整这些参数提供最合适的停顿时间或最大的吞吐量。
2、老年代垃圾收集器
2.1、Serial Old
和Serial基本一致,也是单线程垃圾收集器,只不过Serial使用于新生代,采用复制算法;Serial使用于老年代,采用标记-整理算法,这里就不过多介绍了,运行原理如图:
2.2、Parallel Old
可以认为它是Parallel Scavenge收集器的老年代版本,这里也不做过多介绍了,大致是一样的,在注重吞吐量或CPU资源稀缺的场合,可以有效考虑使用Parallel Scavenge + Parallel Old组合,运行示意图如下:
2.3、CMS(Concurrent Mark Sweep)
以获取最短回收停顿时间为目标的收集器。
相比于两外两个收集器,CMS采用的是标记-清除算法,它的运行过程分为四个步骤,其中,初始标记和重新标记需要STW:
- 初始标记:仅仅标记GC Roots能直接关联到的对象,速度很快
- 并发标记:从GC Roots直接关联对象开始,遍历整个对象图的过程,过程耗时较长但不需要停顿用户线程
- 重新标记:为了修正并发标记阶段,因用户线程继续运行而导致标记产生变动那部分对象的标记记录,停顿时间大于初始标记,小于并发标记
- 并发清除:清除标记阶段判断的已经死亡对象,该阶段可以与用户线程同时并行
CMS有三个明显的缺点:
- CMS对CPU资源十分敏感
虽然CMS不会造成用户线程停顿,但是会因为占用了一部分线程而导致应用程序变慢,降低总吞吐量- CMS无法处理浮动垃圾
有可能出现Con-current Mode Failure
失败进而导致另一次STW的Full GC产生。在CMS并发标记和并发清理阶段,用户线程还是在运行,自然就会有新的垃圾对象产生,这部分对象还是在标记过程结束后产生的,CMS无法在本次GC过程中处理掉它们,只能等到下一次清除;所以CMS不能像其他收集器那样等到老年代几乎填满在收集,必须预留一部分空间以供并发收集时程序运作使用。可以通过参数-XX:CMSInitiatingOccu-pancyFraction
控制CMS触发百分比,设置太高容易造成大量的并发失败产生,设置太低会导致频繁进行GC- CMS采用标记-清除算法,所以会有内存碎片问题
3、全堆垃圾收集器
3.1、Garbage First(G1)
G1开创了面向局部收集设计思路和基于Region的内存布局形式。
G1把连续的堆内存划分为多个大小相等的独立区域(Region),每个Region都可以根据需要扮演成Eden、Survivor、老年代空间,针对不同角色的Region采用不同的策略去处理,获取很好的收集结果。
Region中有一类特殊的Humongous
区域,专门用于存储大对象。
G1认为只要大小超过一个Region一半的对象皆为大对象(Region大小可以通过参数-XX:G1HeapRegionSize
设定,范围为1~32MB)。对于超过了整个Region容量的超级大对象会被存放在N个连续的Humongous Region
中,G1将其看作老年代的一部分。
G1每次GC的内存空间都是Region大小的整数倍,它会跟踪每个Region垃圾价值大小,价值即回收所获得的空间大小以及回收所需的时间经验值,然后在后台维护一个优先级列表,优先回收收益最大的那些Region。
G1收集器的运作过程大致可划分为以下四个步骤:
- 初始标记:仅仅标记GC Roots直接关联的对象,并且修改TAMS指针,让下个阶段用户线程并发运行时正确地在可用的Region中分配新对象,耗时很短。
- 并发标记:从GC Root开始对堆中对象进行可达性分析,找到要回收的对象,可与用户线程并发执行,耗时较长。
- 最终标记:用户线程短暂暂停,用于处理并发阶段结束后遗留下来的最后那少量的SATB记录。
- 筛选回收:对各个Region根据回收价值和成本进行排序,根据用户期望停顿时间制定回收计划,自由选择任意多个Region作为回收集,然后把决定回收的部分Region存活对象复制到空的Region中,再清理掉整个旧Region全部空间。这里涉及对象的移动必须是暂停用户线程,多条GC线程并行完成。