本文已被Github仓库收录 https://github.com/silently9527/JavaCore

前言

前几天写了一篇《JVM性能调优实战:让你的IntelliJ Idea纵享丝滑》,其中有对GC垃圾回收器的抉择尝试,本篇咱们就来具体的看看JVM中常见的垃圾回收器有哪些以及每个垃圾回收器的特点,这也是面试的时候常常被问的内容

JVM堆内存概览

在聊垃圾回收器之前,咱们先来看看JVM堆内存的区域划分是怎么样的,看下图

  • 因为虚拟机应用的垃圾回收算法是分代收集算法,所以堆内存被分为了新生代和老年代
  • 新生代应用的垃圾回收算法是复制算法,所以新生代又被分为了 Eden 和Survivor;空间大小比例默认为8:2
  • Survivor又被分为了S0、S1,这两个的空间大小比例为1:1

内存调配以及垃圾回收

  1. 对象优先在Eden区进行调配,如果Eden区满了之后会触发一次Minor GC
  2. Minor GC之后从Eden存活下来的对象将会被挪动到S0区域,当S0内存满了之后又会被触发一次Minor GC,S0区存活下来的对象会被挪动到S1区,S0区闲暇;S1满了之后在Minor GC,存活下来的再次挪动到S0区,S1区闲暇,这样反反复复GC,每GC一次,对象的年龄就涨一岁,默认达到15岁之后就会进入老年代,对于晋身到老年代的年龄阈值能够通过参数 -XX:MaxTenuringThreshold设置
  3. 在Minor GC之后须要的发送晋身到老年代的对象没有空间安置,那么就会触发Full GC (这步非相对,视垃圾回收器决定)
Minor GC和Full GC的区别:Minor GC是指产生在新生代的垃圾收集行为,因为对象优先在Eden区调配,并且很多对象都是朝生夕死,所以触发的频率绝对较高;因为采纳的复制算法,所以个别回收速度十分快。Full GC是指产生在老年代的垃圾收集行为,Full GC的速度个别会比Minor GC慢10倍以上;所以不能让JVM频繁的产生Full GC

为了可能更好的适应不同程序的内存状况,JVM也不肯定要求必须达到年龄15岁能力晋身到老年代,如果在Survivor区中雷同年龄的所有对象大小总和大于Survivor区空间的一半,年龄大于或者等于这个年龄的对象将会间接进入到老年代

Full GC触发条件

  • 代码中调用System.gc()
  • 老年代空间有余/满了
  • 长久区空间有余/满了
留神:大对象会间接在老年代分配内存,能够通过参数-XX:PretenureSizeThreshold管制对象的大小,通常遇到的大对象是很长的字符串或者数组,如果调配了一大群大对象只是长期应用,生命很短暂,那么就会频繁的产生Full GC,然而此时的新生代的空间还有闲暇;写代码的时候,这种状况应该防止,特地是在创立数组的时候要当心

空间担保

在新生代产生Minor GC的时候,JVM会先查看老年代中可调配的间断空间是否大于新生代所有对象的总和,如果大于,那么本次Minor GC就能够平安的执行;如果不大于,那么JVM会先去查看参数HandlePromotionFailure设置值是否容许空间担保失败,如果容许,JVM会持续查看老年代可调配的间断空间是否大于历次降职到老年代对象的均匀大小,如果大于,只管这次Minor GC是有危险的,JVM也会尝试一次Minor GC;如果不容许担保失败,那么JVM间接进行Full GC

尽管担保有可能会失败,导致饶一圈能力进行GC,然而还是倡议把这个参数关上,能够防止JVM频繁的Full GC

垃圾回收器概览

从上图能够看出:

  • 新生代能够应用的垃圾回收器:Serial、ParNew、Parallel Scavenge
  • 老年代能够实用的垃圾回收器:CMS、Serial Old、Parallel Old
  • G1回收器实用于新生代和老年代
  • 相互之间有连线的示意能够配合应用
CMS和Serial Old同为老年代回收器,为何互相会有连线呢?

Serial收集器

这是个单线程收集器,倒退历史最悠久的收集器,当它在进行垃圾收集工作的时候,其余线程都必须暂停直到垃圾收集完结(Stop The World)。

尽管Serial收集器存在Stop The World的问题,然而在并行能力较弱的单CPU环境下往往体现优于其余收集器;因为它简略而高效,没有多余的线程交互开销;Serial对于运行在Client模式下的虚拟机来说是个很好的抉择

