没有最好的垃圾收集器,也没有万能的垃圾收集器,只有针对具体应用的最合适的垃圾收集器
前言-垃圾收集器的并行与并发
- 并行(Parallel,同时执行):多个线程同时进行垃圾收集,但是所有用户线程都处于暂停状态
- 并发(Concurrent,并行发生):指的是用户线程和垃圾收集线程同时进行,当然了此时的垃圾收集线程也可以是单线程的。
1- Serial (串行) 收集器
- 不仅意味着使用一个线程或者CPU进行垃圾收集。
- 还体现在垃圾收集的串行顺序操作上,即只能暂停当前所有java线程,执行垃圾收集直到垃圾回收的结束。
使用的收集算法:Copying复制算法
- 特点:1.停顿时间长。2.简单高效,没有线程交互的开销
- 应用场景:Client端新生代垃圾收集
2- Serial Old 收集器
Serial 的老年代版本
收集算法:串行(Mark-Compact)标记-整理算法
3- ParNew (Parallel New Generation)收集器
Serial 的多线程的版本,并行的Serial收集器
- 特点:1.存在线程交互的开销,单CPU的情况下效率不高,2. 但是在多CPU的情况下能充分利用系统资源进行垃圾收集
- 应用场景:Server端首选的新生代垃圾收集器,因为在Server端除了Serial外只有它能与CMS配合使用
4- Parallel Scavenge (并行清除)收集器
新生代收集器、吞吐量优先收集器
- 收集算法:并行的Copying复制算法
- 无法和CMS收集器配合使用
- 与其他所有收集器不同的地方--关注的点不同--关注可控的吞吐量(其他的则为停顿时间)
- 吞吐量 = 运行用户代码的时间 / (运行用户代码的时间 + 垃圾收集的时间),衡量的是CPU的利用率
- 其他收集器减少停顿时间是通过牺牲吞吐量来实现的:减小新生代的分区大小,来达到快速回收的目的,但是很快新生代空间不足,导致更频繁的垃圾收集(代价主要体现在线程的交互上,因为线程暂停到恢复需要一定的时间)
- 应用场景:非交互应用--密集计算型程序(不需要太多交互,只要尽快的得到结果)
- GC 自适应的调节策略:使能这个垃圾收集器时,就不需要指定新生代的大小,晋升老年代对象大小这样的细节,虚拟机会根据当前系统的运行信息,动态的调整这些参数以达到最合适的停顿时间或者最大的吞吐量
5- Parallel Old 收集器
Parallel Scavenge收集器的老年代版本
- 收集算法:并行的Mark-Compact算法
- 使用场景:当新生代使用 Parallel Scavenge收集器时,老年代就不能使用CMS收集器了,而使用Series Old收集器在Server端效率太低,所以就出现了 Parallel Old 来搭配使用,实现真正的吞吐量优先,适用于注重吞吐量和CPU资源敏感的场合
6- CMS(Concurrent Mark Sweep)收集器
并发标记清除:以最短回收停顿时间为目标
- 步骤
- ** 初始标记:需要暂停所有用户线程,不过这个阶段只标记GC Roots直接关联对象**,所以停顿时间较短。
- 并发标记:恢复1中暂停的线程,并发的对1中标记的直接关联对象的轮询,以标记这些对象可访问的对象。
- 重新标记:需要暂停整个应用,在2中(并发标记期间)应用可能会修改对象的引用关系或创建新的对象,所以要这部分改变或新创建的对象进行扫描,重新标记。
- 并发清除:恢复3中暂停的线程,进行并发的集中清除标记过的对象。
- 明显的缺点
- CMS对CPU资源非常敏感,在并发阶段,因为占用较多的CPU资源而导致应用程序变慢,总吞吐量会降低。
- 无法处理浮动垃圾(程序运行过程中出现在标记之后的只能在下一次GC时再被清理的垃圾),因为CMS收集器不会像其他垃圾收集器那样等到老年代几乎完全被填满了在进行收集,需要预留一部分空间提供并发收集的程序运行使用。要是CMS运行期间预留的内存无法满足程序的需要,就会触发Concurrent Mode Failure。
- CMS是基于“标记——清除”的并发收集器,收集结束时会出现大量的空间碎片,用 free list 保存可用空间,但是大量碎片会造成明明老年代有大量可用空间,但是在分配大对象时找不到足够的连续空间来存放这个大对象,这将触发一次 Full GC 。
- 使用场景:常用于B/S架构的服务端上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短,已给用户带来较好的体验。
7- G1(Garbage-First) 收集器
面向服务器端应用的垃圾收集器,目标使用来替换CMS,克服CMS的缺点
- 步骤(可与CMS进行对比)
- ** 初始标记:需要暂停所有用户线程,不过这个阶段只标记GC Roots直接关联对象,并修改TAMS(Next Top at Mark Start)的值**,所以停顿时间较短。
- 并发标记:恢复1中暂停的线程,从 GC Root开始对堆中对象进行可达性分析,找出存活对象。
- 最终标记(可并行执行):需要暂停整个应用,在2中(并发标记期间)应用可能会修改对象的引用关系或创建新的对象,所以要这部分改变或新创建的对象的变化记录在线程 Remembered Set Logs里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。
- 筛选回收(可并行执行):恢复3中暂停的线程,首先对各个region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,因为只回收一部分Region,时间是由用户控制的,将大幅提升收集的效率。
- 优点
- 并发与并行:充分利用多CPU、多核环境下的硬件优势,使用多个CPU来减少停顿线程的时间,G1可用通过并发让用户线程继续执行。
- 分代收集:分代的概念在G1中得到保存,G1不需要其他垃圾收集器进行配合就能对整个堆进行回收,而且对于新创建的、已经存活很久的、多次GC仍存活的对象的收集效果很好,可以说是个全能的家伙。
- 空间整合:G1整体上是基于“标记——整理”算法实现的,从局部上(两个Region之间)是基于“复制”算法实现的,所以有这两种算法保证了G1运行期间不会产生空间碎片,收集后能提供规整的可用内存。
- 可预测的停顿:相比CMS,G1建立了可预测的停顿时间模型,能让使用者明确指定在一定长度为M毫秒的时间片段内,消耗在垃圾收收集上的时间不得超过N毫秒,几乎逼近了实时垃圾收集器的性能了。可以有计划的避免在整个Java堆上进行全区域的垃圾收集。具体为:G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的的空间大小与需要花费时间的经验值的综合考量),并在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(Garbage-First),这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。
- Region
在G1收集器中,将Java堆的划分为多个大小相等的独立区域(Region),虽然还保留新生代和老年代的概念,但是已经不是物理上隔离的了,它们都是一部分Region的集合(不需要连续)