0 对象存活的判断
判断对象是否存活一般有两种方式:
- 引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,缺点是无法解决对象相互循环引用的问题。
- 可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
在Java语言中,GC Roots包括:
- 虚拟机栈中引用的对象。
- 方法区中类静态属性实体引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI引用的对象。
由于循环引用的问题,一般采用跟踪(可达性分析)方法
1 GC算法
GC常见指标:吞吐量、垃圾回收器、停顿时间、垃圾回收频率、反应时间
- 吞吐量
应用系统的生命周期内,应用程序所花费的时间和系统总运行时间的比值。
系统总运行时间=应用程序耗时+GC耗时。
如果系统运行了100分钟,GC耗时1分钟,则系统吞吐量=99%
- 垃圾回收器负载
垃圾回收器负载=GC耗时/系统总运行时间
- 停顿时间
垃圾回收器运行时,应用程序的暂停时间
- 垃圾回收频率
垃圾回收器多长时间运行一次。一般而言,频率越低越好,通常增大堆空间可以有效降低垃圾回收发生的频率,但是会增加回收时产生的停顿时间。
- 反应时间
当一个对象成为垃圾后,多长时间内,它所占用的内存空间会被释放掉。
1.1 引用计算法
- 优点
- 可即时回收垃圾
- 最大暂停时间短
- 没有必要沿着指针查找
- 缺点
- 计数器值的增减处理非常繁重
- 计算器需要占用很多位
- 循环引用无法回收
1.2 标记-清除
- 优点
- 实现简单
- 与保守式GC算法兼容
- 缺点
- 碎片化
- 分配速度慢
- 与写时复制技术不兼容
1.3 复制
复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法
- 优点
- 高吞吐量
- 不会碎片化
- 可高速分配
- 与缓存兼容
- 缺点
- 复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低
- 需要浪费50%的内存空间
1.4 标记-整理
标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
- 优点
- 弥补了标记-清除算法,内存区域分散的缺点
- 弥补了复制算法内存减半的代价
- 缺点
- 效率不高,对于标记-清除而言多了整理工作,对于复制算法而言多了标记工作
1.5 功能对比
- 效率:复制算法>标记整理>标记清除
- 内存整齐度:复制=标记整理>标记清除
- 内存使用率:标记整理=标记清除>复制
2 CMS回收器
CMS回收过程:初始标记=》并发标记=》重新标记=》并发清除
- 初始标记(CMS Initial Mark)—— 标记GC root能直接关联的对象(短暂STW)
- 并发标记(CMS Concurrent Mark)—— GCRootsTracing,从并发标记中的root遍历,对不可达的对象进行标记
- 重新标记(CMS Remark)—— 修正并发标记期间因为用户操作导致标记发生变更的对象,有STW
- 并发清除(CMS Concurrent Sweep)
3. G1回收器
G1回收过程:初始标记=》并发标记=》最终标记=》筛选回收
- 初始标记(Initial Mark)—— 标记GC root能直接关联的对象(短暂STW)
- 并发标记(Concurrent mark)—— GCRootsTracing,从并发标记中的root遍历,对不可达的对象进行标记,耗时长但可并行
- 最终标记(Final Remark)—— 收集并发标记期间产生的新垃圾(短暂STW),采用了SATB算法比CMS更快
- 筛选回收(Live Data Counting and Evacuation)—— 对各个Region的回收性价比排序,在保证时间可控的情况下清除失活对象,清除Remember Sets
4 CMS和G1对比
分代 | 并发度 | 清除算法、产生碎片 | 是否可预测停顿 | |
CMS | 老年代 (需要其他收集器配合) | CMS对CPU资源敏感,需要占用25%的线程,如果核数小于4更会占用一半的资源 | 标记-清除,会产生碎片 | 否 |
G1 | 老年代、年轻代 (不需要其他收集器配合) | 并发度更高,充分利用CPU多线程 | 整体上是标记-整理,局部是复制,不产生碎片 | 是 |
4.1 为什么CMS会产生浮动垃圾,而G1不会产 生浮动垃圾?
由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然会有新垃圾产生,这部分垃圾得标记过程之后,所以CMS无法在当收集中处理掉他们,只好留待下一次GC清理掉,这一部分垃圾称为浮动垃圾。
g1也同样会遇到浮动垃圾的问题,但相比于cms,g1采用了stab算法来解决浮动垃圾。stap全称snapshot-at-the-beginning,由Taiichi Yuasa为增量式标记清除垃圾收集器开发的一个算法,主要有以下几个特点:
- stab算法主要应用于垃圾收集的并发标记阶段
- stab算法保证了在并发标记过程中新分配对象不会漏标
- 解决了CMS垃圾收集器重新标记阶段长时间STW的潜在风险
4.2 为什么CMS会产生内存碎片,而G1不会产生内存碎片?
- CMS用的是标记清除算法,会产生内存碎片。
- G1整体上是标记-整理,局部是复制,不产生碎片。
4.3 为什么CMS是并发的而不是并行的?
为了解决因标记清除算法而会产生内存碎片的问题,CMS提供了-XX:UseCMSCompactAtFullCollection开发参数用于开启内存碎片的合并整理,由于内存整理是无法并行的,所以停顿时间会变长。还有-XX:CMSFullGCBeforeCompaction,这个参数用于设置多少次不压缩Full GC后,跟着来一次带压缩的(默认为0)。
4.4 为什么说CMS是对CPU资源敏感的?
CMS默认启动的回收线程数是(cpu数量+3)/ 4,所以CPU数量少会导致用户程序执行速度降低较多。