1. GC分类于性能指标
1. 说明
- 垃圾收集器没有在规范中进行过多的规定, 可以由不同的厂商, 不同版本的JVM来实现.
- 由于JDK的版本处于高速迭代过程中, 因此java发展至今已经衍生了众多的GC版本
- 从不同角度分析垃圾收集器, 可以将GC分为不同的类型.
2. 分类
-
按线程数分, 可以分为串行回收器和并行回收器
-
按工作模式分, 可以分为并发式垃圾回收器和独占式垃圾回收器
-
按碎片处理方式分, 可以分为压缩式垃圾回收器和非压缩式垃圾回收器
-
按工作的内存区间分, 又可分为年轻代垃圾回收器和老年代垃圾回收器
3. 评价GC的性能指标
1. 指标
- 吞吐量: 运行用户代码的时间占总运行时间的比例
- 垃圾收集开销: 吞吐量的补数, 垃圾收集所用时间与总运行时间的比例
- 暂停时间: 执行垃圾收集时, 程序的工作线程被暂停的时间
- 收集频率: 相对于应用程序的执行, 收集操作发生的频率
- 内存占用: java堆区所占用的内存大小
- 快速: 一个对象从诞生到被回收所经历的时间
- 简单来说, 主要抓住两点: 吞吐量和暂停时间
2. 吞吐量(through put)
- 吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值, 即吞吐量=运行用户代码时间/(运行用户代码时间 + 垃圾收集时间)
- 这种情况下, 应用程序能容忍较高的暂停时间, 因此, 高吞吐量的应用程序有更长的时间基准
- 吞吐量优先, 意味着在单位时间内, STW的时间最短.
3. 暂停时间(pause time)
- 暂停时间是指一个时间段内应用程序线程暂停, 让GC线程执行的状态.
- 暂停时间优先, 一位置尽可能让单次STW的时间最短
4. 吞吐量VS暂停时间
-
高吞吐量较好因为这会让应用程序的最终用户感觉只有应用程序线程在做"生产性"工作. 直觉上, 吞吐量越高程序运行越快
-
低暂停时间(低延迟)较好因为从最终用户的角度来看不管时GC还是其他原因导致一个应用被挂起始终是不好的. 因此, 具有低的较大暂停时间是非常重要的, 特别是对于一个交互式应用程序
-
不幸的是. "高吞吐量"和"低暂停时间"是一对相互竞争的目标.
如果选择以吞吐量优先, 那么必然需要降低内存回收的执行频率, 但这样会导致GC需要更长的暂停时间来执行内存回收.
如果选择以低延迟优先为原则, 那么为了降低每次执行内存回收的暂停时间, 只能频繁地执行内存回收, 但这又引起了来来取代内存的缩减和导致程序吞吐量的下降.
-
现在标准: 在最大吞吐量优先的情况下, 降低停顿时间
2. 不同的垃圾收集器概述
1. 垃圾收集器发展史
2. 7款经典的垃圾收集器
-
串行回收器
Serial, Serial Old
-
并行回收器
ParNew, Parallel Scavenge, Parallel Old
-
并发回收器
CMS, G1
3. 关系
-
收集器与垃圾分代之间的关系
-
垃圾收集器的组合关系
3. Serial回收器: 串行回收
1. 说明
-
Serial收集器是最基本, 历史最悠久的垃圾收集器了. JDK1.3之前收集新生代唯一的选择
-
Serial收集器作为HotSpot中Client默认下的默认新生代垃圾收集器
-
Serial收集器采用复制算法, 串行回收和"Stop-the-world"机制的方式执行内存回收.
-
除了年轻代之外, Serial收集器还提供用于执行老年代垃圾收集器的Serial Old收集器. Serial Old收集器同样也采用了串行回收和"Stop the World"机制, 只不过内存回收算法使用的是标记-压缩算法.
-
Serial Old是运行在Client模式下默认的老年代的垃圾回收器
-
Serial Old在Server模式下主要有两个用途:
① 与新生代的Parallel Scavenge配合使用
② 作为老奶奶带CMS收集器的后备垃圾收集方案
-
-
这个收集器是一个单线程的收集器, 但它的"单线程"的意义并不仅仅说明它只会使用一个CPU或一条收集线程区完成垃圾收集工作, 更重要的是它进行垃圾收集时, 必须暂停其他所有的工作线程, 直到它收集结束(Stop the World)
-
优势: 与其他收集器的单线程比, 简单而高效
运行在Client模式下的虚拟机时个不错的选择
-
在HotSpot虚拟机中, 使用
-XX:+UseSerialGC
参数可以指定年轻代和老年代都是用串行收集器等价于: 新生代用Serial GC, 且老年代用 Serial Old GC
4. ParNew回收器: 并行回收
1. 说明
-
如果Serial GC是年轻代中的单线程垃圾收集器, 那么ParNew收集器则是Serial收集器的多线程版本
-
ParNew收集器除了采用并行回收的方式执行内存回收外, 两款垃圾收集器之间机会没有任何区别. ParNew收集器在年轻代中同样也是采用复制算法, "Stop-the-World"机制
-
ParNew 是很多JVM运行在Server模式下新生代的默认垃圾收集器
-
对于新生代, 回收次数频繁, 使用并行方式高效
-
对于老年代, 回收次数少, 使用串行方式节省资源
-
由于ParNew收集器是基于并行回收, 那么是否可以断定ParNew收集器的回收效率在任何场景下都会比Serial收集器更高效?
- ParNew收集器运行在多CPU的环境下, 由于可以充分利用多CPU, 多核心等物理硬件资源优势, 可以更快速地完成垃圾收集, 提升程序的吞吐量
- 在单个CPU的环境下, ParNew收集器不比Seerial收集器更高效.
-
除Serial外, 目前只有ParNew GC能与CMS收集器配合工作
-
在程序中, 开发人员可以通过选项
-XX:+UseParNewGC
手动指定使用ParNew收集器执行内存回收任务. 它表示年轻代使用并行收集器, 不影响老年代 -
-XX:ParallelGCThreads
限制线程数量, 默认开启和CPU数据相同的线程数
5. Parallel 回收器: 吞吐量优先
1. 说明
-
HotSpot的年轻代中除了拥有ParNew收集器是基于并行回收的以外, Parallel Scavenge收集器同样也采用了复制算法, 并行回收 和 "Stop the World"机制
-
和ParNew收集器不同, Parallel Scavenge收集器的目标是达到一个可控制的吞吐量(Throughput), 它也被称为吞吐量优先的垃圾收集器
-
自适应调节策略也是Parallel Scavenge与ParNew一个重要区别
-
高吞吐量则可以高效率地利用CPU时间, 尽快完成程序的运算任务, 主要适合在后台运算而不需要太多交互的任务. 因此, 常见在服务器环境中使用
-
Parallel收集器在JDK1.6时提供了用于执行老年代垃圾收集的Parallel Old收集器, 用来代替老年代Serial Old收集器
-
Parallel Old收集器采用了标记-压缩算法, 但同样也是基于并行回收和"Stop-the-World"机制
-
在程序吞吐量优先的应用场景中, Parallel收集器和Parallel Old收集器的组合, 在Server模式下的内存回收性能很不错
-
在JDK8中, 默认时此垃圾收集器
2. 参数设置
-XX:+UseParallelGC
: 手动指定年轻代使用Parallel并行收集器执行内存回收任务-XX:+UseParallelOldGC
: 手动指定老年代都是使用并行收集器.- 分别适用于新生代和老年代. 默认jdk8是开启的
- 上面两个参数, 默认开启一个, 另一个也会被开启. (相互激活)
-XX:ParallelGCThreads
: 设置年轻代并行收集器的线程数. 一般地, 最好与CPU数量相等, 以避免过多的线程数影响垃圾收集性能-XX:MaxGCPauseMillis
: 设置垃圾收集器最大停顿时间(即STW的时间), 单位是毫秒- 为了尽可能地把停顿时间控制在MaxGCPauseMills以内, 收集器在工作时会调整Java堆大小或者其他一些参数
- 对于用户来讲, 停顿时间越短体验越好. 但是在服务器, 我们注重高并发, 整体的吞吐量. 所以服务器端适合Parallel, 进行控制
- 该参数使用需谨慎
-XX:GCTimeRatio
: 垃圾收集十进占总时间的比例(= 1 / (N + 1)), 用于衡量吞吐量的大小- 取值范围(0, 100). 默认值99, 也就是垃圾回收时间不超过1%
- 与前一个
-XX:MaxGCPauseMillis
参数有一定矛盾性. 暂停时间越长, Ratio参数就越容易超过设定的比例.
-XX:+UseAdaptiveSizePolicy
: 设置Parallel Scavenge收集器具有自适应调节策略- 在这种模式下, 年轻代的大小, Eden和Survivor的比例, 晋升老年代的对象年龄等参数会被自动调整, 以达到在堆大小, 吞吐量和停顿时间之间的平衡点.
- 在手动调优比较困难的场合, 可以直接使用这种自适应的方式, 仅指定虚拟机的最大堆, 目标的吞吐量(GCTimeRatio)和停顿时间(MaxGCPauseMills), 让虚拟机自己完成调优工作
6. CMS回收器: 低延迟
1. 说明
-
在jdk1.5时期, HotSpot推出了一款在强交互应用中几乎可认为有划时代意义的垃圾收集器:CMS(Concurrent-Mark-Sweep)收集器, 这款收集器是HotSpot虚拟机中第一款真正意义上的并发收集器, 它第一次实现了让垃圾收集线程与用户线程同时还工作
-
CMS收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间.
-
CMS的垃圾收集算法采用标记-清除算法, 并且也会"Stop-the-World"
-
不幸的是, CMS作为老年代的收集器, 却无法与JDK1.4.0中已经存在的新生代收集器Parallel Scavenge配合工作, 所以在JDK1.5中使用CMS来收集老年代的时候, 新生代只能选择ParNew或者Serial收集器中的一个
-
在G1出现之前, CMS使用非常广泛.
2. CMS的工作原理
CMS整个过程比之前的收集器要复制, 整个过程分为4个主要阶段, 即初始标记阶段, 并发标记阶段, 重新标记阶段和并发清除阶段.
-
初始标记(Initial-Mark)阶段
在这个阶段中, 程序中所有的工作线程都将会因为"Stop-the-World"机制而出现短暂的暂停, 这个阶段的主要任务仅仅只是标记除GC Roots能直接关联到的对象. 一旦标记完成之后就会恢复之前暂停的所有应用线程. 由于直接关联对象比较小, 所以这里的速度非常块.
-
并发标记(Concurrent-Mark)阶段
从GC Roots的直接关联对象开始遍历整个对象图的过程, 这个过程耗时较长但是不需要停顿用户线程, 可以和垃圾收集线程一起并发运行
-
重新标记(Remark)阶段
由于在并发标记阶段中, 程序的工作线程会和垃圾收集线程同时运行或者交叉运行, 因此为了修正并发标记期间, 因为用户程序继续运作而导致标记产生变动的那部分对象的标记记录, 这个阶段段的停顿时间通常会比初始标记阶段稍长一些, 但也远比并发标记阶段的时间短.(这个阶段也需要STW)
怀疑是垃圾, 然后再次确认
-
并发清除(Concurrent-Sweep)阶段
此阶段清除删除掉标记阶段判断的已经死亡的对象, 释放内存空间. 由于不需要移动存活对象, 所以这个阶段也是可以与用户线程同时并发的.
3. 低延迟
4. 优点
- 并发收集
- 低延迟
5. 弊端
-
会产生内存碎片
-
CMS收集器堆CPU资源非常敏感
-
CMS收集器无法处理浮动垃圾
重新标记阶段是将之前怀疑是垃圾的部分对象再次确认, 但是无法清除原先不是垃圾, 但是在重新标记阶段编程垃圾的部分对象, 这部分对象称为浮动垃圾
6. 参数设置
7. Serial GC, Parallel GC和CMS GC不同点?
- 如果想要最小化地使用内存和并发开销, 选择Serial GC
- 如果想要最大化应用程序的吞吐量, 选择Parallel GC
- 如果想要最小化GC的中断或停顿时间, 选择 CMS GC
8. JDK后续版本中CMS的变化
7. G1回收器: 区域化分代式
1. 说明
1. 为什么发布Garbage First(G1)
- 应用程序所应对的业务越来越庞大, 复杂, 用户越来越多, 没有GC就不能博爱正应用程序正常进行,而教程造成STW的GC又跟不上实际的需求, 所以才会不断地尝试对GC进行优化. G1垃圾回收器实在java7 update 4之后引入的一个新的垃圾回收器, 是当今收集器技术发展的最前沿成果之一.
- 为了适应现在不断扩大的内存和不断增加的处理器数量, 进一步降低暂停时间, 同时兼顾良好的吞吐量.
- G1的目标是在延迟可控的情况下获得尽可能高的吞吐量, 所以才担当起"全功能收集器"的重任与期望.
2. 为什么名字叫G1
3. 概念
- G1是一款面向服务端应用的垃圾收集器, 主要针对配备多核CPU及大容量内存的机器, 以极高概率满足GC停顿时间的同时, 还兼具高吞吐量的性能特征
- 在jdk1.7版本正式启用, 是jdk9以后的默认垃圾回收器, 取代了CMS回收器以及Parallel + Parallel Old组合. 被Oracle官方称为"全功能的垃圾收集器"
- 与此同时, CMS已经在JDK 9中被标记为废弃. 在jdk8中还不是默认的垃圾回收器, 需要使用
-XX:+Use G1GC
来启用.
2. 优缺点
1. 优点
-
与其他GC收集器相比, G1使用了全新的分区算法, 其特点如下
2. 缺点
3. 参数设置
4.操作步骤
G1的设计原则就是简化JVM性能调优, 开发人员只需要简单的三步即可完成调优
- 开启G1垃圾收集器
- 设置堆的最大内存
- 设置最大的停顿时间
G1中提供了三种垃圾回收模式: YoungGC, Mined GC和Full GC, 在不同的条件下被触发.
5. 分区Refion: 化整为零
6. G1回收器垃圾回收过程
1. 说明
2. Remembered Set
3.G1回收过程一: 年轻代GC
4. G1回收过程二: 并发标记
5. G1回收过程三: 混合回收
6. G1回收可选的过程四: Full GC
7. G1回收过程: 补充
8. G1回收器优化建议
8. 垃圾回收器总结
1. 对比
-
根据具体的情况选用不同的垃圾收集器
-
GC发展阶段
Serial => Parallel(并行) => CMS(并发) => G1 => ZGC
2. 垃圾回收器组合
-
不同厂商, 不同版本的虚拟机实现差别很大. HotSpot虚拟机在JDK7/8后所有收集器及组合(连线)
9. GC日志分析
1. 内存分配与垃圾回收的参数列表
-
-XX:+PrintGC
: 输出GC日志. 类似:-verbose:gc
-
-XX:+PrintGCDetails
: 输出GC详细日志 -
-XX:+PrintGCTimeStamps
: 输出GC的时间戳(以基准时间的形式) -
-XX:+PrintGCDateStamps
: 输出GC的时间戳(以日期的形式) -
-XX:+PrintHeapAtGC
: 在进行GC的前后打印出堆的信息 -
-Xloggc:../logs/gc.log
: 日志文件的输出路径
2. 日志补充说明
-
“GC"和"Full GC"说明了这次垃圾收集的停顿类型, 如果有"Full"则说明GC发生了"STW”
-
使用Serial收集器在新生代的名字是Defaul New Generation, 因此显示的是"DefNew"
-
使用ParNew收集器在新生代的名字是Parallel New Generation, 因此显示的是: “ParNew”
-
使用Parallel Scavenge收集器在新生代的名字是"PSYoungGen"
-
老年代的收集和新生代道理一样, 名字也是收集器决定的
-
使用G1收集器的话, 会显示为"garbage-first heap"
-
Allocation Failure
表明本次引起GC的原因是因为在年轻代中没有足够的空间能够存储新的数据了
-
[PSYoungGen: 5986K->696K(8704K)] 5986K->704K(9216K)
中括号内: GC回收前年轻代大小, 回收后大小, (年轻代总大小)
括号外: GC回收前年轻代和老年代大小, hi售后大小, (年轻代和老年代总大小)