炸了!一口气问了我18个JVM问题!(下)

简介: 炸了!一口气问了我18个JVM问题!(下)

cms 和 G1 为了维持并发的正确性分别用了什么手段?


之前文章分析到了并发执行漏标的两个充分必要条件是:

  1. 将新对象插入已扫描完毕的对象中,即插入黑色对象到白色对象的引用。
  2. 删除了灰色对象到白色对象的引用。

cms 和 g1 分别通过增量更新和 SATB 来打破这两个充分必要条件,维持了 GC 线程与应用线程并发的正确性。

cms 用了增量更新(Incremental update),打破了第一个条件,通过写屏障将插入的白色对象标记成灰色,即加入到标记栈中,在 remark 阶段再扫描,防止漏标情况。

G1 用了 SATB(snapshot-at-the-beginning),打破了第二个条件,会通过写屏障把旧的引用关系记下来,之后再把旧引用关系再扫描过。

这个从英文名词来看就已经很清晰了。讲白了就是在 GC 开始时候如果对象是存活的就认为其存活,等于拍了个快照。

而且 gc 过程中新分配的对象也都认为是活的。每个 region 会维持 TAMS (top at mark start)指针,分别是 prevTAMS 和 nextTAMS 分别标记两次并发标记开始时候 Top 指针的位置。

Top 指针就是 region 中最新分配对象的位置,所以 nextTAMS 和 Top 之间区域的对象都是新分配的对象都认为其是存活的即可。


image.png

而利用增量更新的 cms 在 remark 阶段需要重新所有线程栈和整个年轻代,因为等于之前的根有新增,所以需要重新扫描过,如果年轻代的对象很多的话会比较耗时。

要注意这阶段是 STW 的,很关键,所以 CMS 也提供了一个 CMSScavengeBeforeRemark 参数,来强制 remark 阶段之前来一次 YGC。

而 g1 通过 SATB 的话在最终标记阶段只需要扫描 SATB 记录的旧引用即可,从这方面来说会比 cms 快,但是也因为这样浮动垃圾会比 cms 多。


什么是 logging write barrier ?


写屏障其实耗的是应用程序的性能,是在引用赋值的时候执行的逻辑,这个操作非常的频繁,因此就搞了个 logging write barrier。

把写屏障要执行的一些逻辑搬运到后台线程执行,来减轻对应用程序的影响

在写屏障里只需要记录一个 log 信息到一个队列中,然后别的后台线程会从队列中取出信息来完成后续的操作,其实就是异步思想。

像 SATB write barrier ,每个 Java 线程有一个独立的、定长的 SATBMarkQueue,在写屏障里只把旧引用压入该队列中。满了之后会加到全局 SATBMarkQueueSet。

image.png

后台线程会扫描,如果超过一定阈值就会处理,开始 tracing。

在维护记忆集的写屏障也用了 logging write barrier 。


简单说下 G1 回收流程


G1 从大局上看分为两大阶段,分别是并发标记和对象拷贝。

并发标记是基于 STAB 的,可以分为四大阶段:

1、初始标记(initial marking),这个阶段是 STW 的,扫描根集合,标记根直接可达的对象即可。在G1中标记对象是利用外部的bitmap来记录,而不是对象头。

2、并发阶段(concurrent marking),这个阶段和应用线程并发,从上一步标记的根直接可达对象开始进行 tracing,递归扫描所有可达对象。 STAB 也会在这个阶段记录着变更的引用。

3、最终标记(final marking), 这个阶段是 STW 的,处理 STAB 中的引用。

4、清理阶段(clenaup),这个阶段是 STW 的,根据标记的 bitmap 统计每个 region 存活对象的多少,如果有完全没存活的 region 则整体回收。

对象拷贝阶段(evacuation),这个阶段是 STW 的。

根据标记结果选择合适的 reigon 组成收集集合(collection set 即 CSet),然后将 CSet 存活对象拷贝到新 region 中。

G1 的瓶颈在于对象拷贝阶段,需要花较多的瓶颈来转移对象。


简单说下 cms 回收流程


其实从之前问题的 CollectorState 枚举可以得知几个流程了。

1、初始标记(initial mark),这个阶段是 STW 的,扫描根集合,标记根直接可达的对象即可。

2、并发标记(Concurrent marking),这个阶段和应用线程并发,从上一步标记的根直接可达对象开始进行 tracing,递归扫描所有可达对象。

3、并发预清理(Concurrent precleaning),这个阶段和应用线程并发,就是想帮重新标记阶段先做点工作,扫描一下卡表脏的区域和新晋升到老年代的对象等,因为重新标记是 STW 的,所以分担一点。

4、可中断的预清理阶段(AbortablePreclean),这个和上一个阶段基本上一致,就是为了分担重新标记标记的工作。

5、重新标记(remark),这个阶段是 STW 的,因为并发阶段引用关系会发生变化,所以要重新遍历一遍新生代对象、Gc Roots、卡表等,来修正标记。

6、并发清理(Concurrent sweeping),这个阶段和应用线程并发,用于清理垃圾。

7、并发重置(Concurrent reset),这个阶段和应用线程并发,重置 cms 内部状态。

cms 的瓶颈就在于重新标记阶段,需要较长花费时间来进行重新扫描。


cms 写屏障又是维护卡表,又得维护增量更新?


