知识回顾:
第一篇《Jvm 垃圾回收器(基础篇) https://www.cnblogs.com/chenpt/p/9797126.html 》主要讲述了判断对象的生死? 两种基础判断对象生死的算法, 引用计数法, 可达性分析算法, 方法区的回收. 在第二篇《Jvm 垃圾回收器(算法篇) https://www.cnblogs.com/chenpt/p/9799095.html 》中主要介绍了垃圾回收的几种常用算法: 标记 - 清除, 复制算法, 标记 - 整理算法, 分代收集算法. 那么接下来我们重点研究 Jvm 的垃圾收集器(serial 收集器, parnew 收集器, parallel scavenge 收集器, serial old 收集器, parallel old 收集器, cms 收集器, g1 收集器). 前面说了那么多就是为它做铺垫的.
正式进入前先看下图解 HotSpot 虚拟机所包含的收集器:
图中展示了 7 种作用于不同分代的收集器, 如果两个收集器之间存在连线, 则说明它们可以搭配使用. 虚拟机所处的区域则表示它是属于新生代还是老年代收集器.
新生代收集器: Serial,ParNew,Parallel Scavenge
老年代收集器: CMS,Serial Old,Parallel Old
整堆收集器: G1
几个相关概念:
并行收集: 指多条垃圾收集线程并行工作, 但此时用户线程仍处于等待状态.
并发收集: 指用户线程与垃圾收集线程同时工作(不一定是并行的可能会交替执行). 用户程序在继续运行, 而垃圾收集程序运行在另一个 CPU 上.
吞吐量: 即 CPU 用于运行用户代码的时间与 CPU 总消耗时间的比值(吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间 )). 例如: 虚拟机共运行 100 分钟, 垃圾收集器花掉 1 分钟, 那么吞吐量就是 99%
一: Serial 收集器
Serial 收集器是最基本的, 发展历史最悠久的收集器.
特点: 单线程, 简单高效(与其他收集器的单线程相比), 对于限定单个 CPU 的环境来说, Serial 收集器由于没有线程交互的开销, 专心做垃圾收集自然可以获得最高的单线程手机效率. 收集器进行垃圾回收时, 必须暂停其他所有的工作线程, 直到它结束(Stop The World).
应用场景: 适用于 Client 模式下的虚拟机.
Serial / Serial Old 收集器运行示意图
二: ParNew 收集器
ParNew 收集器其实就是 Serial 收集器的多线程版本.
除了使用多线程外其余行为均和 Serial 收集器一模一样(参数控制, 收集算法, Stop The World, 对象分配规则, 回收策略等).
特点: 多线程, ParNew 收集器默认开启的收集线程数与 CPU 的数量相同, 在 CPU 非常多的环境中, 可以使用 - XX:ParallelGCThreads 参数来限制垃圾收集的线程数.
和 Serial 收集器一样存在 Stop The World 问题
应用场景: ParNew 收集器是许多运行在 Server 模式下的虚拟机中首选的新生代收集器, 因为它是除了 Serial 收集器外, 唯一一个能与 CMS 收集器配合工作的.
ParNew/Serial Old 组合收集器运行示意图如下:
三: Parallel Scavenge 收集器
与吞吐量关系密切, 故也称为吞吐量优先收集器.
特点: 属于新生代收集器也是采用复制算法的收集器, 又是并行的多线程收集器(与 ParNew 收集器类似).
该收集器的目标是达到一个可控制的吞吐量. 还有一个值得关注的点是: GC 自适应调节策略(与 ParNew 收集器最重要的一个区别)
GC 自适应调节策略: Parallel Scavenge 收集器可设置 - XX:+UseAdptiveSizePolicy 参数. 当开关打开时不需要手动指定新生代的大小 (-Xmn),Eden 与 Survivor 区的比例(-XX:SurvivorRation), 晋升老年代的对象年龄(-XX:PretenureSizeThreshold) 等, 虚拟机会根据系统的运行状况收集性能监控信息, 动态设置这些参数以提供最优的停顿时间和最高的吞吐量, 这种调节方式称为 GC 的自适应调节策略.
Parallel Scavenge 收集器使用两个参数控制吞吐量:
XX:MaxGCPauseMillis 控制最大的垃圾收集停顿时间
XX:GCRatio 直接设置吞吐量的大小.
四: Serial Old 收集器
Serial Old 是 Serial 收集器的老年代版本.
特点: 同样是单线程收集器, 采用标记 - 整理算法.
应用场景: 主要也是使用在 Client 模式下的虚拟机中. 也可在 Server 模式下使用.
Server 模式下主要的两大用途(在后续中详细讲解...):
在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用.
作为 CMS 收集器的后备方案, 在并发收集 Concurent Mode Failure 时使用.
Serial / Serial Old 收集器工作过程图(Serial 收集器图示相同):
五: Parallel Old 收集器
是 Parallel Scavenge 收集器的老年代版本.
特点: 多线程, 采用标记 - 整理算法.
应用场景: 注重高吞吐量以及 CPU 资源敏感的场合, 都可以优先考虑 Parallel Scavenge+Parallel Old 收集器.
Parallel Scavenge/Parallel Old 收集器工作过程图:
六: CMS 收集器
一种以获取最短回收停顿时间为目标的收集器.
特点: 基于标记 - 清除算法实现. 并发收集, 低停顿.
应用场景: 适用于注重服务的响应速度, 希望系统停顿时间最短, 给用户带来更好的体验等场景下. 如 web 程序, b/s 服务.
CMS 收集器的运行过程分为下列 4 步:
初始标记: 标记 GC Roots 能直接到的对象. 速度很快但是仍存在 Stop The World 问题.
并发标记: 进行 GC Roots Tracing 的过程, 找出存活对象且用户线程可并发执行.
重新标记: 为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录. 仍然存在 Stop The World 问题.
并发清除: 对标记的对象进行清除回收.
CMS 收集器的内存回收过程是与用户线程一起并发执行的.
CMS 收集器的工作过程图:
CMS 收集器的缺点:
对 CPU 资源非常敏感.
无法处理浮动垃圾, 可能出现 Concurrent Model Failure 失败而导致另一次 Full GC 的产生.
因为采用标记 - 清除算法所以会存在空间碎片的问题, 导致大对象无法分配空间, 不得不提前触发一次 Full GC.
七: G1 收集器
一款面向服务端应用的垃圾收集器.
特点如下:
并行与并发: G1 能充分利用多 CPU, 多核环境下的硬件优势, 使用多个 CPU 来缩短 Stop-The-World 停顿时间. 部分收集器原本需要停顿 Java 线程来执行 GC 动作, G1 收集器仍然可以通过并发的方式让 Java 程序继续运行.
分代收集: G1 能够独自管理整个 Java 堆, 并且采用不同的方式去处理新创建的对象和已经存活了一段时间, 熬过多次 GC 的旧对象以获取更好的收集效果.
空间整合: G1 运作期间不会产生空间碎片, 收集后能提供规整的可用内存.
可预测的停顿: G1 除了追求低停顿外, 还能建立可预测的停顿时间模型. 能让使用者明确指定在一个长度为 M 毫秒的时间段内, 消耗在垃圾收集上的时间不得超过 N 毫秒.
G1 为什么能建立可预测的停顿时间模型?
因为它有计划的避免在整个 Java 堆中进行全区域的垃圾收集. G1 跟踪各个 Region 里面的垃圾堆积的大小, 在后台维护一个优先列表, 每次根据允许的收集时间, 优先回收价值最大的 Region. 这样就保证了在有限的时间内可以获取尽可能高的收集效率.
G1 与其他收集器的区别:
其他收集器的工作范围是整个新生代或者老年代, G1 收集器的工作范围是整个 Java 堆. 在使用 G1 收集器时, 它将整个 Java 堆划分为多个大小相等的独立区域 (Region). 虽然也保留了新生代, 老年代的概念, 但新生代和老年代不再是相互隔离的, 他们都是一部分 Region(不需要连续) 的集合.
G1 收集器存在的问题:
Region 不可能是孤立的, 分配在 Region 中的对象可以与 Java 堆中的任意对象发生引用关系. 在采用可达性分析算法来判断对象是否存活时, 得扫描整个 Java 堆才能保证准确性. 其他收集器也存在这种问题(G1 更加突出而已). 会导致 Minor GC 效率下降.
G1 收集器是如何解决上述问题的?
采用 Remembered Set 来避免整堆扫描. G1 中每个 Region 都有一个与之对应的 Remembered Set, 虚拟机发现程序在对 Reference 类型进行写操作时, 会产生一个 Write Barrier 暂时中断写操作, 检查 Reference 引用对象是否处于多个 Region 中(即检查老年代中是否引用了新生代中的对象), 如果是, 便通过 CardTable 把相关引用信息记录到被引用对象所属的 Region 的 Remembered Set 中. 当进行内存回收时, 在 GC 根节点的枚举范围中加入 Remembered Set 即可保证不对全堆进行扫描也不会有遗漏.
如果不计算维护 Remembered Set 的操作, G1 收集器大致可分为如下步骤:
初始标记: 仅标记 GC Roots 能直接到的对象, 并且修改 TAMS(Next Top at Mark Start)的值, 让下一阶段用户程序并发运行时, 能在正确可用的 Region 中创建新对象.(需要线程停顿, 但耗时很短.)
并发标记: 从 GC Roots 开始对堆中对象进行可达性分析, 找出存活对象.(耗时较长, 但可与用户程序并发执行)
最终标记: 为了修正在并发标记期间因用户程序执行而导致标记产生变化的那一部分标记记录. 且对象的变化记录在线程 Remembered Set Logs 里面, 把 Remembered Set Logs 里面的数据合并到 Remembered Set 中.(需要线程停顿, 但可并行执行.)
筛选回收: 对各个 Region 的回收价值和成本进行排序, 根据用户所期望的 GC 停顿时间来制定回收计划.(可并发执行)
G1 收集器运行示意图:
-- 结束 -- JVM 垃圾收集暂告一段落.
Jvm 垃圾回收器(终结篇)
来源: http://www.bubuko.com/infodetail-2983928.html