面试官经常问的垃圾收集器,这次我全懂了国内常见的垃圾收集器有哪些,每个垃圾收集器的特点是什么?这也是面试时经常被问到的问题。JVM堆内存概述在说垃圾回收器之前,我们先来看看JVM堆内存的划分。怎么样,看下图,因为虚拟机使用的垃圾回收算法是分代回收算法,所以堆内存分为新生代和老年代。新生代使用的垃圾回收算法是复制算法,所以新生代分为Eden和Survivor;默认空间比为8:2。Survivor分为S0和S1。这两者的空间比为1:1。内存分配和垃圾回收1.对象首先分配在伊甸区。满了之后会触发MinorGC。2.MinorGC之后,将从Eden存活下来的对象移动到S0区。当S0内存满时,会再次触发一次MinorGC,将S0区域存活下来的对象搬走。去S1区,S0区免费;S1满了之后,在MinorGC中,存活下来的再次移动到S0区,S1区空闲,如此重复GC。每进行一次GC,对象的年龄就会增加一年,默认达到15。age之后会进入老年代,进入老年代的年龄阈值可以通过参数-XX:MaxTenuringThreshold设置3.MinorGC后,没有空间存放需要送往的对象老年代,然后Full会触发GC(这一步不是绝对的,取决于垃圾收集器)MinorGC和FullGC的区别:MinorGC是指发生在新生代的垃圾收集行为。由于对象先分配在Eden区,很多对象会born和die,所以触发的频率比较高;由于采用的复制算法,恢复速度一般都很快。FullGC是指发生在老年代的垃圾回收行为。FullGC的速度一般比MinorGC慢10倍以上;因此,不能让JVM频繁发生FullGC。为了更好的适应不同程序的内存情况,JVM也不一定要满15岁才能加入老年代。如果Survivor区所有同龄对象的大小之和大于Survivor区空间的一半,则年龄大于等于这个年龄的对象将直接进入老年代FullCallSystemGC触发条件代码中的.gc()老年代空间不足/满持久区空间不足/满注意:大对象会直接在老年代分配内存,对象的大小可以由参数-XX:PretenureSizeThreshold控制,通常遇到的大对象都是很长的字符串或者数组。如果一大群大对象被分配临时使用,并且它们的生命周期很短,FullGC会频繁发生,但此时新生代中的空间仍然是空闲的;在编写代码时,应该避免这种情况,尤其是在创建数组时,要注意空间保证。当新生代发生MinorGC时,JVM会先检查老年代可分配的连续空间是否大于新生代所有对象的总和,如果更大,则MinorGC可以这次安全执行;如果不大于,那么JVM会先检查参数HandlePromotionFailure的设置值是否允许空间保证失败,如果允许,JVM会继续检查老年代可以分配的连续空间是否大于平均大小的对象提升到老年代?如果较大,JVM将尝试MinorGC,即使此MinorGC有风险;失败,会需要一段时间去执行GC,但是建议开启这个参数,避免JVMFullGC频繁垃圾收集器概述从上图可以看出:新生代可以使用的垃圾收集器:Serial,ParNew,ParallelScavenge老年代适用的垃圾收集器:CMS,SerialOld,ParallelOldG1收集器分别适用于新生代和老年代。它们之间存在联系,这意味着它们可以一起使用。CMS和SerialOld都是老年代收集器。为什么?彼此之间会有联系吗?串行收集器这是一种单线程收集器,发展历史最悠久的收集器,当它在进行垃圾收集时,其他线程必须挂起,直到垃圾收集结束(StopTheWorld)Serial收集器虽然存在StopTheWorld的问题,但在并行能力较弱的单CPU环境下,往往表现优于其他收集器;因为简单高效,没有多余的线程交互开销;Serial对于运行Client模式的虚拟机来说是一个不错的选择。使用-XX:+UseSerialGC参数设置新一代使用这个串行收集器。ParNew收集器。ParNew收集器是Serial收集器的多线程版本;除了使用多线程进行垃圾回收外,其他都和Serial一致;它默认启动的线程数与CPU的核心数相同,线程数可以通过参数-XX:ParallelGCThreads设置。从上图可以看出,除了Serial之外,只有ParNew是唯一可以与CMS结合使用的收集器,所以ParNew通常是运行在Server模式下的首选新生代垃圾收集器。使用-XX:+UseParNewGC参数可以设置新生代使用这个并行收集器ParallelScavenge收集器ParallelScavenge收集器仍然是一个多线程的新生代收集器,它使用的是复制算法。它和其他收集器的区别在于它主要关心吞吐量,而其他收集器则侧重于尽可能减少用户线程的等待时间(缩短StopTheWorld时间)。吞吐量=用户线程执行时间/(用户线程执行时间+垃圾回收时间),虚拟机总共运行100分钟,垃圾回收耗时1分钟,则吞吐量为99%。停顿时间越短,越适合与用户互动一个好的节目,一个好的响应可以提升用户体验。高效的吞吐量可以充分利用CPU时间,尽快完成计算任务,因此ParallelScavenge收集器适用于后台计算任务程序。-XX:MaxGCPauseMillis可以控制垃圾回收的最大暂停时间,需要注意的是不要以为把这个时间设置小了就可以减少垃圾回收的临时时间,这样可能会导致频繁的GC,反而降低了吞吐量-XX:GCTimeRatio设置吞吐量大小,参数为整数,取值范围0-100,即垃圾回收所花费的时间,默认为99,则垃圾回收所花费的最大时间为1%-XX:+UseAdaptiveSizePolicy如果启用该参数,则不需要用户手动控制新生代的大小,提升老年代的年龄等参数,JVM会启用GC自适应调整策略SerialOldcollector的SerialOld收集器也是单线程收集器,适用于老年代,使用flag-排序算法,可以配合Serial收集器在Client模式下使用。它可以用作CMS收集器的备份计划。如果CMS出现并发模式故障,SerialOld将充当备份收集器。(后面会详细介绍CMS)ParallelOld收集器ParallelOld收集器可以和ParallelScavenge收集器一起使用,实现“吞吐量优先”。主要针对老年代收集器,采用标记-排序算法。在注重吞吐量的任务中,可以优先使用-XX:+UseParallelOldGc这个组合来设置老年代使用这个收集器。XX:+ParallelGCThreads设置垃圾回收的线程数。CMS收集器CMS收集器是一种旨在获得最短恢复停顿时间的收集器。互联网网站和B/S架构中常用的采集器是CMS,因为系统停顿时间最短,给用户带来更好的体验。-XX:+UseConcMarkSweepGC设置老年代使用这个收集器。-XX:ConcGCThreads设置并发线程数。CMS采用mark-sweep算法,主要分为4个步骤:Initializemark,concurrentlymark,re-mark,concurrentlyclear,初始化mark和remark这两个步骤还是会发生StopTheWorld,初始mark只标记GCRoot可以直接关联对象更快,并发标记可以和用户线程并发执行;重标记是为了纠正用户线程在并发标记过程中产生的垃圾。这个时间比初始标记稍长,比并发标记短得多。整个过程请看下图的优势。CMS是一个优秀的收藏家。它的主要优点是:并发收集和低停顿,所以CMS收集器又被称为并发低停顿收集器(ConcurrentLowPauseCollector)。缺点CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但会因为占用一部分线程(或CPU资源)而拖慢应用程序,降低总吞吐量。CMS默认启动的回收线程数为(CPU数+3)/4,即当CPU数大于4时,并发回收时垃圾回收线程占用CPU资源不少于25%,且垃圾收集线程的数量随着CPU数量的增加而增加。衰退。但是当CPU少于4个时(比如2个),CMS对用户程序的影响可能会变得非常大。如果CPU负载比较大,则必须分配一半的计算能力来执行收集器线程。结果,用户程序的执行速度一下子降低了50%,这其实是不能接受的。无法处理漂浮垃圾。由于在CMS的并发清理阶段用户线程还在运行,所以随着程序的运行自然会不断产生新的垃圾。这部分垃圾出现在marking过程之后,CMS无法在本次collection中处理掉,只能在下一次GC中清理掉。这部分垃圾被称为“漂浮垃圾”。也是因为在垃圾回收阶段用户线程还需要运行,所以需要预留足够的内存空间给用户线程使用,所以CMS收集器不能像其他收集器一样等到老年代几乎完全被填满在继续之前。收集和回收阈值可以通过参数-XX:CMSInitiatingoccupancyFraction设置;如果回收阈值设置过大,分配的大对象在CMS运行过程中找不到足够的空间,就会出现“ConcurrentModeFailure”故障,这意味着有时会临时启动SerialOldGC重新收集旧的一代,所以停顿时间会更长。mark-sweep算法引起的空间碎片CMS是基于“mark-sweep”算法的收集器,这意味着在收集结束时会产生大量的空间碎片。当空间碎片过多时,会给大对象的分配带来很大的麻烦。老年代往往会有剩余空间,但不可能找到足够大的连续空间来分配当前对象。为了解决这个问题,CMS提供了一个参数-XX:+UseCMSCompactAtFullCollecion。如果启用,内存碎片整理和整合过程将在FullGC期间启动。由于内存整理的过程不能并行执行,暂停的时间会比较长。考虑到每次FullGC都进行内存碎片整理不是很合适,CMS提供了另一个参数-XX:CMSFullGCsBeforeCompaction来控制进行多少次不进行碎片整理的FullGC,然后一个碎片整理GCG1收集器G1为server-side的垃圾收集器应用程序。ParallelismandConcurrency:与CMS类似,充分利用多核CPU,G1仍然可以在不挂起用户线程的情况下进行垃圾回收。分代收集:分代代的概念在G1中还是保留的,当时不需要和其他垃圾收集器配合使用,可以独立管理整个堆内存空间的整合:G1使用mark-sorting算法作为一个整体,并且使用了从本地(Region)复制的算法,这两者都意味着G1不需要进行内存碎片整理。PredictablePause:允许用户指定垃圾收集时间在一个时间段内不超过多长时间。虽然Region在G1中仍然保留了新生代和老年代的概念,但是它使用了完全不同的方式来组织堆内存。它将整个堆内存划分为很多大小相同的区域(Regions),而新生代和老年代在物理上并不是连续的内存区域,请看下图:每个Region用E,S,O来标记而H,其中H在前面的算法中是没有的,它代表Humongous,意思是存储这些RegionsHuge对象。当一个新创建的对象的大小超过一个Region的一半时,直接分配到一个或多个新的连续Region中,标记为H。Region区的内存大小可以通过-XX:G1HeapRegionSize参数指定,大小范围只能是2的幂,如:1M,2M,4M,8MG1GC模式新生代GC:与其他新生代类似generationcollectors,objects在伊甸园区域优先分配。如果eden区域内存不足,就会触发新生代的GC,将存活的对象放入survivor区域,或者提升到old区域。HybridGC:当越来越多的对象被提升到oldregion,当oldage的内存使用量达到一定阈值时,就会触发hybridGC。可以通过参数-XX:InitiatingHeapOccupancyPercent设置阈值百分比,类似于CMS中-XX:CMSInitiatingoccupancyFraction的功能;hybridGC会回收新生代和部分老年代内存,注意是老年代的一部分,而不是全部老年代;G1会跟踪每个Region的垃圾回收值,在用户指定的垃圾回收时间内,优先选择值最大的region。FullGC:如果对象内存分配速度太快,mixedGC还没有完成回收,导致老年代被填满,就会触发fullgc。G1的fullgc算法是单线程执行的串行oldgc。这个过程类似于CMS,会导致停顿时间异常长。可能避免fullgc。