应用-XX:+UseSerialGC参数能够设置新生代应用这个Serial收集器

ParNew收集器

ParNew收集器是Serial收集器的多线程版本;除了应用了多线程进行垃圾收集以外,其余的都和Serial统一;它默认开始的线程数与CPU的核数雷同,能够通过参数-XX:ParallelGCThreads来设置线程数。

从下面的图能够看出,可能与CMS配合应用的收集器,除了Serial以外,就只剩下ParNew,所以ParNew通常是运行在Server模式下的首选新生代垃圾收集器

应用-XX:+UseParNewGC参数能够设置新生代应用这个并行回收器

Parallel Scavenge收集器

Parallel Scavenge收集器仍然是个采纳复制算法的多线程新生代收集器,它与其余的收集器的不同之处在于它次要关怀的是吞吐量,而其余的收集器关注的是尽可能的缩小用户线程的等待时间(缩短Stop The World的工夫)。吞吐量=用户线程执行工夫/(用户线程执行工夫+垃圾收集工夫),虚拟机总共运行100分钟,其中垃圾收集破费工夫1分钟,那么吞吐量就是 99%

进展工夫越短适宜须要和用户进行交互的程序,良好的响应可能晋升用户的体验。而高效的吞吐量能够充沛的利用CPU工夫,尽快的实现计算工作,所以Parallel Scavenge收集器实用于后盾计算型工作程序。

-XX:MaxGCPauseMillis能够管制垃圾收集的最大暂停工夫,须要留神不要认为把这个工夫设置的很小就能够缩小垃圾收集暂用的工夫,这可能会导致产生频繁的GC,反而升高了吞吐量

-XX:GCTimeRatio设置吞吐量大小,参数是取值范畴0-100的整数,也就是垃圾收集占用的工夫,默认是99,那么垃圾收集占用的最大工夫 1%

-XX:+UseAdaptiveSizePolicy 如果关上这个参数,就不须要用户手动的管制新生代大小,降职老年代年龄等参数,JVM会开启GC自适应调节策略

Serial Old收集器

Serial Old收集器也是个单线程收集器,实用于老年代,应用的是标记-整顿算法,能够配合Serial收集器在Client模式下应用。

它能够作为CMS收集器的后备预案,如果CMS呈现Concurrent Mode Failure,则SerialOld将作为后备收集器。(前面CMS具体阐明)

Parallel Old收集器

Parallel Old收集器能够配合Parallel Scavenge收集器一起应用达到“吞吐量优先”,它次要是针对老年代的收集器,应用的是标记-整顿算法。在重视吞吐量的工作中能够优先思考应用这个组合

-XX:+UseParallelOldGc设置老年代应用该回收器。

XX:+ParallelGCThreads设置垃圾收集时的线程数量。

CMS收集器

CMS收集器是一种以获取最短回收进展工夫为指标的收集器,在互联网网站、B/S架构的中罕用的收集器就是CMS,因为零碎进展的工夫最短,给用户带来较好的体验。

-XX:+UseConcMarkSweepGC设置老年代应用该回收器。

-XX:ConcGCThreads设置并发线程数量。

CMS采纳的是标记-革除算法,次要分为了4个步骤:

  • 初始化标记
  • 并发标记
  • 从新标记
  • 并发革除

初始化标记和从新标记这两个步骤仍然会产生Stop The World,初始化标记只是标记GC Root可能间接关联到的对象,速度较快,并发标记可能和用户线程并发执行;从新标记是为了修改在并发标记的过程中用户线程产生的垃圾,这个工夫比初始化标记稍长,比并发标记短很多。整个过程请看下图

长处

  • CMS是一款优良的收集器,它的次要长处:并发收集、低进展,因而CMS收集器也被称为并发低进展收集器(Concurrent Low Pause Collector)。

