GC垃圾收集器整理
收集器种类有下面几种:
- serial / serial old 收集器
- parnew收集器
- parallel scavenger / parallel old 收集器
- cms 收集器
- g1 收集器
收集器 | 空间代 |
---|---|
Serial | young |
ParNew | young |
parallel scavenger | young |
CMS | old |
Serial Old | old |
parallel Old | old |
G1 | young/old |
java准备了这么多的收集器,无非是想解决"stop the world" 的问题,即使是目前主流的g1和cms也不能完全的解决的停顿问题。
可以分为2个方向去解决这个问题,一个是并发
,GC线程和业务线程能并发执行,一个是并行
GC 线程之间的并行
serial / serial old
最古老的收集器,单线程收集器。单线程完成垃圾收集工作,并且在他进行垃圾收集时工作线程无法工作,必须停止,知道结束。serial old 则是serial 的一个老年代版本。
parnew
parnew 也是serial的一个衍生物,在GC过程中,采用了多线程进行垃圾收集,但是依旧需要停止所有的用户工作线程。ParNew 是目前虚拟机中的首选新生代收集器,因为目前只有它和serial与CMS收集器配个工作。因此,一般在使用cms的服务上,观察gc日志,是可以发现有ParNew的踪迹的。
parallel scavenger
parallel scavenger 收集器与ParNew相似,在可调整参数上有不一样的。因为parallel scavenger 关注的是吞吐量,吞吐量 = 运行 用户代码时间 / (运行 用户代码时间+垃圾收集时间),提供了2个参数控制:
-xx:MaxGCPauseMillis 最大垃圾收集停顿时间。需要根据实际运行的情况来设置,停顿时间少了,那么次数必定会频繁。
-xx:GCTimeRatio 直接设置吞吐量大小。
如果觉得麻烦,这个收集器提供了一个开关参数:-xx:+UseAdaptiveSizePolicy ,当此开关打开后,就不需要手工指定新生代的大小,等细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,自动调整已达到最大的吞吐量。—— GC自适应的调节策略。 同理 parallel old为parallel scavenger 的老年代版本。
参数 | 涵义 |
---|---|
-XX:MaxGCPauseMills | 最大停顿时间,单位毫秒 |
-XX:GCTimeRatio | 0-100的取值范围 垃圾收集时间占总时间的比 |
CMS(Concurrent Mark & Sweep)收集器
获取最短回收停顿时间为目标的收集器。
运用算法:标记 -> 清除。
标记分为3步: 初始标记 - 并发标记 - 重新标记
清除:并发清除
虽然可以看到标记-清除算法中是存在缺陷的,但是cms还是在它的基础上改造后使用。
初始标记:触发stop the world ,因为只是标记了GC Roots能直接关联到的对象,所以速度很快。
并发标记:进行GC Roots Tracing 的过程,字面意思,可以GC线程和工作线程同步进行(类似于CPU轮转,2种线程交替进行),所以CMS受cpu性能的影响
重新标记:触发stop the world,为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象记录。这个阶段一般长与初始标记阶段。
清除阶段:这个阶段也是很耗时,但好在可以GC线程和工作线程同步进行(交替进行)。
CMS并行收集只能在老年代中使用,在年轻代使用时,还是需要stop-the-world
上诉来看,CMS收集器,存在2个缺点:
- 无法处理浮动垃圾,清理与用户线程同时运行,这一部分还是不停的产生垃圾,所以这部分垃圾是无法在这一次中清理掉的,只能等到下一次。
- 基于"标记-清除"算法,天然存在内存碎片问题。对此CMS提供了一个开关参数-XX:+UseCMSCompactAtFullCollection 开关参数,用于在cms要进行fullgc时开启内存碎片的合并整理过程。内存整理是无法并发的,所以停顿时间会边长。还有一个-xx:CMSFullGCsBeforeCompaction 这个参数用于设置执行多少次不整理的full gc后,跟着来一次带整理的。默认 0
参数 | 涵义 |
---|---|
-XX:CMSInitiatingOccupancyFraction | 触发GC的阈值 |
-XX:+CMSFullGCsBeforeCompaction | 设置进行几次Full GC后,进行一次碎片整理 |
-XX:ParallelCMSThreads | 设定CMS的线程数量 |
基于本人的知识来看,内存碎片,是一个非常值得重视的问题,java在上面还是做了很多方法去解决。
G1收集器
1 代表了 First
所以全称为:Garbage First,优先处理垃圾多的内存块。
G1收集器与之前的收集器有很大的区别,当使用g1时,内存分布发生了变化。普通的收集器:Eden(E), Suvivor(S)和Old(O)
g1中也是把内存分为了这3类,但是G1不是划分的这么分明,g1把内存分为了很多小块(region),每一块会被标记为E/S/O中的一个,位置不确定。
可以理解为,象棋棋盘,楚河汉届分的很清楚,而G1像围棋,每一个格子都可以落子。
g1 的工作过程与CMS大致一样:
初始标记 - 并发标记 - 重新标记-并发清除
为什么要这么区分?
1、内存块的粒度变小了,从而可以垃圾回收工作更彻底的并行化。
2、从整体来看是基于“标记-整理”,从局部上来看是基于复制算法实现的。这2中算法都意味着不会产生内存碎片,收集后可以提供连续内存。
3、且解决了CMS在年轻代中无法并行收集的问题,g1可以在年轻代中也可以并行收集。
4、可以预估应用的停顿时间,这个只有G1可以做到,其他GC每次都会回收整个年轻代和老年代,而实际的垃圾大小是不可控的,所以时间也是不可控的,而G1每次并不会回收整个内存空间,可以根据配置,优先来回收垃圾堆积价值大的内存块。时间长就多收集几个内存块,少就少收集几个。
G1并不是说完胜CMS,当内存空间本身很小时,由于G1的收集算法复杂,效果不如CMS。
参数 | 涵义 |
---|---|
-XX:MaxGCPauseMillis=n | 设置最大GC停顿时间(GC pause time)指标(target). 这是一个软性指标(soft goal), JVM 会尽量去达成这个目标. |
-XX:InitiatingHeapOccupancyPercent=n | 启动并发GC周期时的堆内存占用百分比. G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示"一直执行GC循环". 默认值为 45. |
-XX:G1ReservePercent=n | 设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10. |
-XX:G1HeapRegionSize=n | 使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb. |
化整为零的思路,微服务、分布式等等开发中各处可见,不知道后面的趋势如何,毕竟合久必分,分久必合
最近在工作中,有调整为G1收集器的打算,后续再记录实际使用的情况。
常用GC参数
-XX:+UseSerialGC:在新生代和老年代使用串行收集器
-XX:SurvivorRatio:设置eden区大小和survivior区大小的比例
-XX:NewRatio:新生代和老年代的比
-XX:+UseParNewGC:在新生代使用并行收集器
-XX:+UseParallelGC :新生代使用并行回收收集器
-XX:+UseParallelOldGC:老年代使用并行回收收集器
-XX:ParallelGCThreads:设置用于垃圾回收的线程数
-XX:+UseConcMarkSweepGC:新生代使用并行收集器,老年代使用CMS+串行收集器
-XX:ParallelCMSThreads:设定CMS的线程数量
-XX:CMSInitiatingOccupancyFraction:设置CMS收集器在老年代空间被使用多少后触发
-XX:+UseCMSCompactAtFullCollection:设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理
-XX:CMSFullGCsBeforeCompaction:设定进行多少次CMS垃圾回收后,进行一次内存压缩
-XX:+CMSClassUnloadingEnabled:允许对类元数据进行回收
-XX:CMSInitiatingPermOccupancyFraction:当永久区占用率达到这一百分比时,启动CMS回收
-XX:UseCMSInitiatingOccupancyOnly:表示只在到达阀值的时候,才进行CMS回收
网友评论