上一篇文章中学习了 JVM 的垃圾回收机制, 和内存分配和回收策略. 不过这都是一些理论知识, 这篇文章中会学习一下 HotSpot 虚拟机中的垃圾收集器, 这都是垃圾回收理论的具体实现.
垃圾收集器
HotSpot 虚拟机中有多种收集器, 不同的收集器特点也不同, 各年代使用的收集器也可以根据应用的特点和要求进行组合.
Serial 收集器
Serial 收集器是一个单线程的收集器, 它不仅只会使用一个 CPU 或一条收集线程去完成垃圾收集工作, 而且在垃圾收集时, 必须暂停其他所有的工作线程, 直到它收集结束.
Serial 收集器是 HotSpot 虚拟机在运行 Client 模式下的默认新生代收集器. 在垃圾收集时, 年轻代使用 "复制" 算法, 老年代使用 "标记 - 整理" 算法.
但它也有优点, 与其他收集器的单线程相比, 由于没有现成交互的开销, 专心做垃圾收集, 所以其简单而高效;
可以使用 -XX:UseSerialGC 参数选择使用 Serial 收集器, 此时年轻代采用 Serail, 老年代采用 Serial Old.
ParNew 收集器
ParNew 收集器是 Serial 收集器的多线程版本, 除了使用多线程进行垃圾回收外, 其他几乎一样.
它是许多运行在 Server 模式下的虚拟机首选的新生代收集器, 一个很重要的原因是除了 Serial 收集器外, 只有 ParNew 收集器与 CMS 收集器配合工作. 垃圾收集时, 年轻代使用 "复制" 算法, 老年代使用 "标记 - 整理" 算法.
ParNew 收集器默认开启的线程数与 CPU 的数量相同, 可以使用 -XX:ParallelGCThreas 参数来限制垃圾收集的线程数. 使用 -XX:UseParNewGC 参数来使用 ParNew 收集器.
Parallel Scavenge 收集器
Parallel Scavenge 收集器是新生代收集器, 也是使用复制算法的多线程收集器. 与其他收集器不同的是, 它关注的是达到一个可控制的吞吐量, 吞吐量 = 运行代码时间 / (运行代码时间 + 垃圾收集时间).
Parallel Scanenge 收集器提供了两个参数用于精确控制吞吐量. 第一个是控制最大垃圾收停顿时间的 -XX:MaxGCPauseMillis 参数. 它允许是一个大于 0 的毫秒数, 收集器将尽可能保证内存回收时间不超过设定值. 这个参数也不是越小越好, GC 停顿时间缩短是以牺牲吞吐量和新生代空间为代价换取的.
第二个是直接设置吞吐量大小的 -XX:GCTimeRatio 参数. 它允许是一个大于 0 且小于 100 的整数, 就是垃圾收集时间占总时间的比率.
另外, Parallel Scavenge 收集器拥有自适应调节机制, 它不需要手工指定新生代的大小 (-Xmn),Eden 与 Survivor 区的比例 (-XX:SurvivorRatio), 晋升老年代对象大小 -XX:PretenureSizeThreshold 等细节参数, 虚拟机会根据当前系统的运行情况收集性能监控信息, 动态调整参数以提供最合适的停顿时间及最大的吞吐量. 可使用 -XX:UseAdaptiveSizePolicy 参数来开启.
Serial Old 收集器
Serial Old 是 Serial 收集器的老年代版本, 是一个单线程收集器, 使用 "标记 - 整理算法".
Parallel Old 收集器
Parallel Old 是 Parallel Scavenge 收集器的老年代版本, 使用多线程和 "标记 - 整理" 算法. 如果新生代选择了 Parallel Scavenge 收集器, 老年代只能选择 Serial Old 收集器.
CMS 收集器
CMS(Concurrent Mark Sweep) 是一种以获取最短停顿时间为目标的收集器, 使用 "标记 - 清除" 算法. 如果应用重视响应速度, 希望停顿时间最短, 就可以选择 CMS 收集器.
它的运作过程可分为 4 个步骤:
初始标记: 仅仅标记 GC Roots 能直接关联到的对象, 速度很快, 需要 Stop the world.
并发标记: 进行 GC Roots Tracing 过程.
重新标记: 修正并发标记期间因用户程序运行而导致标记产生变动的对象的标记记录.
并发清除.
CMS 的主要优点是并发收集, 低停顿. 但也有三个缺点:
对 CPU 资源非常敏感. 并发阶段虽不会导致用户线程停顿, 但会因为占用资源而导致程序变慢, 总吞吐量降低.
无法处理浮动垃圾, 也就是在标记过程后, 清除阶段产生但当次收集中不能处理的垃圾, 可能出现
Concurrent Mode Failure
失败而导致另一次 Full FC 的产生.
CMS 基于标记 - 清除算法, 收集结束时会产生大量空间碎片. 碎片过多时, 无法找到足够的连续空间来分配大对象, 不得不提前出发一次 Full GC.
可以使用 -XX:UseConcMarkSweepGC 参数来选择 CMS 收集器.
G1 收集器
G1(Garbage-First) 是一款面向服务端应用的垃圾收集器. 它的特点如下:
并行与并发: 充分利用多 CPU, 多核环境, 使用多个 CPU 来缩短停顿的时间, 部分需要其他收集器原本需要停顿 Java 线程执行的 GC 动作, G1 收集器可通过并发的方式让 Java 程序继续执行.
分代收集: G1 收集器能独立管理整个 GC 堆, 并且能采用不同的方式处理不同时期的对象.
空间整合: G1 收集器从整体来看, 基于 "标记 - 整理" 算法实现; 从局部来看, 基于 "复制" 算法实现.
可预测的停顿: G1 能明确指定垃圾收集的限制时间.
使用 G1 收集器时, 将 Java 堆划分为多个大小相等的区域 Region.G1 跟踪各个 Region 的回收价值和成本 (回收获得空间及回收时间), 后台会维护一个优先列表, 每次根据允许的收集时间, 优先回收价值最大的 Region. 它通过使用 Remembered Set 来避免全堆扫描.
G1 收集器的运行步骤可分为:
初始标记: 仅仅标记一下 GC Roots 直接能关联到的对象, 需要停顿, 但耗时很短.
并发标记: 从 GC Roots 开始对堆中对象进行可达性分析, 找出存活的对象, 耗时较长, 但可并发执行.
最终标记: 修正并发标记期间因用户程序运行而导致标记产生变动的对象的标记记录.
筛选回收: 对各个 Region 的回收价值和成本进行排序, 根据指定的 GC 停顿时间制定回收计划.
常用收集器组合
HotSpot 虚拟机中包含了七种垃圾收集器, 如下图:
它们的组合说明如下:
新生代收集器 | 年老代收集器 | 说明 |
---|---|---|
Serial | Serial Old | 都是单线程,GC 时会暂停所有应用线程。 使用 -XX:+UseSerialGC 选项来开启 |
Serial | CMS + Serial Old | CMS 是并发 GC,不需要暂停所有应用线程。 当 CMS 进行 GC 失败时,会自动使用 Serial Old 策略进行 GC 使用 -XX:+UseConcMarkSweepGC 选项来开启 |
ParNew | CMS | ParNew 是 Serial 的并行版本,可以指定 GC 线程数 < br ztid="176" ow="0" oh="0"> 默认 GC 线程数为 CPU 的数量 |
ParNew | Serial Old | 使用 -XX:+UseParNewGC 选项来开启 |
Parallel Scavenge | Serial Old | Parallel Scavenge 策略关注吞吐量,适用于后台持久运行的应用程序 < br ztid="185" ow="0" oh="0"> 使用 -XX:+UseParallelGC 选项来开启 |
Parallel Scavenge | Parallel Old | Parallel Old 是 Serial Old 的并行版本 < br ztid="190" ow="0" oh="0"> 使用 -XX:+UseParallelOldGC 选项来开启 |
G1GC | G1GC | -XX:+UseG1GC #开启 < br ztid="195" ow="0" oh="0">-XX:MaxGCPauseMillis #暂停时间目标 |
参考资料
雨点的名字:[JVM 虚拟机] (3)--- 垃圾回收器 https://www.cnblogs.com/qdhxhz/p/9211269.html
来源: https://juejin.im/post/5c73c8866fb9a049f43bf4fb