毛病

  • CMS收集器对CPU资源十分敏感。 在并发阶段,它尽管不会导致用户线程进展,但会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会升高。CMS默认启动的回收线程数是(CPU数量+3)/4,也就是当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,并且随着CPU数量的减少而降落。然而当CPU有余4个时(比方2个),CMS对用户程序的影响就可能变得很大,如果原本CPU负载就比拟大,还要分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度突然升高了50%,其实也让人无奈承受。
  • 无奈解决浮动垃圾。 因为CMS并发清理阶段用户线程还在运行着,随同程序运行天然就还会有新的垃圾一直产生。这一部分垃圾呈现在标记过程之后,CMS无奈再当次收集中解决掉它们,只好留待下一次GC时再清理掉。这一部分垃圾就被称为“浮动垃圾”。也是因为在垃圾收集阶段用户线程还须要运行,那也就还须要预留有足够的内存空间给用户线程应用,因而CMS收集器不能像其余收集器那样等到老年代简直齐全被填满了再进行收集,回收阀值能够通过参数-XX:CMSInitiatingoccupancyFraction来设置;如果回收阀值设置的太大,在CMS运行期间如果调配大的对象找不到足够的空间就会呈现“Concurrent Mode Failure”失败,这时候会长期启动SerialOld GC来从新进行老年代的收集,这样的话进展的工夫就会加长。
  • 标记-革除算法导致的空间碎片 CMS是一款基于“标记-革除”算法实现的收集器,这意味着收集完结时会有大量空间碎片产生。空间碎片过多时,将会给大对象调配带来很大麻烦,往往呈现老年代空间残余,但无奈找到足够大间断空间来调配以后对象。为了解决这个问题CMS提供了一个参数-XX:+UseCMSCompactAtFullCollecion,如果启用,在Full GC的时候开启内存碎片整顿合并过程,因为内存碎片整顿的过程无奈并行执行,所以进展的工夫会加长。思考到每次FullGC都要进行内存碎片合并不是很适合,所以CMS又提供了另一个参数-XX:CMSFullGCsBeforeCompaction来管制执行多少次不带碎片整顿的FullGC之后,来一次带碎片整顿GC

G1收集器

G1是一款面向服务端利用的垃圾回收器。

  • 并行与并发:与CMS相似,充沛里用多核CPU的劣势,G1依然能够不暂停用户线程执行垃圾收集工作
  • 分代收集:分代的概念仍然在G1保留,过后它不须要和其余垃圾收集器配合应用,能够独立治理整个堆内存
  • 空间的整合:G1整体上采纳的是标记-整顿算法,从部分(Region)采纳的是复制算法,这两种算法都意味着G1不须要进行内存碎片整顿
  • 可预测的进展:可能让用户指定在工夫片段内,耗费在垃圾收集的工夫不超过多长时间。

Region

尽管在G1中仍然保留了新生代和老年代的概念,然而采纳的是一种齐全不同的形式来组织堆内存,它把整个堆内存宰割成了很多大小雷同的区域(Region),并且新生代和老年代在物理上也不是间断的内存区域,请看下图:

每个Region被标记了E、S、O和H,其中H是以往算法中没有的,它代表Humongous,这示意这些Region存储的是巨型对象,当新建对象大小超过Region大小一半时,间接在新的一个或多个间断Region中调配,并标记为H。Region区域的内存大小能够通过-XX:G1HeapRegionSize参数指定,大小区间只能是2的幂次方,如:1M、2M、4M、8M

G1的GC模式

  • 新生代GC:与其余新生代收集器相似,对象优先在eden region调配,如果eden region内存不足就会触发新生代的GC,把存活的对象安置在survivor region,或者降职到old region
  • 混合GC:当越来越多的对象降职到了old region,当老年代的内存使用率达到某个阈值就会触发混合GC,能够通过参数-XX:InitiatingHeapOccupancyPercent设置阈值百分比,此参数与CMS中-XX:CMSInitiatingoccupancyFraction的性能相似;混合GC会回收新生代和局部老年代内存,留神是局部老年代而不是全副老年代;G1会跟踪每个Region中的垃圾回收价值,在用户指定的垃圾收集工夫内优先回收价值最大的region
  • Full GC:如果对象内存调配速度过快,混合GC还未回收实现,导致老年代被填满,就会触发一次full gc,G1的full gc算法就是单线程执行的serial old gc,此过程与CMS相似,会导致异样长时间的暂停工夫,尽可能的防止full gc.
    • -

写到最初(点关注,不迷路)

文中或者会存在或多或少的有余、谬误之处,有倡议或者意见也十分欢送大家在评论交换。

最初,请敌人们不要白嫖我哟,心愿敌人们能够点赞评论关注三连,因为这些就是我分享的全副能源起源????

    • -
我曾经从零开始手写了简易版springmvc,以及编写了具体的阐明文档,心愿可能帮忙搭档们深刻了解springmvc外围原理,有须要的敌人欢送关注公众号:贝塔学JAVA ,回复源码即可