深入理解Java虚拟机之经典垃圾收集器
作者:崇尚学技术的科班人
本文参考于《深入理解Java虚拟机》
1. 综述
1. 总述:
如果说收集算法是内存回收的方法论,那垃圾收集器就是内存回收的实践者。《Java虚拟机规范》中对垃圾收集器应该如何实现并没有做出任何规定,因此不同的厂商、不同版本的虚拟机所包含的垃圾收集器都可能会有很大差别,不同的虚拟机一般也都会提供各种参数供用户根据自己的应用特点和要求组合出各个内存分代所使用的收集器。
2. 图示总述
上图展示了七种作用于不同分代的收集器,如果
两个收集器之间存在连线
,就说明它们可以搭配使用
,图中收集器所处的区域
,则表示它是属于新生代收集器抑或是老年代收集器
。
3. 应用中应如何做出选择?
虽然我们会对各个收集器进行比较,但并非为了挑选一个最好的收集器出来
,虽然垃圾收集器的技术在不断进步,但直到现在还没有最好的收集器出现,更加不存在“万能”的收集器,所以我们选择的只是对具体应用最合适的收集器
。这点不需要多加论述就能证明:如果有一种放之四海皆准、任何场景下都适用的完美收集器存在,HotSpot虚拟机完全没必要实现那么多种不同的收集器了。
2. Serial收集器
1. 简介
Serial收集器是最基础、历史最悠久的收集器,曾经(在JDK 1.3.1之前)是HotSpot虚拟机新生代收集器
的唯一选择。大家只看名字就能够猜到,这个收集器是一个单线程工作的收集器
,但它的“单线程”的意义并不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作
,更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程
,直到它收集结束(“Stop The World”)。
2. 图解工作过程
3.使用的垃圾收集算法
标记-复制算法
4. 优点
简单而高效(与其他收集器的单线程相比),对于内存资源受限的环境
,它是所有收集器里额外内存消耗最小
的;对于单核处理器或处理器核心数较少的环境
来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率
。
5. 缺点
在垃圾回收过程中会触发 “Stop The World” 而产生停顿
,从而导致用户体验不好。
6. 主要应用场景
Serial收集器对于运行在客户端模式下的虚拟机
来说是一个很好的选择。
3. ParNew收集器
1. 简介
ParNew收集器实质上是Serial收集器的多线程并行版本
(新生代收集器),除了同时使用多条线程进行垃圾收集之外,其余的行为包括Serial收集器可用的所有控制参数
(例如:-XX:SurvivorRatio、-XX PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法
、Stop The World
、对象分配规则
、回收策略
等都与Serial收集器完全一致,在实现上这两种收集器也共用了相当多的代码。
2. 图解工作过程
3. 使用的垃圾收集算法
标记-复制算法
4. 补充概念
- 并行:并行描述的是
多条垃圾收集器线程之间的关系
,说明同一时间
有多条这样的线程在协同工作,通常默认此时用户线程是处于等待状态。 - 并发:并发描述的是
垃圾收集器线程与用户线程之间的关系
,说明同一时间
垃圾收集器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。
5. 主要应用场景
它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外
,只有它能与 CMS 收集器
(真正意义上的并发收集器,后面会介绍到)配合工作。
4. Parallel Scavenge收集器
1. 简介
Parallel Scavenge收集器也是一款新生代收集器
,它同样是基于标记-复制算法实现的收集器,也是能够并行收集的多线程收集器
。Parallel Scavenge的诸多特性从表面上看和ParNew非常相似。Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间
,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量
。
2. 补充概念
吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值
。
如果虚拟机完成某个任务,用户代码加上垃圾收集总共耗费了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
停顿时间越短
就越适合需要与用户交互或需要保证服务响应质量的程序
,良好的响应速度能提升用户体验;而高吞吐量则可以最高效率地利用处理器资源
,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的分析任务
。
3. 图解工作过程
4. 使用的垃圾收集算法
标记-复制算法
5. 相关的参数
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量
,分别是控制最大垃圾收集停顿时间
的 -XX:MaxGCPauseMillis 参数以及直接设置吞吐量大小
的 -XX:GCTimeRatio 参数。
5. Serial Old收集器
1. 简介
Serial Old是Serial收集器的老年代版本
,它同样是一个单线程收集器
,使用标记-整理算法。
2. 图解工作过程
3. 使用的垃圾收集算法
标记-整理算法
4. 主要的应用场景
供客户端模式下的HotSpot虚拟机使用
。- 在服务端模式下,一种在JDK 5以及之前的版本中
与Parallel Scavenge收集器搭配使用
;另外一种就是作为CMS收集器发生失败时的后备预案
。
6. Parallel Old收集器
1. 简介
Parallel Old是Parallel Scavenge收集器的老年代版本
,支持多线程并发收集,基于标记-整理算法实现。
2. 图解工作过程
3. 使用的垃圾收集算法
标记-整理算法
4. 主要的应用场景
注重吞吐量或者处理器资源较为稀缺的场合
,都可以优先考虑Parallel Scavenge 加 Parallel Old收集器这个组合。
7. CMS收集器
1. 简介
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标
的收集器。CMS收集器是基于标记-清除算法
实现的。它非常适合在注重用户体验的应用上使用
。
2. 工作过程
- 初始标记:
标记一下GC Roots能直接关联到的对象
,速度很快 - 并发标记:从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是
不需要停顿用户线程
,用户线程可以与垃圾收集线程一起并发运行。
- 重新标记:为了
修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录
,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短。 - 并发清除:
清理删除掉标记阶段判断的已经死亡的对象
,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
3. 图解工作过程
4. 使用的垃圾收集算法
标记-清除算法
。
5. 优点
回收停顿时间短
,给用户带来良好的交互体验。并发收集
6. 缺点
对处理器资源非常敏感
无法处理“浮动垃圾”
- 因为CMS收集器是一款基于标记-清除算法的垃圾收集器,
其收集结束时会有大量空间碎片产生
。
7. 主要的应用场景
它非常适合在注重用户体验和要求停顿时间短的应用上使用
。
8. Garbage First收集器
1. 简介
G1 (Garbage-First) 是一款面向服务器的垃圾收集器
,主要针对配备多颗处理器及大容量内存的机器。以极高概率满足 GC 停顿时间要求
的同时,还具备高吞吐量性能特征
。G1在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间,优先处理回收价值收益最大的那些Region
。这种使用 Region划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。
2. 工作过程
- 初始标记:仅仅
只是标记一下GC Roots能直接关联到的对象
,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短
,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。 - 并发标记:从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,
找出要回收的对象
,这阶段耗时较长,但可与用户程序并发执行
。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。最终标记:对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录
。 - 筛选回收:负责更新Region的统计数据,
对各个Region的回收价值和成本进行排序
,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中
,再清理掉整个旧Region的全部空间
。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。
从上述阶段的描述可以看出,G1收集器除了并发标记外,其余阶段也是要完全暂停用户线程的,换言之,它
并非纯粹地追求低延迟
,官方给它设定的目标是在延迟可控的情况下获得尽可能高的吞吐量
,所以才能担当起“全功能收集器”的重任与期望。
3. 图解工作过程
4. 特点
- 并行与并发:
- 分代收集
- 空间整合
- 可预测的停顿
5. 使用此收集器下的堆内存说明
G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region)
,每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间
。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。Region中还有一类特殊的Humongous区域(H),专门用来存储大对象
。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize
设定,取值范围为1MB~32MB,且应为2的N次幂。而对于那些超过了整个Region容量的超级大对象
,将会被存放在N个连续的Humongous Region之中
,G1的大多数行为都把Humongous Region作为老年代的一部分
来进行看待,如上图所示。虽然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们都是一系列区域(不需要连续)的动态集合
。
6. 主要的应用场景
主要使用于服务端模式下的垃圾回收
。
到此这篇关于深入理解Java虚拟机之经典垃圾收集器的文章就介绍到这了,更多相关java垃圾收集器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!