在阐述三种垃圾收集器以前,先普及下几种垃圾回收算法
①、引用计数算法:通过对象被引用的次数确定对象是否被使用,缺点是无法解决循环引用的问题。
②、复制算法:分为from块和to块,开始在from块,回收时将from块存活的对象复制到to块,将from块清空,to块变from块,from块变to块,缺点是内存使用率较低。
③、标记清除算法:分为标记对象和标记不在使用的对象两个阶段,缺点是会产生内存碎片。
④、标记整理算法:与标记清除算法相同,不过在清楚后会进行内存整理。
⑤、分代回收算法:当前的商业虚拟机的垃圾收集都是采用“分代收集”(Generational Collection)算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把堆划分为新生代和老年代,这样就可以根据各个年代的特点采用最适合的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法来进行回收。
如上图,Eden用于分配新的内存空间,当第一轮回收后剩下的对象被放到Survivor1,此时年龄为1。第二次剩下的对象则年龄为1,第一次的年龄为2,他们被复制到Survivor2。当再次回收的时候,又由Survivor2转换到Survivor1容器,他们两个反复替换。当对象的年龄成长到8以后,被移动到老年代。永久代又叫方法区。
Minor GC 年轻代的回收
Major GC 年老代的回收
jvm提供的年轻代回收算法属于复制算法,CMS、G1,ZGC属于标记清除算法。
一、CMS收集器
Concurrent Mark Sweep,以获取最短回收停顿时间为目标的收集器,基于并发“标记清理”实现。JDK1.7之前的默认垃圾回收算法,并发收集,停顿小。
优点:
并发,低停顿
缺点:
1、对CPU非常敏感:在并发阶段虽然不会导致用户线程停顿,但是会因为占用了一部分线程使应用程序变慢
2、无法处理浮动垃圾:在最后一步并发清理过程中,用户县城执行也会产生垃圾,但是这部分垃圾是在标记之后,所以只有等到下一次gc的时候清理掉,这部分垃圾叫浮动垃圾
3、CMS使用“标记-清理”法会产生大量的空间碎片,当碎片过多,将会给大对象空间的分配带来很大的麻烦,往往会出现老年代还有很大的空间但无法找到足够大的连续空间来分配当前对象,不得不提前触发一次FullGC,为了解决这个问题CMS提供了一个开关参数,用于在CMS顶不住,要进行FullGC时开启内存碎片的合并整理过程,但是内存整理的过程是无法并发的,空间碎片没有了但是停顿时间变长了
过程:
1、初始标记:独占PUC,仅标记GCroots能直接关联的对象
补充点:GCroots能直接关联的4种对象
①、虚拟机栈中引用的对象;②、方法区中类静态属性引用的对象;③、方法区中常量引用的对象;④、本地方法栈JNI(native方法)引用的对象
2、并发标记:可以和用户线程并行执行,标记所有可达对象
3、重新标记:独占CPU(STW),对并发标记阶段用户线程运行产生的垃圾对象进行标记修正
4、并发清理:可以和用户线程并行执行,清理垃圾
CMS 出现FullGC的原因:
1、年轻带晋升到老年带没有足够的连续空间,很有可能是内存碎片导致的
2、在并发过程中JVM觉得在并发过程结束之前堆就会满,需要提前触发FullGC
二、G1收集器
Garbage First,是一款面向服务端应用的垃圾收集器。G1算法JDK1.9之后默认回收算法,特点是保持高回收率的同时,减少停顿。
特点:
1、并行于并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
2、分代收集:分代概念在G1中依然得以保留。虽然G1可以不需要其它收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。也就是说G1可以自己管理新生代和老年代了。
3、空间整合:由于G1使用了独立区域(Region)概念,G1从整体来看是基于“标记-整理”算法实现收集,从局部(两个Region)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片。
4、可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用这明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
如果不计算维护Remembered Set的操作,G1收集器的运作大致可划分为以下几个步骤:
1、初始标记(Initial Making)
2、并发标记(Concurrent Marking)
3、最终标记(Final Marking)
4、筛选回收(Live Data Counting and Evacuation)
看上去跟CMS收集器的运作过程有几分相似,不过确实也这样。初始阶段仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可以用的Region中创建新对象,这个阶段需要停顿线程,但耗时很短。并发标记阶段是从GC Roots开始对堆中对象进行可达性分析,找出存活对象,这一阶段耗时较长但能与用户线程并发运行。而最终标记阶段需要吧Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但可并行执行。最后筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,这一过程同样是需要停顿线程的,但Sun公司透露这个阶段其实也可以做到并发,但考虑到停顿线程将大幅度提高收集效率,所以选择停顿。
与其它收集器相比,G1变化较大的是它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留了新生代和来年代的概念,但新生代和老年代不再是物理隔离的了它们都是一部分Region(不需要连续)的集合。同时,为了避免全堆扫描,G1使用了Remembered Set来管理相关的对象引用信息。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏了。
三、ZGC收集器
Z Garbage Collector 垃圾收回器,也被称为 ZGC, 是一种可伸缩的低延迟垃圾收集器。Java 11包含一个全新的垃圾收集器--ZGC,它由Oracle开发。
目标
垃圾回收停顿时间不超过10ms
无论是相对小的堆(几百MB)还是大堆(TB级)都能应对自如
与G1相比,吞吐量下降不超过15%
方便日后在此基础上实现新的gc特性、利用colored pointers(译者注:暂时翻译为彩色指针)和读屏障进一步优化收集器
ZGC描述
大体上来说,ZGC是一种并发的、不分代的、基于Region且支持NUMA的压缩收集器。因为只会在枚举根节点的阶段STW, 因此停顿时间不会随着堆大小或存活对象的多少而增加。
ZGC的一个核心设计就是读屏障与彩色指针(colored object pointers, 缩写, colored oops)组合起来使用总体来说是一种利用64位指针中未使用的bit来保存元数据的指针)。这是ZGC可以与用户线程并发执行的原因。从Java的线程角度来看,读取Java对应中的引用变量的操作属于一种读屏障。与单纯的取对象内存地址相比,使用读屏障可以利用彩色指针中包含的信息来决定在允许Java线程读取指针的地址值之前是否需要执行一些操作。例如,对象可能被垃圾收集器移动过了,这时读屏障就可以感知到这种情况并执行一些必要的行为。
跟其它的可选方案相比,我们认为使用彩色指针模式有一些非常吸引人的优势,比如:
– 这允许我们在移动对象/整理内存阶段,在指向可回收/重用区域的指针确定之前回收/重用这部分内存。(原文: It allows us to reclaim and reuse memory during the relocation/compaction phase, before pointers pointing into the reclaimed/reused regions have been fixed.)。这有利于降低堆的开销。这同时也意味着我们不需要再实现一个单独的标记-整理算法用于处理Full GC。
– 这允许我们使用相对来说更少量、更简单的GC屏障。这可以降低JVM运行时的性能开销。同时也可以让JVM字节码解释器和JIT编译器中的GC代码更加容易实现和优化。
– 我们目前会在彩色指针中保存与标记和重定位相关的数据。不过,只要彩色指针中还有足够的未使用的bit, 我们还可以在里面存储更多对读屏障有用的信息。我们认为这为未来实现更多的特性奠定了良好的基础。比如,在复杂多变的内存环境下,我们可以在彩色指针中存储一些追踪信息来让垃圾回收器在移动对象时能将低频次使用的对象移动到不常访问的内存区域。
来源:oschina
链接:https://my.oschina.net/u/4308002/blog/3274972