CMS是并行标记回收器,使用标记-清除算法进行收集。适用于对时延要求较高的在线服务,不接受长时间停顿的那种。但是如果服务运行较长时间,会造成严重的内存碎片。
CMS收集周期:
STW
initialMarking阶段:该阶段单线程执行,主要做两件事情:
Remarking并发重新标记阶段,因为之前的标记可能重新产生引用关系:
CMS算法中提供了一个参数:CMSScavengeBeforeRemark,默认并没有开启,如果开启该参数,在执行该阶段之前,会强制触发一次YGC,可以减少新生代对象的遍历时间,回收的也更彻底一点。
不过,这种参数有利有弊,利是降低了Remark阶段的停顿时间,弊的是在新生代对象很少的情况下也多了一次YGC,最可怜的是在AbortablePreclean阶段已经发生了一次YGC,然后在该阶段又傻傻的触发一次。
CMS对于Old GC会后台线程轮询(2s钟)判断是否需要触发。一般有以下几种触发条件:
CMS的堆内存结构如下:
同时要求这些空间必须是地址连续的。
JVM所采用的Old区垃圾收集器为CMS,CMS会在以下几种情况下发生Full GC:
为了解决CMS算法产生内存碎片的一系列问题,HotSpot提供了G1算法。G1适用于那些多CPU大内存的服务,为满足高吞吐需求的同时可以满足缩短GC暂停时间的需求。
特点如下:
G1中通过将堆划分成多个大小相等的region,每个region是逻辑连续的一段内存:
每个region被标记成E,S,O和H,每个region在运行时会充当其中一种角色,H代表了Humongous,表示Region存储是大对象,当对象大小超过region大小的一半时,直接在新的一个或多个连续的region中分配,并标记成H。这样再也不用单独对每个代大小进行设置了,不用担心每个代空间内存是否够用。
Region大小可以通过-XX:G1HeapRegionSize
参数指定,大小只能是2的幂次方,如果-XX:G1HeapRegionSize
没有设,会在初始化时根据堆大小计算region实际大小(默认将堆内存分成2048份,得到一个合理的大小)。
每个代region数量是可以动态调整的。G1不太适合面向年轻代的收集器,因为新生代特点大多采用复制算法就满足了。
GC方式
G1提供了三种GC回收模式:
YGC
ygc是对年轻代的gc算法,一般对象都在eden region中分配,当eden region耗尽时无法申请内存时,会触发ygc,执行ygc之后活跃对象会被copy到survivor region或者晋升到old region,空闲的region被放入空闲列表中等待被使用。
mixed gc
当越来越多对象晋升到old region时,为避免堆内存耗尽,虚拟机会触发一个mixed gc,会回收整个young region还会回收一部分old region。
mixed gc通过设置阈值-XX:InitiatingHeapOccupancyPercent
来设置,当老年代占整个堆空间大小百分比达到阈值时会触发一次mixed gc。
mixed gc执行过程和cms很像:
full gc
如果对象内存分配较快,mixed gc来不及回收,导致老年代被填满,会触发一次full gc,g1的full gc算法是单线程执行serial old gc会导致长时间暂停时间,需要进行不断的调优,尽量避免full gc。
对象查找
G1引入了Region(分区)的概念,每个region中有个关联的Remembered Set(RS),RS数据结构是Hash,里面的数据是卡表(Card table),RS里面的存的是region中存活对象引用本region对象的关联指针。RS作用是跟踪某个堆区内的对象引用。
当region中数据发生变化时,首先会反映到card table中的一个或多个card上,RS通过扫描card table知道region中内存使用情况,在region使用过程中如果region被填满,分配内存的线程会重新选择一个一个新的region,空闲的region被组织到一个基于链表的数据结构里面,这样可以快速找到新的region。
G1年轻代由eden region和survivor region两部分组成,新建的对象(除了大对象)大部分都分在eden region中,如果分配失败,说明eden region区已满,会触发ygc,回收eden region中的垃圾对象,进行空间释放。
对象分配4个阶段:
对象分配之前会进行逃逸分析,如果该对象只会在本线程使用,就将对象分配到栈上。
这样在函数调用后销毁,减少堆的压力,避免gc。如果在栈上分配不成功,会使用TLAB来分配,TLAB为线程本地分配缓冲区,目的是使对象尽快分配出来。
如果对象在一个共享的空间分配,需要采用一些同步机制管理这些空间的空闲空间指针。在edne中每个线程都有一块属于自己的空间就是TLAB。这样在分配对象时就不需要进行任何同步操作了。
当TLAB中不能进行对象分配时,会尝试在Eden区中进行分配,如果大对象则直接在Humongous区分配。
小对象
G1默认优先从TLAB进行对象空间分配,如果分配失败说明当前TLAB剩余空间不能满足需求,调用allocate_new_tlab方法重新申请一块TLAB空间,G1会从eden region空间分配,小对象分配过程采用指针碰撞进行分配。
大对象
前面介绍了对于小对象的分配,如果是大对象由于TLAB中发不下,会调用G1CollectedHeap::mem_allocate()进行分配。
判断当前对象大小是否超过region大小的一半,如果大于则认定为大对象。大对象在内存分配之前,会进行加锁操作,根据所分配的大小计算出至少需要多少个连续的region。