JVM系列5——垃圾收集器

        java堆主要分为新生代和老年代两部分,新生代主要使用复制或者标记清除垃圾回收算法,老年代使用标记整理回收算法。java虚拟机提供了不同的收集器。垃圾收集的目标范围整个新生代(Minor GC)或者整个老年代(Major GC)或者整个Java堆(Full GC)。下图展示了七种作用于不同分代的收集器,如果两个收集器存在关联可以搭配使用。 

JVM系列5——垃圾收集器_第1张图片一、Serial收集器

        单线程工作的垃圾收集器,采用复制算法,Jdk1.3之前唯一的新生代垃圾收集器。单线程并不是指只有一个线程,是指当垃圾回收时必须暂停其他所以的工作线程直到收集结束。优点就是简单高效,适合单线程环境,没有线程交互开销。Serial和Serial Old搭配回收垃圾过程:

JVM系列5——垃圾收集器_第2张图片

二、ParNew收集器

        ParNew收集器实际上是Serial收集器的多线程并行版本,使用复制算法。功能和Serial一样,垃圾回收的时候需要暂停其他的线程。ParNew默认开启和CPU相同的线程数。ParNew和Serial Old收集器搭配回收垃圾过程:

JVM系列5——垃圾收集器_第3张图片

三、Parallel Scavenge 收集器

        Parallel Scavenge 收集器是新生代的垃圾收集器,采用的是复制算法。也是能够并行收集的多线程收集器。特点就是达到一个可控制的吞吐量,吞吐量就是cpu用于运行用户代码的时间与CPU消耗时间的比值,吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),比如虚拟机总共运行了100分钟,垃圾回收1一分钟,吞吐量为99%。停顿时间越短越适合与用户交互的程序,高吞吐量可以高效的利用CPU,尽快的完成程序的运算任务,主要适合后台运算而不需要太多交互的任务。

四、Serial Old垃圾收集器

        Serial Old是Serial收集器的老年代版本,也是单线程收集器,需要暂停所有线程。使用标记整理算法。Serial和Serial Old搭配回收垃圾过程:

JVM系列5——垃圾收集器_第4张图片

五、Parallel Old 垃圾收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,使用标记整理算法。系统对吞吐量要求比较高的时候,jdk1.6之后可以使用Parallel Scavenge和Parallel Old搭配使用。Parallel Scavenge和Parallel Old搭配垃圾回收过程:

JVM系列5——垃圾收集器_第5张图片六、CMS垃圾收集器

CMS垃圾收集器主要是获取最短垃圾回收停顿时间,使用多线程标记清除算法。CMS工作机制比其他的老年代收集器比较复杂,4个阶段:

(1)初始标记:标记GC roots能直接关联的对象,速度很快,需要Stop The World。

(2)并发标记:进行GC roots跟踪的过程,和用户线程一起工作,不暂停工作线程。

(3)重新标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的一部分对象的标记记录,需要Stop The World。

(4)并发清除:清除GC roots不可达对象,和用户线程一起工作,不需要暂停线程。

JVM系列5——垃圾收集器_第6张图片        CMS垃圾收集器优点就是并发收集,低停顿。缺点就是在并发阶段,占用了一部分线程而导致了程序变慢,降低了总的吞吐量。

        CMS收集器无法处理“浮动垃圾”,有可能出现“Con-current Mode Failure”失败导致另一次Stop The World的Full GC产生,在CMS的并非标记和并发清除阶段,用户线程还在继续运行,会产生新的垃圾,这部分没有被标记过,CMS无法在当前这次的收集中处理掉它们,只能留到下一次垃圾收集清理掉。这一部分就是浮动垃圾。新的对象进入老年代,老年代没有空间了,会导致并发清理concurrent mode failure(并发失败),会使用串行收集器(单线程进行垃圾回收的回收器。老年代的串行回收器使用标记压缩算法,串行独占式的进行垃圾回收,经常会有较长时间的stw)回收老年代的垃圾,导致停顿时间长。CMSInitiatingOccupancyFractio要设置一个合理的值,大了会导致concurrent mode failure频繁,小了会导致cmsgc频繁。CMS通过参数-XX:CMSInitiatingOccu-pancyFraction配置达到多少空间开始垃圾回收。JDK 6默认是92。还有一个缺点是CMS基于标记清除算法实现的收集器,会产生大量的空间碎片,碎片过多,大对象来了没有连续空间分配,就要提前触发Full GC。为了解决这个问题CMS垃圾收集器提了参数-XX:+UseCMS-CompactAtFullCollection开关参数(默认开启)用来在CMS进行Full GC时开启内存碎片整理过程,由于要移动活着的对象,所以没有办法并发处理,会增加停顿时间。还有一个参数数-XX:CMSFullGCsBefore-Compaction是在多少次不整理空间的Full GC之后下一次会先进行碎片整理(默认值0)。

CMS部分参数设置:

  • -XX:+UseConcMarkSweepGC 启用cms垃圾收集器
  • -XX:ConcGCThreads:并发的GC线程数
  • -XX:+UseCMSCompactAtFullCollection 开关参数,默认开启(1.9废弃)。FullGC之后做压缩整理(减少碎片)
  • -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0(JDK 9废弃),代表每次FullGC后都会压缩一 次
  • -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(jdk1.6之后默认是92%,这是百分比)
  • -XX:+CMSParallelRemarkEnabled 重新标记阶段多线程执行,减少STW时间。
  • -XX:+CMSParallellnitialMarkEnabled 初始标记阶段多线程执行,减少STW时间。
  • -XX:+CMSScavengeBeforeRemark 在CMSGC之前启动一次minor gc,减少老年代对年轻代的饮用,降低CMS GC标记的时间,标记阶段占用GC耗时80%的时间。
  • -XX:+UseCMSInitiatingOccupancyOnly 只使用设置的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值)。不设置,第一次使用设定值,后续自动调整。

