垃圾收集器ParNew和CMS
垃圾收集算法
- 分代收集理论
- 根据不同代的特点选择合适的垃圾收集算法
- 标记复制算法
年轻代使用复制算法
- 可以将内存分为大小相同的两块,每次使用其中一块,当这一块使用完后,就将还存活下来的对象复制到另一块,然后再将使用的那一块一次性清理掉,这样就每次的内存回收都是堆内存区间的一半进行回收,
很少产生内存碎片
问题
: 复制算法产生什么问题浪费空间
,会让一半的空闲空间空着.
- 标记清除算法
老年代使用较多
- 分为标记和清除两个阶段,标记存活的对象,统一回收所有被标记的对象,也可以反过来
问题
: 标记清除会产生什么问题- 如果
空间太大,需要标记的对象太多,效率太低
- 标记清除后容易
产生大量不连续的内存碎片
(可以使用参数-XX:+UseCMSCompactAtFullCollection让JVM在执行完标记清除后再做整理)
- 如果
- 标记整理算法
老年代使用较多
- 标记过程和标记清除算法一样,只不过后续不是直接回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存
问题
:标记整理有什么问题- 在标记清除的基础上还需要进行
对象的移动,成本比较高
,但是不会产生内存碎片
- 在标记清除的基础上还需要进行
垃圾收集器
详细链接: https://www.processon.com/view/link/61e594711efad4259c6bbf0a
Serial收集器
- 现在基本不用此收集器
- 这个收集器是单线程收集器,只能通过单线程去完成回收工作,而且工作的时候会触发STW机制,直到收集结束
优点
: 与其他单线程收集器相比简单高效- 对应的SerialOld收集器是Serial收集器老年代的版本
- 使用参数: -XX:+UseSerialGC -XX:+UseSerialOldGC
年轻代采用的是复制算法,老年代采用的是标记整理算法
Parallel收集器
- JDK8默认的年轻代和老年代收集器.Parallel对于小内存效率还行,但是如果内存超过4G有些问题,STW时间较长,其实就是Serial多线程版本,默认的收集线程数和CPU线程数相同,也可以通过参数
-XX:ParallelGCThreads
指定收集线程,但一般不推荐修改 - 此线程的
关注点是吞吐量
,也就是高效率利用CPU,CMS等收集器的关注点是用户线程的停顿时间
,为了保证用户体验
,Parallel收集器提供了很多参数供用户找到合适的停顿时间或最大吞吐量 - 使用参数: -XX:UseParallelGC -XX:UseParallelOldGC
年轻代采用的是复制算法,老年代采用的是标记整理算法
ParNew收集器
CMS是老年代才使用的收集器
,但不能与Parallel一起使用,ParNew就是为了配合CMS一起使用才诞生的
,且只能用在年轻代- 此收集器和Parallel收集器很类似,区别就在于它是配合CMS一起使用的
- 使用参数: -XX:UseParNewGC
只能用于年轻代,采用的是复制算法
- 参数设置
- -XX:SurvivorRatio: 设置Eden空间大小与幸存者空间大小之间的比率。默认情况下,此选项设置为8
- -XX:PreTenureSizeThreshold: 大对象到底多大,大于这个值的参数直接在老年代分配
- -XX:MaxTenuringThreshold: 最大值15,并行(吞吐量)收集器的默认值为15,而CMS收集器的默认值为6
- -XX:+ParallelGCThreads: 并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同
- -XX:+UseAdaptiveSizePolicy: 自动选择各区大小比例
CMS收集器
- CMS是
老年代才使用的收集器
,主要是以获取最短回收停顿时间为目标
,更注重的是用户体验
的收集器,是hotspot第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作 - CMS运作步骤
- 初始标记
STW利用可达性分析算法找gcroots链上的直接引用对象
,这里不包含间接引用对象(它们的成员对象引用到的其他对象)
,速度很快- 这里进行STW的原因主要是因为
程序运行期间可能会不断有新的对象被引用,导致初始标记不完
- 并发标记
从gcroots的直接引用对象开始遍历整个对象图的过程
,耗时较长但是不需要暂停用户线程,可以与垃圾收集线程一起工作
。因为用户线程继续运行着,可能会导致已经标记的对象的状态发生了改变
,这也是后续重新标记的原因,并发标记占用整个运作过程的时间最长,大概占整个收集过程的80%
- 并发标记不进行STW的原因主要是
为了用户体验
- 重新标记
STW修正并发标记期间因为用户线程继续运行而导致标记状态改变的对象
,这个阶段的停顿时间会比初始标记的时间稍长,远远比并发标记的时间段,主要是用到了三色标记
里面的增量更新
做重新标记
- 并发清理
开启用户线程,同时GC线程开始对未标记的对象进行清理
,整个阶段如果有新增对象会被标记为黑色(三色标记),当做浮动垃圾,不做处理
- 并发重置
重置本轮GC过程的标记数据
,也就是还原标记的数据
- 初始标记
优点
- 用户体验好,并发收集,低停顿
缺点
- 对CPU资源比较敏感,
会和服务器抢资源
在并发标记和并发清理产生的垃圾无法处理
(浮动垃圾),这种垃圾只能等到下一次GC在清理
- 使用
标记清除算法,会产生大量的内存碎片
,当然可以配置参数在清理完之后进行整理
,使用参数-XX:+UseCMSCompactAtFullCollection - 执行过程中的不确定性,会存在上一次垃圾回收还没执行完,然后又再次触发回收,特别是在
并发标记和并发清理的阶段会出现一边回收,系统一边执行,也许没回收完就再次触发FullGC,也就是并发模式失败(concurrent mode failure)
,此时会STW使用SerialOld单线程去垃圾回收
- 对CPU资源比较敏感,
问题
: 并发模式失败什么情况下才会产生这个问题- 当收集
处于并发清理/并发标记的时候,用户线程还在把大对象放到老年代,但是老年代放不下了
,这种情况会触发STW,然后自动切换SerialOld单线程去回收,尽量避免出现此类情况
- 当收集
- 常用参数
- -XX:+UseConcMarkSweepGC: 启用CMS
- -XX:ConcGCThreads: 并发的GC线程数
- -XX:+UseCMSCompactAtFullCollection: FullGC后做压缩整理,减少内存碎片
- -XX:CMSFullGCsBeforeCompaction: 多少次FullGC压缩整理一次
- -XX:CMSInitiatingOccupancyFraction: 老年代达到多少阈值触发Full GC
- -XX:+UseCMSInitiatingOccupancyOnly: 只是用设定的回收阈值
- -XX:+CMSScavengeBeforeRemark: 在CMS Full GC前启动一次Minor GC,用来减少老年代对年代的引用,降低CMS GC标记阶段的开销
- -XX:+CMSParallellnitialMarkEnabled: 初始标记的时候多线程执行,缩短STW
- -XX:+CMSParallelRemarkEnabled: 在重新标记的时候多线程执行,缩短STW
小结
CMS是在用户体验和回收效率
上直接取了一个均衡值,Parallel主要注重回收效率高吞吐
,所以内存设置在4-8G以内
,超过8G的推荐使用G1
所有的垃圾收集器只针对于堆
,不应用在方法区,因为方法区基本回收不了什么垃圾,回收条件比较苛刻,基本配置上一个合适的大小不随意触发FullGC就行了