CMS收集器是一种以获取最短回收停顿时间为目标的收集器。很大一部分是应用在互联网网站或者浏览器的B/S系统的服务端。
CMS是基于标记清除算法实现的,他的整个过程的步骤如下
初始化标记是标记一下GC Roots能直接关联的对象,速度很快,并发标记就是从GC Roots中直接关联的对象开始遍历整个对象的过程,这个过程耗时比较长,但是用户线程不用停顿,重新标记是因为在并发标记阶段会有用户线程运行,会导致标记产生变动,这个阶段停顿时间比初始化阶段要长,但是远比并发标记停顿时间要短,而并发清除,清除那些被判断死亡的对象,此阶段不需要移动对象,因此不需要停顿用户线程
CMS的优点和缺点
优点并发收集,低停顿,但是他的缺点也是不可忽略的。
Garbage First收集器(G1)
G1收集器是垃圾收集器技术历史上的一个里程碑,他开创了面向局部的设计思路和Region的内存布局形式,他是最为CMS收集器的替代者和继承人,而设计者希望可以设计出一款能够建立起停顿时间模型的收集器,停顿时间模型是指可以指定一定长度为M毫秒的时间段内,消耗在垃圾收集器上的时间大概率不能超过N毫秒的目标。
之前的垃圾收集器都是在新生代,老年代或者java整个堆进行垃圾收集,而G1并不是这样,他可以面向堆内存中任何部分组成回收集进行回收,衡量的标准不再是属于那个分代,而是那块内存存放的垃圾数量多,且回收的收益更大,这个就是G1收集器的Mixed GC模式
G1是基于Region的堆内存布局实现,G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续Java堆划分成多个大小相等独立区域,每个Region扮演的角色可以是新生代Eden,Survivor空间,或者老年代,这样无论是新创建的对象还是已经被熬过了多次垃圾回收的对象都能获得很多的效果
且他还有一个Humongous对象,专门存储大对象,当一个对象的大小是Humongous大小的一半就认为是大对象,如果超过整个Region容量的超级大对象,会用N个连续的Humongous存放,Humongous Regions看做老年代的一部分,而Region是可以用参数设置大小的(-XX:G1HeapRegionSize设置,范围是1M-32M,且是2的幂次方).
虽然G1还保留着新生代和老年代的概念,但是他们不再是固定的了,而是一系列区域的动态集合,由于Region是单次回收的最小单位,每次回收的内存空间都是Region大小的整数倍,这样就可以避免整个java堆中全区域垃圾回收,而G1收集器去跟踪各个Region里面的垃圾收集的价值大小,价值即回收所获得空间大小以及回收所需要的时间经验值,而后台维护一个优先级队列类表,每次根据用户设置允许的手机停顿时间,优先回收价值收益最大的那些Region,使用Region划分内存空间,以及具有优先级的区域回收方式,保证了G1收集器在有限时间内尽可能获取高的收益效率。
Region如何解决对象引用
首先我们要知道一个东西就是卡表,我们知道如果老年代引用新生代的引用,是不是要把整个老年代加进GC Roots里面,当然不是这样的,为了解决这个问题引入了记忆集的数据结构,而记忆集的具体实现就是卡表
卡表是一个字节数组,且每一个元素的对应一个内存区域的特定大小的内存块,这个内存块就做卡页,卡页的内存包含着一个或多个对象,只要卡页有一个对象存在跨代指针,就标记对应卡表元素的值为1,称之为脏的,没有则标记为0,当发生Minor GC的时候我们直接在卡表中找到脏的,添加到GC Roots中扫描,就不必进行扫描整个老年代,扫描完之后就直接再标记卡表的元素为0.
而G1卡表是一种双向卡表的结构(我指向谁,谁指向我,进行记录),要比原来的卡表实现起来更加复杂,同时由于Region数量比传统收集器的分代数量明显要多很多,因此G1收集器要比传统的收集器更高的内存占用负担。
如何解决用户线程和收集线程互不干扰
如何解决可靠的停顿预测模型
用户可以通过参数-XX:MaxGCpauseMillis参数指定停顿时间仅仅意味着垃圾收集器发生之前的期望值,但是具体G1怎么做才能做到用户的预期呢.
G1是以衰减均值为理论基础来实现的,在垃圾收集的时候,G1收集器会收集每个Region的回收耗时,每个Region记忆卡集的脏卡的数量以及花费的成本,并分析平均值,标准偏差,置信度等统计信息,且衰减均值是最近的平均状态,Region的统计状态越新越能决定其回收的价值,然后通过这些信息预测现在开始回收的化,那些Region是最有价值的。
G1垃圾收集器的步骤如下
G1优点和缺点
G1可以指定最大停顿时间,分Region的内存布局,按收益动态确定回收这些创新性设计带来的红利,也不会产生内存空间碎片.
但是G1也有许多弱项,就内存来说虽然G1和CMS都是使用卡表处理跨代指针,但是G1的卡表实现更复杂,且是每一个Region都会有一个卡表,而CMS只需要唯一一份卡表,因此就会导致G1记忆集可能会占整个堆内存的20%甚至更多的内存空间。
还有就是负载的角度上,G1和CMS虽然都是使用写后屏障进行卡表的维护工作,但是G1还要用写前屏障来跟踪并发时的指针变化情况,此时由于跟踪引用变化带来的额外负担。且G1对写屏障的复杂操作要比CMS消耗更多的运算资源,所以CMS的写屏障和写后屏障实现是直接的同步操作,而G1就不得不将其实现为类似消息队列的结构,把写前屏障和写后屏障中要做的事情放到队列里面,然后一步处理。