Garbage First 综述
前言
本文为《Java 性能调优》第一章节的读书笔记,是本人阅读过程中理解归纳的一个记录,其中也参杂扩展了《Java性能优化权威指南》中的一部分知识点。
术语
- 并行,这是一个多线程的垃圾回收器
- 并发,垃圾回收线程与用户线程同时工作
- stop-the-world (STW),所有应用线程将被暂停
一个垃圾收集器可以用以上三个术语的一个或多个组合起来进行描述。
性能优化3大目标
- 吞吐量,不考虑垃圾回收引起的停顿和内存消耗,应用所能达到的最高性能指标
- 延迟,目标是缩短垃圾回收引起的停顿
- 内存占用,应用流畅运行所需的内存总量
GC调优3选2原则
选择上面三个属性中的最多两个属性进行优化
并行垃圾收集器
并行 | 并发 | STW |
---|---|---|
Y | N | Y |
概念,每次发生垃圾回收时,停掉所有应用线程,并行的执行垃圾回收工作。
目标,是提高吞吐量,但延迟可能会很大。
与堆,堆越大,延迟时间越长。
使用场景,批处理任务。
串行垃圾收集器
并行 | 并发 | STW |
---|---|---|
N | N | Y |
- 概念,每次发生垃圾回收时,停掉所有应用线程,串行的执行垃圾回收工作。
- 目标,是降低内存占用,但吞吐量和延迟会比较糟糕。
与堆,堆越大,延迟时间越长。
使用场景,客户端应用和嵌入式系统。
CMS 垃圾收集器
并行 | 并发 | STW |
---|---|---|
Y | Y | Y |
概念,年轻代与并行垃圾收集器一样,老年代采用标记清理算法,把标记阶段拆分成三个周期,其中只有两个周期需要STW
目标, 缩短延迟时间
与堆, 堆越大,延迟越大(标记时需要扫描整个堆,堆越大,当然需要的时间越长)
使用场景,与客户交互式应用(大部分web应用)
CMS垃圾收集器阶段
初始标记 –> 并发标记 –> 预清理 –> 重新标记 –> 并发清除
CMS垃圾收集器注意
老年代并发收集正在进行的时候有可能会发生一个年轻代的收集,老年代中断,等待年轻代收集。
CMS垃圾收集器的缺点
- 占用更多的内存,比并行GC多用10%~20%
- 默认不做内存压缩,会留下很多内存碎片,可开启内存压缩,但会增加延迟时间
- 占用应用cpu(并发,与应用线程一起执行),cpu多无所谓(大势所趋,cpu核数原来越多)
收集器总结
基本上前三种收集器可以满足大部分场景:嵌入式、批处理、web应用。但他们三个也有一些共同的无法解决的问题:
- 堆越大、延迟越大(未来物理机内存肯定是越来越大)
- 年轻代和老年代的内存地址是连续的,要先决定年轻代和老年代虚拟地址空间什么位置(这句没理解)
为了解决这些问题,引入了G1, G1 是CMS的一个不错的替代者,但不见得是并行收集器的替代者。
G1 垃圾收集器
G1 采用不同的手段来做垃圾回收,为了避免前几个垃圾回收器面临的共同问题(堆越大性能越差,因为要扫面整个堆),G1把堆分成若干小分区,分而治之的思路
初始标记 — 并发标记 - 重新标记 - 清除
超过内存占用阈值,触发初始标记
初始标记会根着下一次年轻代gc同时进行
重新标记stw运行
重新标记之后识别出最适合回收的分区的集合,CSet
CSet内的分区在接下来的几次年轻代gc中被回收掉(混合收集)
不管是青年代还是老年代,G1会把每个收集过垃圾的分区中存活的对象转移到一个可用分区中。(G1之前的年轻代也是这种收集逻辑,复制清理)
这样做的好处是,垃圾回收过后,内存不会产生碎片。
G1允许用户设置gc暂停时间目标,G1根据目标自动调整年轻代尺寸和堆的尺寸。
G1内部包含一个“启发式”算法,可以做自我调整。
综上,G1使用的场景是“需要合理最大延迟时间的大尺寸java堆的应用”,包括可以替代CMS和并行收集器的应用。但目前采用并行收集器的应用如果可以容忍最大延迟时间比较长,并行收集器仍然是最好的选择。
G1 设计
分区尺寸
- 1、2、4、8、16、32 mb
- jvm运行过程中尺寸不会发生变化
- 尺寸是根据堆初始值和最大值的平均数来的
Rset
每个分区包含一个Rset,他是一个关联的记忆集合,记录跟踪分区外指向分区内的引用,避免堆整个堆的扫描。
Rset尺寸以来应用的行为,最小大概占堆的1%,最多可能会达到20%。
一个分区一次最多只能用于一个目的
可用分区 -> 被使用(作为年轻代、老年代、巨型对象…) -> 被垃圾回收 -> 可用分区
当标记阶段如果发现老年代分区中没有任何存活对象,G1会提前将这个分区回收;反之,如果有包含存活对象则安排到未来将发生的混合收集中进行回收
G1 并发标记线程为了避免占用过多的cpu,工作方式是爆发式的,一段时间内拼命干活,任何暂停一段时间让应用线程工作。
巨型对象
尺寸特别大的对象,甚至超过一个分区的大小,给他们分配专门的分区(甚至几个分区联合盛放一个巨型对象)
full gc
g1垃圾回收过程中如果发生任何不幸,内存不够了之类的,就引发一个full gc,对整个堆回收一把(和cms类似),而且是串行的垃圾回收器(搞不懂为啥不并行),g1希望不进行full gc,或通过调优实现不发生full gc。个人理解,要去监控g1的gc日志,如果发生过full gc就要进行分析和调优了,cms也一样。
并发周期
初始标记 并发根分区扫描 并发标记 重新标记 清除
stw的包括初始标记和重新标记,初始标记跟着年轻代收集一起,因为年轻代收集本来也要做这个事情
堆空间调整
G1堆大小在 -Xms 和 -Xmx 之间动态的扩大或缩小
增加堆尺寸的几个理由:
- 在一次full gc中,基于堆尺寸的计算结果会调整堆的空间
- 在发生年轻代收集或混合收集时,G1会计算花费的时间,以及执行java应用所花费的时间,在根据用户预期的时间(-XX:GCTimeRatio)进行调整
- 对象分配失败时
- 巨型对象分配失败,找不到连续的内存空间
- gc需要一个新的分区来转移对象的时候
读书总结
本章主要通过G1之前的垃圾收集器做引子,逐步引出G1垃圾收集器。主要简单的讲了G1的一些基本概念和过程,细节的地方讲的不全。G1的思路是可以的,未来在工作中满足下面的条件时考虑用G1替换cms:
- 大尺寸java堆
- 最大延迟过长(因为gc的原因)
- 并发垃圾回收器吞吐量够,但是最大延迟时间无法忍受
当然还需要继续阅读接下来几章,来了解G1调优的策略监控的重点指标等。