阿里三面被虐惨,为什么GC需要Stop the World?

最近,五哥回忆起4年前在蚂蚁金服三面的经历。关于GC的一个问题,让我记忆深刻。

当聊起来Java GC时,我提到 young gc 和 full gc都会 Stop the world。

”为什么需要 Stop the world“,蚂蚁面试官问道。

我略微怔住,想了一会,回答道:“如果一边垃圾回收,业务线程一边跑,可能清理的不干净吧,也可能有一些对象被错误回收”。 事实上,我没背过这个八股文,只能凭感觉瞎说。本以为这个问题就这么糊弄过去了。

“你可以举例说明下,为什么GC需要Stop the world吗?举个例子说明下?” ,蚂蚁面试继续追问道。

这个追问让我措手不及,一脸懵逼。想了几分钟后,半天也没放出个屁…… 支支吾吾没答出来,只得坦白这个问题没有想清楚,我不会。

大家都明白,为什么需要Stop the world,但要举一个实际例子来证明这一点有难度,就像大家都了解快速排序的原理,但手写非递归版本的快速排序真的很困难。

后来的我花了很久,才想到两个例子来反证这个结论。在此之前,我先啰嗦一下,故事的背景……


故事的背景

2019年的春天,刚毕业两年的我,在忙着换工作,那时候的求职环境还不像今天这么寒冷,大公司的面试机会有很多,即便如此,我也非常珍惜大公司的面试机会,尤其是蚂蚁金服。那一天,我坐地铁10号线,去朝阳区蚂蚁金服的办公点——环球金融中心 现场面试,在我看来,这个大楼的名字和蚂蚁金服四个字一样,非常高大上。算上电话面试,今天是第三面,如果面试通过,不出意外的话,后面没有技术面了,应该就能拿到Offer。 心想着,终于可以拿到一个满意的Offer了, 于是既兴奋又紧张的走进了蚂蚁金服。

我没有想到,两个小时后,我会灰溜溜的出来~

当时的我准备得十分充分,我先用小公司、中型公司的面试机会练手,像蚂蚁金服类的大公司留到最后再面试,目的是一击必中,不留遗憾。在当时,我已经有十几轮的面试经验了,自我介绍和项目介绍背的滚瓜烂熟,常见八股文,简单leetCode完全难不倒我。甭管实力如何,至少在心理上我十分自信。

然而蚂蚁金服的面试官从浅入深,在各方面盘问我的技术实力。我的印象是:他们从一个点开始问,一直问到底,问到我不会为止,我一度接近崩溃……。这些问题我努力回忆了一下,可以 # 点击查看8家大厂后端面试题

为什么需要Stop the world

比较官方的解释如下

分析工作必须在一个能确保一致性的快照中进行

一致性指整个分析期间整个执行系统像被冻结在某个时间点上

如果出现分析过程中对象引用关系还在不断地变化,则分析结果的准确性无法保证。

官方给的解释中,重点在强调,GC的分析要确保在一个一致性的视图之上,否则无法保证垃圾回收的准确性。这和我说的几乎一样,“有一些对象可能会被错误回收”,只不过官方的说法更加专业。但是官方并没有给出具体的例子…… 这需要我们自己探索。

垃圾回收算法中的标记工作

在Java堆中,存放着所有Java的对象实例。在进行垃圾收集之前,JVM需要确定哪些对象已经不被使用(即垃圾),哪些对象仍然被使用。为了判断对象是否是“垃圾”,JVM采用了可达性分析算法。

可达性分析算法 是指通过指定 GC Root 根对象,从根对象开始搜索引用的对象,通过引用链条,层层遍历链条上的对象,可以到达的对象不可被垃圾回收。而最终没有被搜索遍历到的对象,则为 不可达对象,应该被垃圾回收。

JVM中的 GC Root根对象包括如下:

  1. 虚拟机栈引用的对象
  2. 本地方法栈内JNI(本地方法)引用的对象
  3. 方法区中常量引用的对象(字符串常量池)
  4. 所有被同步锁synchronized持有的对象
  5. Java虚拟机内部的引用

垃圾回收必须要先标记出垃圾对象,才可以进行后续的清理工作。无论是采用 标记-整理算法还是标记-清理算法。标记工作都是必不可少的。

如上文指出,JVM 明确标记工作进行时,业务线程必须暂停执行!

下面我提出两个例子说明下,如果业务线程没有被暂停,会造成什么后果!

使用反证法证明,为什么需要Stop the world

接下来,我通过一段代码,证明这个结论: 如果不暂停业务线程,对象会被错误的垃圾回收!

以上代码中,声明了 target static静态常量,引用了Context类型对象。因为被常量引用,target在GC Root上。也就是说垃圾回收时,会以target为根,开始遍历。正常情况下target引用的对象不会被回收…… 但如果不暂停业务线程后,Context对象会被错误回收!

