跳槽必问的 JVM 垃圾收集器,看这篇就够了!| JAVA 开发实战

843 阅读8分钟

这是我参与更文挑战的第3天,活动详情查看: 更文挑战

本文正在参加「Java主题月 - Java 开发实战」,详情查看 活动链接

前言

hello,大家好,这里是经典鸡翅。今天我们来聊聊jvm垃圾收集器,垃圾收集器是我们在面试中必然会被问到的。今天选取了我们非常常见的垃圾收集器,给大家进行详细解读。

jvm垃圾收集器

jvm垃圾收集器是jvm内存回收的具体实现。本次讨论hotspot虚拟机中的垃圾收集器。

图中总共有7中垃圾收集器。两个收集器中连线则表示可以配合使用。 中间的横线代表新生代和老年代的分区。上面的是新生代的垃圾收集器,下面是老年代的垃圾收集器。

serial收集器

serial收集器是最早的收集器,是单线程的垃圾收集器,采用复制收集算法。在他进行垃圾收集的时候,只会占用一个cpu或一个线程。垃圾收集时,必须暂停所有的用户线程,直到垃圾收集结束。 serial收集器的运行过程如下:

serial收集器是运行在client模式下的默认新生代收集器。简单而高效。

ParNew收集器

ParNew收集器是Serial收集器的多线程版本。除了多线程外,其他与serial收集器一摸一样。也是一款复制算法的收集器。 ParNew收集器的运行过程如下:

ParNew收集器是server模式下的虚拟机的首选垃圾收集器。ParNew收集器在单CPU环境下,效果没有serial收集器的效率高,还存在线程切换的开销。默认开启的线程数与CPU数量相等。可以使用-XX:ParallelGcThreads参数来限制收集的线程数。

Parallel Scavenge收集器

Parallel Scavenge收集器也采用复制算法,特点是该收集器关注吞吐量。吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值。停顿时间越短就越适合与用户交互的程序。高吞吐量可以高效率的利用CPU时间,尽快完成程序运算。适合在后台运算,不需要太多的交互任务。

Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。

  • MaxGCPauseMillis参教允许的值是一个大于0的毫秒数,收集器将尽可能地保证内存回收花费的时间不超过设定值。 不过大家不要认为如果把这个参数的值设置得很小,就能使
得系统的垃圾收集速度变得更快,GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的。
系统把新生代调小一点,收集300MB新生代肯定比收集500MB快,这也直接导致垃圾
收集发生得更频繁一些,原来10秒收集一次、每次停顿100毫秒,现在变成5秒收集一次、
每次停顿70毫秒。 停顿时间的确在下降,但吞吐量也降下来了。
  • GCTimeRatio参数的值应当是一个大于0且小于100的整数,也就是垃圾收集时间占总
时间的比率,相当于是吞吐量的倒数。如果把此参数设置为19,那允许的最大GC时间就占
总时间的5% (即1/<1+19)>,默认值为99。就是允许最大1%(即1 / (1+99))的垃圾收
集时间。
由于与吞吐量关系密切,Parallel Scavenge收集器也经常称为吞吐量优先收集器。 
除上述两个参数之外,Parallel Scavenge收集器还有一个参数-XX:+UscAdaptiveSizePolicy
值得关注。这是一个开关参数,当这个参数打开之后,就不需要手工指定新生代的大
小-Xmn, Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄
(-XX:pretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整参数。这种调节方式为自适应调节策略。

Serial Old收集器

Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记一
整理”算法。这个收集器的主要意义也是在于给Client模式下的虚拟机使用。如果在Server
模式下,那么它主要还有两大用途:一种用途是在JDK1.5以及之前的版本中与Parallel
Scavenge收集器搭配使用。另一种用途就是作为CMS收集器的后备预案,在并发收集发生
Concurrent Mode Failure时使用。这两点都将在后面的内容中详细讲解。 Serial Old收集器的运行过程如下:

Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记一整理”算
法。这个收集器是在JDK1.6中才开始提供的。 Parallel Old的运行过程如下

Cms收集器

CMS收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器是基于"标记一清除”算法实现的。 运行过程分为四个步骤:

1、初始标记

2、并发标记

3、重新标记

4、并发清除

其中初始标记、重新标记这两个步骤仍是-Stop The World。初始标记只是标记GC Roots能直接关联到的对象,速度很快; 并发标记阶段进行GC Roots tracing的过程。而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。 运行过程如下:

Cms有几个明显的缺点:

1、Cms收集器对cpu资源异常敏感。

2、Cms收集器无法处理浮动垃圾,可能出现Concurrent mode failure失败导致另一次gc的产生。

3、Cms标记清楚会产生内存垃圾。

G1收集器

G1是一款面向服务端应用的垃圾收集器。G1收集器具备如下特点。

1、并行与并发,G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU (CPU或者CPU核心)来缩短Stop The World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。

2、分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够釆用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。

3、空间整合:与CMS的“标记一清理”算法不同,G1从整体来看是基于“标记一整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。

4、可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。 在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划 分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region (不需要连续)的集合。 G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。 G1跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许
的收集时间,优先回收价值最大的Region (这也就是Garbage-First名称的来由)。这种使用
Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以
获取尽可能高的收集效率。 G1收集器的运作过程如下:

1、初始标记

2、并发标记

3、最终标记

4、筛选回收

总结

到此我们的垃圾收集器就介绍完毕了,文中难免有不足,欢迎大家交流指正。