JVM专题JVM · Java虚拟机原理 · JVM上语言·框架· 生态系统

JVM之垃圾收集器

2018-12-19  本文已影响5人  LandHu

背景:看完《深入理解Java虚拟机》和相关博客,对JVM还是没有一个条理清晰的认识,遂提取了书中相关知识点和参考相关优秀博客并整理成JVM专题博文系列,帮助自己巩固并理清有关JVM的知识重点,也分享出来给有需要的童鞋,如有差错,欢迎拍砖!

垃圾收集算法是方法论,垃圾收集器是具体实现。
JVM规范对于垃圾收集器的应该如何实现没有任何规定,因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器差别较大,这里只看HotSpot虚拟机。

JDK7/8后,HotSpot虚拟机所有收集器及组合(连线)如下:


HotSpot虚拟机收集器

Serial收集器

类别:新生代-串行-收集器
策略:标记-复制-清除;
优点:简单高效,适合Client模式的桌面应用(Eclipse);
缺点:多核环境下,无法充分利用资源;

Serial收集器是最基本、历史最久的收集器,曾是新生代收集的唯一选择。他是单线程的,只会使用一个CPU或一条收集线程去完成垃圾收集工作,并且它在收集的时候,必须暂停其他所有的工作线程,直到它结束,即“Stop the World”。停掉所有的用户线程,对很多应用来说难以接受。比如你在做一件事情,被别人强制停掉,你心里奔腾而过的“羊驼”还数的过来吗?

尽管如此,它仍然是虚拟机运行在client模式下的默认新生代收集器:简单而高效(与其他收集器的单个线程相比,因为没有线程切换的开销等)。

工作示意图:

Serial收集器

ParNew收集器

类别:新生代-并行-收集器
策略:标记-复制-清除;
优点:多线程、独占式,多核环境下提高CPU利用率;
缺点:单核环境下比Serial效率低;

ParNew收集器是Serial收集器的多线程版本,除了使用了多线程之外,其他的行为(收集算法、stop the world、对象分配规则、回收策略等)同Serial收集器一样。
是许多运行在Server模式下的JVM中首选的新生代收集器,其中一个很重还要的原因就是除了Serial之外,只有他能和老年代的CMS收集器配合工作。

工作示意图:

ParNew收集器

Parallel Scavenge收集器

类别:新生代-并行-收集器
策略:标记-复制-清除;
优点:精准控制“吞吐量”、gc时间。吞吐量=执行用户代码时间/(执行用户代码时间+内存回收时间);配置参数(可通过参数精准调控)

新生代收集器,并行的多线程收集器。它的目标是达到一个可控的吞吐量(就是CPU运行用户代码的时间与CPU总消耗时间的比值,即 吞吐量=行用户代码的时间/[行用户代码的时间+垃圾收集时间]),这样可以高效率的利用CPU时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。

Serial Old收集器

类别:老年代-串行-收集器)
策略:标记-清除-整理;
优点:简单高效;
缺点:多核环境下,无法充分利用资源;

Serial 收集器的老年代版本,单线程,“标记整理”算法,主要是给Client模式下的虚拟机使用。
另外还可以在Server模式下:
JDK 1.5之前的版本中雨Parallel Scavenge 收集器搭配使用
可以作为CMS的后背方案,在CMS发生Concurrent Mode Failure是使用

工作示意图:

Serial Old收集器

Parallel Old收集器

类别:老年代-并行-收集器
策略:标记-清除-整理;
优点:多核环境下,提高CPU利用率;
缺点:单核环境下,比Serial Old效率要低;

Parallel Scavenge的老年代版本,多线程,“标记整理”算法,JDK 1.6才出现。在此之前Parallel Scavenge只能同Serial Old搭配使用,由于Serial Old的性能较差导致Parallel Scavenge的优势发挥不出来,尴了个尬~~

Parallel Old收集器的出现,使“吞吐量优先”收集器终于有了名副其实的组合。在吞吐量和CPU敏感的场合,都可以使用Parallel Scavenge/Parallel Old组合。
组合的工作示意图如下:

Parallel Old收集器

CMS收集器

类别:老年代-并发-收集器
策略:标记-清除;优点:“停顿时间”最短;
缺点:内存碎片(有补偿策略);
适用场景:互联网Web应用的Server端、涉及用户交互、响应速度快;

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,停顿时间短,用户体验就好。
基于“标记清除”算法,并发收集、低停顿,运作过程复杂,分4步:

1)初始标记:仅仅标记GC Roots能直接关联到的对象,速度快,但是需要“Stop The World”
2)并发标记:就是进行追踪引用链的过程,可以和用户线程并发执行。
3)重新标记:修正并发标记阶段因用户线程继续运行而导致标记发生变化的那部分对象的标记记录,比初始标记时间长但远比并发标记时间短,需要“Stop The World”
4)并发清除:清除标记为可以回收对象,可以和用户线程并发执行