在main方法中一共有 4 步。

首先 第一步:定义 temp变量为 null;

第二步:将target引用赋值给 temp;

第三步:将target 变量指向null,此时Context对象只被 temp 变量引用。

最后一步第四步,调用temp.toString();

接下里我开始分析,假设开启垃圾回收时,不暂停业务线程,垃圾回收线程和业务线程一起并发执行,会有哪些潜在的坑点!

为了清晰期间,我使用表格来表示时间线。在此例中,两个线程为 main 线程和垃圾回收标记线程。

通过上图的分析,我们发现,原Context对象,在被其他变量引用的情况下,被错误的垃圾回收,造成了不可预测的情况发生。

原因是,当垃圾回收线程检查 main 线程时,发现无法通过 main 线程的虚拟机栈引用 Context 对象。这是因为 temp 还未被赋值。于是,垃圾回收线程转而遍历 target 变量。在此时间窗口内,main 线程从 target 变量中获取到 Context 对象的引用,并将 target 变量设置为null。回到垃圾回收线程,它检查到 target 变量为null。在垃圾回收线程看来,无论是main线程还是 target 变量,都没有引用到 Context 对象。因此,垃圾回收器回收了 Context 对象。然而,main 线程中的 temp 变量仍然持有 Context 对象。在这种情况下,对 Context 对象的任何操作都将变得不可预测。

这个过程略微有些混乱,可以通过参考时间线表格来更好地理解。

此时再去理解 JVM官方文档给的原因

分析工作必须在一个能确保一致性的快照中进行,一致性指整个分析期间整个执行系统像被冻结在某个时间点上

正是因为 main 线程和垃圾回收线程同时执行,导致垃圾回收在分析 main 线程的虚拟机栈和target变量时,并没有在一个冻结的时间上,而是在先后的两个时间点分析对象是否可达。在先后时间的窗口期内,Context 对象被赋值给其他对象,但是垃圾回收线程对此毫无感知…… 最终当 Context 被错误回收以后,业务线程访问 Context时,将出现极其诡异且致命的问题……

通过反证法,我们证明出,如果不暂停业务线程,那么无法进行准确的垃圾回收工作。

其他例子

还有其他例子可以佐证。

例如只有两行代码 的一段程序。

Context temp = null;

temp = new Context();`

main 线程中,创建一个新的 Context对象,此时只有 temp 变量持有 Context对象,恰好垃圾回收线程在遍历 main 线程的 虚拟机栈时, temp变量还为null;

于是在垃圾回收标记完成后,发现Context对象不可达,于是被当成垃圾回收了……

如果不暂停业务线程,在垃圾回收期间新创建的对象,有可能会被错误的回收掉,这真的太可怕了。

之所以可能出现这么离谱的现象, 原因就在于,业务线程和垃圾回收线程并行执行,谁也无法预知 垃圾标记的工作和 引用关系的变化 谁先谁后。

只有当业务线程被暂停,才能保证垃圾回收的标记是准确的

假如业务线程被暂停,还会有问题吗?

第二个例子中,假设在 第一行代码后 Context temp = null; 业务线程被暂停。由于 Context对象还未创建,所以不会有对象被回收。

假设在 第二行代码执行后,被暂停。由于 main线程 temp 变量还持有 Context对象引用,所以 Context对象不会被垃圾回收。

回到第一个表格的代码,当main线程被暂停后,无论被暂停在 哪一行代码,垃圾税收标记工作都不会出现任何问题。读者可以自行假设论证一下。

总结

通过 列举两个反例,通过反证法证明 业务线程必须被暂停,才可以进行垃圾回收标记工作。

4年前的面试,我被问到这个问题时,我的破解思路是,有两个业务线程,互相修改引用关系,垃圾回收判断垃圾对象,会出现错误。但是两个业务线程的场景,实在复杂,我无法举出实际的例子。其实 只需要一个main线程 和垃圾线程对比分析,就能说明问题,根本不需要两个业务线程来证明这个问题。

一个线程尚且出问题,由此可见,业务逻辑千奇百怪,当存在上千个业务线程时,如果不暂停业务线程,就进行垃圾回收,该多么可怕!

一般情况下,在聊GC时,没有面试官会深入到 为什么需要Stop the world 这类问题,但是阿里的面试官独辟蹊径,成功的卷到我。

这个问题虽然看起来简单,但真要现场举例说明,还是有难度的!

我当时没有回答上来这个问题,一度以为被挂掉,但最终这轮技术面试还是通过了…… 也是一个惊喜

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,273评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,349评论 3 398
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,709评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,520评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,515评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,158评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,755评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,660评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,203评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,287评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,427评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,122评论 5 349
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,801评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,272评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,393评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,808评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,440评论 2 359

推荐阅读更多精彩内容