卡表其实只有一份,又得用来支持 YGC 又得支持 CMS 并发时的增量更新肯定是不够的。

每次 YGC 都会扫描重置卡表,这样增量更新的记录就被清理了。

所以还搞了个 mod-union table,在并发标记时,如果发生 YGC 需要重置卡表的记录时,就会更新  mod-union table 对应的位置。

这样 cms 重新标记阶段就能结合当时的卡表和  mod-union table 来处理增量更新,防止漏标对象了。


GC 调优的两大目标是啥?


分别是最短暂停时间和吞吐量

最短暂停时间:因为 GC 会 STW 暂停所有应用线程,这时候对于用户而言就等于卡顿了,因此对于时延敏感的应用来说减少 STW 的时间是关键。

吞吐量:对于一些对时延不敏感的应用比如一些后台计算应用来说,吞吐量是关注的重点,它们不关注每次 GC 停顿的时间,只关注总的停顿时间少,吞吐量高。

举个例子:

方案一:每次 GC 停顿 100 ms,每秒停顿 5 次。

方案二:每次 GC 停顿 200 ms,每秒停顿 2 次。

两个方案相对而言第一个时延低,第二个吞吐高,基本上两者不可兼得。

所以调优时候需要明确应用的目标


GC 如何调优


这个问题在面试中很容易问到,抓住核心回答。

现在都是分代 GC,调优的思路就是尽量让对象在新生代就被回收,防止过多的对象晋升到老年代,减少大对象的分配。

需要平衡分代的大小、垃圾回收的次数和停顿时间

需要对 GC 进行完整的监控,监控各年代占用大小、YGC 触发频率、Full GC 触发频率,对象分配速率等等。

然后根据实际情况进行调优。

比如进行了莫名其妙的 Full GC,有可能是某个第三方库调了 System.gc。

Full GC 频繁可能是 CMS GC 触发内存阈值过低,导致对象分配不过来。

还有对象年龄晋升的阈值、survivor 过小等等,具体情况还是得具体分析,反正核心是不变的。


最后


其实还有关于 ZGC 的内容没有分析,别急, ZGC 的文章已经写了一半了,之后会发。

有关 GC 的问题在面试中还是很常见的,其实来来回回就那么几样东西,记得我提到的抓住核心即可。

当然如果你有实际调优经历那更可,所以要抓住工作中的机会,如果发生异常情况请积极参与,然后勤加思考,这可都是实打实的实战经历。

当然如果你想知道更多的 GC 细节那就看源码吧,源码之中无秘密。

个人能力有限,如果有纰漏的地方请抓紧联系我,也欢迎私信联系我


巨人的肩膀


segmentfault.com/a/119000002…

blogs.oracle.com/jonthecolle…

www.iteye.com/blog/user/r… R大的博客

www.jianshu.com/u/90ab66c24… 占小狼的博客




相关文章
|
存储 缓存 运维
JVM面试连环炮
JVM面试连环炮
111 0
|
4月前
|
Arthas 监控 算法
JVM成神路终章:深入死磕Java虚拟机序列总纲
JVM成神路终章:深入死磕Java虚拟机序列总纲
113 1
|
5月前
|
存储 Java 程序员
老程序员分享:Java虚拟机详解(九)
老程序员分享:Java虚拟机详解(九)
24 0
|
6月前
|
存储 算法 Java
[JVM] 美团二面,说一下JVM数据区域
[JVM] 美团二面,说一下JVM数据区域
|
6月前
|
安全 Java 程序员
牛皮了!八年美团大佬耗时3月竟在写《java虚拟机并发编程》
除了咖啡因,我想没有什么能比写出一段执行速度飞快的代码更能令程序员们兴奋了。然而我们如何才能满足这种对计算速度的渴求呢?诚然,摩尔定律可以帮我们解决部分问题,但多核处理器才代表了未来真正的发展方向。
|
存储 算法 Java
JVM 中的垃圾回收算法有啥门道吗?
JVM 中的垃圾回收算法有啥门道吗?
71 0
|
存储 监控 架构师
炸了!一口气问了我 18 个 JVM 问题
# 前言 GC 对于Java 来说重要性不言而喻,不论是平日里对 JVM 的调优还是面试中的无情轰炸。 这篇文章我会以一问一答的方式来展开有关 GC 的内容。 本文章所说的 GC 实现没有特殊说明的话,默认指的是 HotSpot 的。 我先将十八个问题都列出来,如果都清楚的话那就可以关闭这篇文章了。
237 0
炸了!一口气问了我 18 个 JVM 问题
|
存储 算法 安全
《我想进大厂》之JVM夺命连环10问
这是面试专题系列第五篇JVM篇。这一篇可能稍微比较长,没有耐心的同学建议直接拖到最后。
《我想进大厂》之JVM夺命连环10问
|
Java
烧点脑子使劲看--JVM运行时数据区详讲(上)
Java虚拟机在执行程序的过程会把它管理的内存划分为若干个不同的数据区。这些数据区有些是随着虚拟机进程的启动而一直存在的,有些区域则是依赖线程的启动和结束而创建和销毁的。
98 0
|
存储 Java
炸了!一口气问了我18个JVM问题!(中)
炸了!一口气问了我18个JVM问题!(中)
炸了!一口气问了我18个JVM问题!(中)