关于GC之二-常用GC算法
2019-01-27 本文已影响15人
AlanKim
回收
回收是个比较大的话题,有各种各样的算法,针对不同区域,也有不同的算法选择。
常见的GC算法
标记清除(Mark-Sweep):
最基础的GC算法,将需要回收的对象做标记,之后扫描,对标记为可清除的对象进行回收。
清理完成之后会产生随便。是CMS是基础
复制(Copying)
标准应用就是young代的两个S区,互为From、To,复制后,清除From及eden中所有空间。如果To空间不足,则需要对象晋升到Old代。
标记-整理(Mark-Compact)
实现上与标记清除算法前半段一样,都是先标记。但是在清除时,是先将标记过的不需要回收的对象移动到一起,使得内存连续,这样,只要将标记边界之外的内存空间清理掉就好了。解决了碎片化问题。
GC实现的搭配使用
关于GC的实现及搭配使用,先上一个图:
1353773614_1052.jpg可以看到,除了G1是自己包办天下外,其他的GC算法都是新生代+老年代搭配起来用的,这跟各个分代中的对象特性有一定关系。下面详细说明:
Serial GC:
- 顾名思义,串行GC。单线程回收机制,在做回收时,会停掉其他所有操作,即STW。
- 适用于单CPU、新生代空间很小以及对暂停时间不是很敏感的应用上
- 使用JVM args:-XX:+UseSerialGC 来指定使用。
- 同样有Serial Old算法,用于老年代收集,但是会STW很久。
ParNew GC:
- 在SerialGC的基础上加入了多线程,但是在GC的时候一样需要STW,只是GC是多个线程在同时进行而已,提高了效率。
- 使用JVM args: -XX:+UseParNewGC来指定使用
- 只在Young代收集
Parallel Scavenge:
- 在扫描和复制过程采用多线程,与parNew很类似,但是侧重点不同。
- parNew主要侧重于停顿时间尽量少,不至于使用户一直等待。而parallel scavenge则侧重于达到一个可控制的吞吐量(Throughput)。也被称为吞吐量优先收集器。
- 所谓吞吐量就是CPU用于运行用户代码时间与CPU总消耗时间的比值。吞吐量=运行用户代码时间/运行用户代码时间+垃圾收集时间。
- 使用JVM args: -XX:+UseParallelGC来指定使用
- 使用JVM args: -XX:ParallelGCThreads=4 来指定线程数
- 对应的Parallel Old GC 用于老年代收集
CMS
- Concurrent Mark Sweep
- 目标是为了解决Serial GC的停顿问题,达到最短回收时间。
- 从其缩写可以看到,基于Mark-Sweep算法实现的,当然也就会有碎片化问题。
- 整个收集过程分为以下五个大步骤:
- 初始化标记,CMS initial mark,标记GC Roots能直接关联到的对象,需要STW,执行速度很快
- 并发标记,CMS concurrent mark,进行GC Roots Tracing,一条条链路直到叶子节点,并发执行,不影响业务。
- 重新标记,CMS remark,修正并发标记执行中,线程继续执行产生的标记变化,需STW,比初始化标记时间久,但是远小于并发标记耗时。
- 并发清除,CMS concurrent sweep,根据标记执行并发清除,不需要STW。
- 并发reset,恢复初始化设置,不需要stw,耗时非常短
- CMS缺点
- 对CPU资源非常敏感,在并发阶段,虽然不会导致用户线程停顿,但是会占用cpu资源,从而导致应用变慢,总吞吐量下降。
- 默认的回收线程数是: (cpu-kernel-count + 3)/ 4。也就是说,四核cpu,那么就是 (4+3)/4 = 1(取整)
- 无法处理浮动垃圾
- 由于GC过程中,用户线程继续在执行,所以要预留足够的内存空间给用户线程使用。因此CMS不能像其他GC收集器那样等到老年代快被填满了再进行收集,需要预留一部分空间,默认在Old代 68%的空间被使用时就会触发CMS。
- 可以使用JVM args: -XX:CMSInitiatingOccupancyFraction来指定触发CMS的百分比,比如提高百分比,减少CMS GC触发的次数,提高性能。该参数必须配合-XX:UseCMSInitiatingOccupancyOnly使用才有效
- 但是如果预留的内存太少,无法满足应用运行的内存需要,那么就会出现Concurrent Mode Failure 异常
- 出现Concurrent Mode Failure时,JVM会临时启用Serial Old GC,来重新进行Old代的收集,这样STW时间就会很长。
- 基于Mark-Sweep,会产生很多碎片。在有大对象需要分配空间时,内存无法找到连续空间来分配,不得不提前偿触发一次Full GC。
- 使用JVM args: -XX:UseCMSCompactAtFullCollection在Full GC之后增加一个压缩过程
- 使用JVM args: -XX:CMSFullGCBeforeCompaction=? 参数设置执行多少次不需要压缩的CMS Full GC后,进行一次压缩操作
- 浮动垃圾 floating garbage:并发清理阶段产生的garbage,本次gc已经无法处理,只能等待下次处理。
- GC Roots:
- 虚拟机栈(栈帧中局部变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- Native Method Stack中JNI中引用的对象。
G1
基于标记整理,Mark-Compact,不会产生内存碎片。
分region,region维度进行GC,而不是在young、old、方法区整个维度进行整理。