七、Garbage First收集器

        Garbage First(G1)垃圾收集器把java堆划分成多个大小相等的独立区域(Region),最多2048个Region。1个region的大小就是堆大小除2048,一般是2M(手动修改)。每一个Region都可以根据需求,扮演新生代的Eden区、Survivor区,或者老年代。垃圾收集器对扮演不同角色的Region采用不同的策略去处理。默认新生代初始大小为堆的5%,执行过程中可以给新生代增加更多的region,最多不超过堆的60%。新生代比例还保留8:1:1。region不完全属于新生代老年代,gc回收之后空余的会重新分配。

        G1新增加了H(Humongous)区(相当于老年代的一部分),用来存储大对象。超过1个region的50%就属于大对象,进入H区。大对象过大可以跨越使用多个Region存储。H可以存储短期大对象,不用直接进入老年代,节约了老年代空间,避免老年代内存不够需要GC。每个Region的大小可以通过参数-XX:G1HeapRegionSize设 定,取值范围为1MB~32MB,且应为2的N次幂。

        G1垃圾收集器适合50%以上的堆被存活对象占用 、对象分配和晋升的速度变化非常大、垃圾回收时间特别长超过1秒、8GB以上的堆内存(建议值) 、停顿时间是500ms以内等场景。优化方案主要从-XX:MaxGCPauseMills参数的大小入手。保证gc不频繁,存活的年轻代对象不会都进入老年代。 

JVM系列5——垃圾收集器_第7张图片fullgc包含了新生代、老年代、H区。GC步骤包含4个阶段:

(1)初始标记(Initial Marking):标记一下GC Roots能直接关联到的对象,并且修改TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要stop the world,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。

(2)并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆 里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。

(3)最终标记(Final Marking):需要 stop the world,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。

(4)筛选回收(Live Data Counting and Evacuation):更新Region的统计数据,对每一个region的价值和回收成本进行排序,根据配置的停顿时间(默认两百毫秒,)制定回收计划,可以自由选择任意多个Region 构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。后台维护一个优先级列表,每次根据所允许的回收时间,优先回收垃圾最多的区域,这样释放的空间多。

JVM系列5——垃圾收集器_第8张图片G1特点:

  1. 采用标记--整理算法,不会产生内存碎片。
  2. 可以精确控制停顿时间,不牺牲吞吐量的情况下,能达到最小停顿时间。
  3. 分代收集,G1一个垃圾收集器管理整个堆,保留分代概念。

youngGC:Eden区域满了不会直接触发youngGC,先计算回收时间,小于设置的G1停顿时间,增加Region给Eden区,增加的超过60%堆内存或者达到停顿时间在触发youngGC。

mixedGC:老年代的GC。内存使用率达到-XX:InitiatingHeapOccupancyPercent配置的值触发mixedGC,回收所有的young区和部分old区和大对象。使用复制算法。存活的对象保存到别的region内,别的region存储不下活的对象触发fullgc。

fullGC:停止应用程序,使用单线程进行标记、清理和压缩整理。空闲出一批Region给下一次mixedGC使用。

G1收集器部分参数设置 :

  • -XX:+UseG1GC:使用G1收集器
  • -XX:ParallelGCThreads:指定GC工作的线程数量
  • -XX:G1HeapRegionSize:指定分区大小(1MB~32MB,且必须是2的N次幂),默认将整堆划分为2048个分区
  • -XX:MaxGCPauseMillis:目标暂停时间(默认200ms)
  • -XX:G1NewSizePercent:新生代内存初始空间(默认整堆5%)
  • -XX:G1MaxNewSizePercent:新生代内存最大空间(默认整堆60%)
  • -XX:TargetSurvivorRatio:Survivor区的填充容量(默认50%),Survivor区域里的一批对象(年龄1+年龄2+年龄n的多个年龄对象)总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代
  • -XX:MaxTenuringThreshold:最大年龄阈值(默认15)
  • -XX:InitiatingHeapOccupancyPercent:老年代占用空间达到整堆内存阈值(默认45%),则执行新生代和老年代的混合收集(MixedGC),比如我们之前说的堆默认有2048个region,如果有接近1000个region都是老年代的region可能就要触发MixedGC了
  • -XX:G1MixedGCLiveThresholdPercent(默认85%) region中的存活对象低于这个值时才会回收该region,如果超过这 个值存活对象过多回收无意义。
  • -XX:G1MixedGCCountTarget:在一次回收过程中指定做几次筛选回收(默认8次),在最后一个筛选回收阶段可以回收一段时间,然后暂停回收,恢复系统运行,之后再开始回收,避免单次停顿时间过长。
  • -XX:G1HeapWastePercent(默认5%): gc过程中空出来的region是否充足阈值,在混合回收的时候,对Region回收都是基于复制算法进行的,都是把要回收的Region里的存活对象放入其他Region,然后这个Region中的垃圾对象全部清理掉,这样的话在回收过程就会不断空出来新的Region,一旦空闲出来的Region数量达到了堆内存的5%,此时就会立即停止混合回收。

你可能感兴趣的:(jvm,java,算法)