java堆主要分为新生代和老年代两部分,新生代主要使用复制或者标记清除垃圾回收算法,老年代使用标记整理回收算法。java虚拟机提供了不同的收集器。垃圾收集的目标范围整个新生代(Minor GC)或者整个老年代(Major GC)或者整个Java堆(Full GC)。下图展示了七种作用于不同分代的收集器,如果两个收集器存在关联可以搭配使用。
单线程工作的垃圾收集器,采用复制算法,Jdk1.3之前唯一的新生代垃圾收集器。单线程并不是指只有一个线程,是指当垃圾回收时必须暂停其他所以的工作线程直到收集结束。优点就是简单高效,适合单线程环境,没有线程交互开销。Serial和Serial Old搭配回收垃圾过程:
ParNew收集器实际上是Serial收集器的多线程并行版本,使用复制算法。功能和Serial一样,垃圾回收的时候需要暂停其他的线程。ParNew默认开启和CPU相同的线程数。ParNew和Serial Old收集器搭配回收垃圾过程:
Parallel Scavenge 收集器是新生代的垃圾收集器,采用的是复制算法。也是能够并行收集的多线程收集器。特点就是达到一个可控制的吞吐量,吞吐量就是cpu用于运行用户代码的时间与CPU消耗时间的比值,吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),比如虚拟机总共运行了100分钟,垃圾回收1一分钟,吞吐量为99%。停顿时间越短越适合与用户交互的程序,高吞吐量可以高效的利用CPU,尽快的完成程序的运算任务,主要适合后台运算而不需要太多交互的任务。
Serial Old是Serial收集器的老年代版本,也是单线程收集器,需要暂停所有线程。使用标记整理算法。Serial和Serial Old搭配回收垃圾过程:
Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,使用标记整理算法。系统对吞吐量要求比较高的时候,jdk1.6之后可以使用Parallel Scavenge和Parallel Old搭配使用。Parallel Scavenge和Parallel Old搭配垃圾回收过程:
CMS垃圾收集器主要是获取最短垃圾回收停顿时间,使用多线程标记清除算法。CMS工作机制比其他的老年代收集器比较复杂,4个阶段:
(1)初始标记:标记GC roots能直接关联的对象,速度很快,需要Stop The World。
(2)并发标记:进行GC roots跟踪的过程,和用户线程一起工作,不暂停工作线程。
(3)重新标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的一部分对象的标记记录,需要Stop The World。
(4)并发清除:清除GC roots不可达对象,和用户线程一起工作,不需要暂停线程。
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部分参数设置:
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不频繁,存活的年轻代对象不会都进入老年代。
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的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。后台维护一个优先级列表,每次根据所允许的回收时间,优先回收垃圾最多的区域,这样释放的空间多。
youngGC:Eden区域满了不会直接触发youngGC,先计算回收时间,小于设置的G1停顿时间,增加Region给Eden区,增加的超过60%堆内存或者达到停顿时间在触发youngGC。
mixedGC:老年代的GC。内存使用率达到-XX:InitiatingHeapOccupancyPercent配置的值触发mixedGC,回收所有的young区和部分old区和大对象。使用复制算法。存活的对象保存到别的region内,别的region存储不下活的对象触发fullgc。
fullGC:停止应用程序,使用单线程进行标记、清理和压缩整理。空闲出一批Region给下一次mixedGC使用。
G1收集器部分参数设置 :