一、CMS 垃圾收集器
1、特点
- 低延迟、并发收集
- 让垃圾收集线程与用户线程同时工作
- 尽量缩短垃圾收集时用户线程的停顿时间
- CMS:采用标记-清除算法、并发收集、STW的方式回收内存
2、垃圾回收的四个阶段
- 初始标记
程序中所有的工作线程都会因为STW而出现短暂的暂停,仅仅只是标记出根节点能直接关联的对象。标记完成后,恢复之前被暂停的所有应用线程,速度很快。 - 并发标记
从根节点的直接关联对象开始遍历整个对象图的过程,耗时长但是不需要暂停用户线程,可以与垃圾回收线程一起并发运行。 - 重新标记
为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。这个阶段的停顿时间比初始阶段长一些,比并发标记阶段短。 - 并发清除
此阶段清除标记阶段判断的已经死亡的对象,释放内存空间,也是和用户线程并发执行的阶段。
3、CMS的优缺点
优点:
- 并发收集
- 低延迟
缺点:
- 会产生内存碎片:CMS是基于“标记–清除”算法实现的,所以在收集结束的时候会有大量的空间碎片产生。
- 对CPU资源非常敏感: 在并发阶段,虽然不会导致用户线程停顿,但是会因为占用了一部分线程使应用程序变慢,总吞吐量会降低,为了解决这种情况,虚拟机提供了一种“增量式并发收集器” 的CMS收集器变种, 就是在并发标记和并发清除的时候让GC线程和用户线程交替运行,尽量减少GC 线程独占资源的时间,这样整个垃圾收集的过程会变长,但是对用户程序的影响会减少。
- 无法处理浮动垃圾:并发清理阶段由于用户程序的运行可能会产生垃圾对象,只能等到下次gc才能清理。
4、CMS参数设置
-
-XX:+UseConcMarkSweepGC,手动指定使用CMS收集器
-
-XX:CMSlintiatingOccupanyFraction,设置堆内存使用率的阈值,一旦达到该值则开始进行回收
-
-XX:+UseCMSCompactAtFullCollection,用于指定在执行完Full GC后对内存空间进行压缩整理
-
-XX:CMSFullGCsBeforeCompaction,设置在多少次Full GC后对内存空间进行压缩整理
-
-XX:ParallelCMSThreads,设置CMS的线程数量
二、G1 垃圾收集器
1、概述
- G1的目标:在延迟可控的情况下获得尽可能高的吞吐量。
- Garbage first:把内存分割为很多不相关的区域(不连续)。有计划的避免在整个堆中进行全区域的垃圾回收,侧重回收垃圾最大量的区间。
- G1跟踪各个region中的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许回收的时间,优先回收价值最大的region。
- G1是面向服务器端应用的垃圾收集器,主要针对配备多核CPU及大容量内存的机器。
- Humongous:主要用于存储大对象,如果超过1.5个region,就放到H中。如果一个H区装不下一个大对象,则寻找连续的H区存储大对象。
- 分区region:使用G1收集器时,将整个堆划分为2048个大小相同的独立region块,实际的堆大小根据情况设置。所有region的大小相同,且在生命周期内不会被改变。
2、特点与优势
- 并行与并发
- 并行:G1在垃圾回收期间,可以有多个GC线程同时工作,此时用户线程STW。
- 并发:G1和应用程序交替执行,部分工作可以和应用程序同时执行,所以一般来说,在整个回收阶段不会发生完全阻塞应用程序的情况。
- 分代收集
- G1:分代型垃圾回收器,不要求区域是连续的,不固定大小和数量。将堆空间分为若干个区域,包括了老年代和新生代,同时兼顾老年代和新生代。
- 空间整合
- G1:内存的回收是以region作为基本单位的。region之间是复制算法,整体上是标记-压缩算法,都可以避免内存碎片。有利于程序的长时间执行。
- 可预测的停顿时间模型:
- 让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。M-N
- G1:G1跟踪各个region里面的垃圾堆积的价值大小(回收所获得的空间大小和回收所需时间的经验值),在后台维护一个优先列表,每次根据允许收集时间,优先回收价值最大的region,保证了G1收集器在有限的时间内可以获得尽可能高的收集效率。
G1的缺点:
- 在用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用还是程序运行时的额外执行负载都要比CMS要高。G1需要占用额外的内存空间(需要维护一个优先列表)。
- 在小的内存应用上CMS的表现大概率会优于G1,而G1在大内存应用上则发挥优势。
3、垃圾回收过程
G1 垃圾回收过程: 主要包括三个环节。1.年轻代GC->2.并发标记->3.混合回收
-
年轻代GC:45分钟出现一次。YGC时,首先会停止应用程序的执行(STW),创建回收集(需要被回收的内存分段的集合),然后回收年轻代Eden区和Survivor区所占用的内存空间。
(1)扫描根节点
(2)更新Rset:Rset可以准确反映老年代所在的内存分段中对象的引用。
(3)处理Rset:识别被老年代对象指向的Eden区中的对象,这些对象被视为存活对象。
(4)复制对象:对象树被遍历,进行存活对象的复制操作。
(5)处理引用:处理软引用、弱引用等引用,最终Eden区被清空,目标内存中的对象都是连续存储的,没有内存碎片,复制过程可以达到内存整理的效果。
-
老年代并发标记过程:堆空间空间使用率超过45%时,开始进行老年代并发标记。并发标记完成后进行混合回收。
(1)初始标记阶段:标记从根节点直接可达的对象,工作线程会被STW,触发一次年轻代GC。
(2)根区域扫描:扫描S区直接可达的老年代区域对象,并标记为被引用对象。(3)并发标记:整个堆中进行并发标记,可能会被young GC中断。若发现区域内存中的所有对象都是垃圾,则会被立即回收。同时会计算每个区域的对象活性。
(4)再次标记:由于应用程序的持续进行,需要修正上一次的标记结果。是STW的。
(5)独占清理:计算各个区域中的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域。是STW的。
(6)并发清理过程:识别并清理完全空闲的内存。
-
混合回收
当越来越多的对象晋升到老年代区域时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾回收器,回收整个年轻代和部分老年代。可以选择哪些老年代区域进行收集。
4、Rset
Remenbered Set :无论是G1,还是其他分代收集器,JVM使用 Remenbered Set 来避免全局扫描。
- 每个region对应一个 Remenbered Set;
- 每次引用类型数据写操作时,都会产生一个write barrier(写屏障)暂时中断操作;
- 然后检查将要写入的引用指向的对象是否和该引用类型数据在不同的region;
- 如果不同,通过 cardtable 把相关引用信息记录到引用指向的对象所在region对应的Remenbered Set中;
- 当进行垃圾回收时,在根节点的枚举范围加入Remenbered Set,就可以保证不进行全局扫描,也不会有遗漏。