介绍
垃圾收集算法是内存回收的方法,那么垃圾收集器就是根据算法去进行垃圾回收的具体实现。java虚拟机规范没有规定垃圾收集器应该如何实现,所以不同公司有着不同的垃圾收集器,下面介绍java7 Update14之后的HotSpot虚拟机,需要注意的是没有最好的,只有最适合的垃圾收集器
新生代收集器
Serial收集器
jdk 1.3以前,回收新生代内存的唯一选择
- 最基本、发展最悠久
- 单线程垃圾收集器
- 回收垃圾时,会暂停其他线程(Stop The World)
- 收集完成之后,其他线程才能继续执行
- 效率比较低
- 简单而高效(与其他收集器的单线程相比)
仍然是虚拟机运行在Client模式下的默认新生代收集器
运行示意图:
ParNew收集器
ParNew收集器相当于Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其他行为与Serial收集器完全一样。
- 并行多线程收集器
- 回收垃圾时,会暂停其他线程,多个垃圾收集器同时进行收集,从而降低了回收时间
- 并行:指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态
- 收集完成之后,其他线程才能继续执行
- 新生代收集器
运行示意图:
Parallel Scavenge收集器
- 新生代收集器
- 垃圾回收算法:标记复制算法
- 并行多线程收集器
- 目标:达到一个可控的吞吐量
- 吞吐量 = 程序运行时间/(程序运行时间+GC时间)
- 也就是说Parallel收集器是为了控制垃圾回收时间
- 提供了两个参数用于精确控制吞吐量:
- 控制最大垃圾收集停顿时间:-XX:MaxGCPauseMills
- 不是越小越好,小了,收集不完全,就会让垃圾收集更加频繁,反而适得其反
- 直接设置吞吐量:-XX:GCTimeRatio
- 控制最大垃圾收集停顿时间:-XX:MaxGCPauseMills
- 开关参数: -XX:+UseAdaptiveSizePolicy
- 打开此参数后,系统会根据系统的运行情况收集性能监控信息,动态调整jvm内存细节参数,以提供最合适的停顿时间或最大的吞吐量
- GC自适应的调节策略
老年代收集器
Serial Old收集器
Serial Old是Serial收集器的老年代版本
- 单线程收集器
- 标记-整理算法
- 主要是给Client模式下的虚拟机使用
- 在JDK1.5及之前的版本,与Parallel Scavenge收集器配合使用
- 作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用
运行示意图:
Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,用于老年代的垃圾回收,与Parallel Scavenge不同的是,它使用的是“标记-整理算法”。适用于注重于吞吐量及CPU资源敏感的场合。
- 使用方式:-XX:+UseParallelOldGC,打开该收集器后,将使用Parallel Scavenge(年轻代)+Parallel Old(老年代)的组合进行GC。
- 吞吐量太大,会导致GC次数增加,会降低性能
- 吞吐量太小,停顿时间太长,也会降低性能
注意,Parallel Scavenge收集器只能与Serial Old或者Parallel Old收集器配合使用
运行示意图:
CMS收集器
CMS收集器的目标是获取最短回收停顿时间,牺牲了系统的吞吐量来追求收集速度,适合追求垃圾收集速度的服务器上。
- 老年代垃圾收集器
- 并发收集器:可以与用户线程并发操作,指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾手机程序运行在另一个CPU上
- 最短回收停顿时间
- 收集过程:
- 初始标记:标记一下GC Roots能直接关联到的对象。需要STW(Stop The World),速度很快。
- 并发标记(CMS concurrent mark):进行GC Roots Tracing。不需要STW。会产生浮动垃圾。
- 并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。这部分垃圾就称为“浮动垃圾”
- 重新标记:找到并发标记期间产生的浮动垃圾。需要STW,停顿时间一般会比初始标记稍长,但远比并发标记短。
- 并发清除:清除已标记的垃圾。不需要STW,会产生浮动垃圾,只能等待下一次GC清理
- 优点:
- 并发收集
- 低停顿
- 缺点:
- 占用大量CPU资源
- 无法处理浮动垃圾
- 可能出现 Concurrent Mode Failure
- 空间碎片
运行示意图:
Concurrent Mode Failure:
当young gc的时候,把eden和survivor里的都还存活的对象,统一移到另一个survivor区中时,发现装不下了,就需要把部分对象,放到老年代中去,结果老年代空间也不足,这种场景呢,叫做promotion failed
在promotion failed的前提下,老年代恰好还正在full gc,那么就会有字样提示:concurrent mode failure。
G1垃圾收集器
设计初衷是为了尽量缩短处理超大堆(大于4GB)时产生的停顿。相对于CMS的优势而言是内存碎片的产生率大大降低。
- G1收集器是面向服务端应用的垃圾收集器,目标是为了替换掉CMS收集器
- 并行与并发:利用
- 分代收集
- 最大特点:引入分区概念,弱化了分代
G1相对于CMS的改进:
- 空间整合算法:G1基于标记-整理算法, 不会产生空间碎片,分配大对象时不会无法得到连续的空间而提前触发一次FULL GC。
- 停顿时间可控: G1可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象。
- 通过考察各个区域,哪个区域回收后的效果最大,就去回收哪个区域(分区)
- 会在后台存放一张表Remember Set,用于考察各个区域的回收效率
- G1能更充分的利用CPU,多核环境下的硬件优势来缩短stop the world的停顿时间。
CMS和G1的区别:
- CMS中,堆被分为PermGen,YoungGen,OldGen;而YoungGen又分了两个survivo区域。在G1中,堆被平均分成几个区域(region),在每个区域中,虽然也保留了新老代的概念,但是收集器是以整个区域为单位收集的。
- G1在回收内存后会马上同时做合并空闲内存的工作、而CMS默认是在STW(stop the world)的时候做。
- G1会在Young GC中使用、而CMS只能在O区使用。
G1垃圾收集算法主要应用在多CPU大内存的服务中,在满足高吞吐量的同时,尽可能的满足垃圾 回收时的暂停时间。
G1收集过程示意图:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收(CMS这里是并发清除,所以会产生浮动垃圾),G1这里可以并发也可以串行
G1堆内存结构
堆内存会被切分成很多个固定大小区域(Region),每个是连续范围的虚拟内存
堆内存中一个区域的大小,可以通过 -XX:G1HeapRegionSize
参数指定,大小区间最小1M,最大32M,是2的幂次方
默认是把堆内存按照2048份均分
G1堆内存分配
每个Region被标记了E、S、O和H,这些区域在逻辑上被映射为Eden,Survivor和老年代。
存活的对象从一个区域转移(即复制或移动)到另一个区域。区域被设计为并行收集垃圾,可能会暂停所有应用线程。
如下图所示,区域可以分配到Eden,survivor和老年代。此外,还有第四种类型,被称为巨型区域(Humongous Region)。Humongous区域是为了那些存储超过50%标准region大小的对象而设计的,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。
垃圾收集器的配合
图中用线连起来的两个收集器可以相互配合使用
参考
- 本文作者: xczll
- 本文链接: https://xczllgit.github.io/2020/03/26/jvm/2020-03-26-garbageCollectionMachine/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!