由于整个过程耗时最长的并发标记和并发清除都可以和用户线程一起工作,所以总体上来看,CMS收集器的内存回收过程和用户线程是并发执行的。

工作示意图:

CMS收集器

CSM收集器有3个缺点:

对CPU资源非常敏感
并发收集虽然不会暂停用户线程,但因为占用一部分CPU资源,还是会导致应用程序变慢,总吞吐量降低。
CMS的默认收集线程数量是=(CPU数量+3)/4;当CPU数量多于4个,收集线程占用的CPU资源多于25%,对用户程序影响可能较大;不足4个时,影响更大,可能无法接受。

无法处理浮动垃圾
在并发清除时,用户线程新产生的垃圾叫浮动垃圾,可能出现"Concurrent Mode Failure"失败。
并发清除时需要预留一定的内存空间,不能像其他收集器在老年代几乎填满再进行收集;如果CMS预留内存空间无法满足程序需要,就会出现一次"Concurrent Mode Failure"失败;这时JVM启用后备预案:临时启用Serail Old收集器,而导致另一次Full GC的产生;

产生大量内存碎片:
CMS基于"标记-清除"算法,清除后不进行压缩操作产生大量不连续的内存碎片,这样会导致分配大内存对象时,无法找到足够的连续内存,从而需要提前触发另一次Full GC动作。

G1收集器

类别:新生代&老年代-并行&并发-服务端收集器
策略:G1将内存划分为Region,避免内存碎片;
优点:Eden、Survivor、Tenured不再固定,内存使用率更高;可控的STW时间,根据预期的停顿时间,只回收部分Region;
适应场景:多核CPU,JVM占用内存比较大的情况(>4GB);

G1(Garbage-First)是JDK7-u4才正式推出商用的收集器。G1是面向服务端应用的垃圾收集器。它的使命是未来可以替换掉CMS收集器。

G1收集器特性:

并行与并发:能充分利用多CPU、多核环境的硬件优势,缩短停顿时间;能和用户线程并发执行。
分代收集:G1可以不需要其他GC收集器的配合就能独立管理整个堆,采用不同的方式处理新生对象和已经存活一段时间的对象。
空间整合:整体上看采用标记整理算法,局部看采用复制算法(两个Region之间),不会有内存碎片,不会因为大对象找不到足够的连续空间而提前触发GC,这点优于CMS收集器。
可预测的停顿:除了追求低停顿还能建立可以预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不超N毫秒,这点优于CMS收集器。

为什么能做到可预测的停顿?
是因为可以有计划的避免在整个Java堆中进行全区域的垃圾收集。
G1收集器将内存分大小相等的独立区域(Region),新生代和老年代概念保留,但是已经不再物理隔离。
G1跟踪各个Region获得其收集价值大小,在后台维护一个优先列表;
每次根据允许的收集时间,优先回收价值最大的Region(名称Garbage-First的由来);
这就保证了在有限的时间内可以获取尽可能高的收集效率。

对象被其他Region的对象引用了怎么办?

判断对象存活时,是否需要扫描整个Java堆才能保证准确?在其他的分代收集器,也存在这样的问题(而G1更突出):新生代回收的时候不得不扫描老年代?
无论G1还是其他分代收集器,JVM都是使用Remembered Set来避免全局扫描:
每个Region都有一个对应的Remembered Set;
每次Reference类型数据写操作时,都会产生一个Write Barrier 暂时中断操作;
然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的 Region(其他收集器:检查老年代对象是否引用了新生代对象);
如果不同,通过CardTable把相关引用信息记录到引用指向对象的所在Region对应的Remembered Set中;
进行垃圾收集时,在GC根节点的枚举范围加入 Remembered Set ,就可以保证不进行全局扫描,也不会有遗漏。

不计算维护Remembered Set的操作,回收过程可以分为4个步骤(与CMS较为相似):

1)初始标记:仅仅标记GC Roots能直接关联到的对象,并修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时能在正确可用的Region中创建新对象,需要“Stop The World”
2)并发标记:从GC Roots开始进行可达性分析,找出存活对象,耗时长,可与用户线程并发执行
3)最终标记:修正并发标记阶段因用户线程继续运行而导致标记发生变化的那部分对象的标记记录。并发标记时虚拟机将对象变化记录在线程Remember Set Logs里面,最终标记阶段将Remember Set Logs整合到Remember Set中,比初始标记时间长但远比并发标记时间短,需要“Stop The World”
4)筛选回收:首先对各个Region的回收价值和成本进行排序,然后根据用户期望的GC停顿时间来定制回收计划,最后按计划回收一些价值高的Region中垃圾对象。回收时采用复制算法,从一个或多个Region复制存活对象到堆上的另一个空的Region,并且在此过程中压缩和释放内存;可以并发进行,降低停顿时间,并增加吞吐量。

工作示意图:


G1收集器

参考

面试必问之JVM原理


技术讨论 & 疑问建议 & 个人博客

版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议,转载请注明出处!

上一篇下一篇

猜你喜欢

热点阅读