困扰了你大半辈子的STW,今天总算可以毕业了

简介: 最近在看GC那块的源码,想把之前遗留的一些疑惑给整明白。恰好今天在群里看到有小伙伴在问:看了无数的资料,还是觉得STW好抽象啊,谁能告诉我STW到底是什么?择日不如撞日吧,就写篇文章告诉大家STW到底长什么样子。GC时一定会谈到的一个概念:安全点,又是什么?

hello,小伙伴们好,我是江湖人送外号[道格牙]的子牙老师。

最近在看GC那块的源码,想把之前遗留的一些疑惑给整明白。恰好今天在群里看到有小伙伴在问:看了无数的资料,还是觉得STW好抽象啊,谁能告诉我STW到底是什么?择日不如撞日吧,就写篇文章告诉大家STW到底长什么样子。GC时一定会谈到的一个概念:安全点,又是什么?

问题分析

STW,即Stop The World。

为什么需要STW呢?试想你妈给你打扫房间的场景:把你撵出去,关上门,打扫干净,打开门,数落你,揍你…一套标准化流程后,房间干净了。打完你,你妈的心情变好了,打麻将都能多赢点。这里面有个关键环节:把你撵出去。尽管在打扫方面的过程中你可能不会制造垃圾,但是你的存在就有这个风险,所以必须把你撵出去。这话不是我说的,是从你妈的行为中揣摩出来的。^_^

试想,如果不把你撵出去,你妈打扫垃圾的同时,你又陆陆续续制造了垃圾,那这场打扫房间的行动是不是变成了无法结束的行动啊。或者到某个时间点,你妈打扫了一半走了,丢下一句话:朽木不可雕也,孺子不可教也。

垃圾收集器也是一样的,为了保证清理垃圾的完整性,在某些环节,就会STW。比如所有垃圾收集器中都有的一个阶段:初始阶段,即扫描根对象,需要STW。小伙伴门看过的几乎所有资料,讲到这基本就没了对吧。但这不是子牙老师我的风格,咱们接着往后面说。

STW

JVM中要做到STW是很难的。为什么这么说呢?因为需要考虑很多很多因素。

一、JVM中存在多种类型的会发生改变内存行为的线程:

  1. 执行业务逻辑的用户线程
  2. 执行native方法的Java线程
  3. 执行垃圾收集的GC线程(并行并发垃圾收集需要考虑)
  4. 执行即时编译的JIT线程

二、每种类型的线程个数,在需要STW的那一刻,可能都不止一个。

三、每种类型的线程,在需要STW的那一刻,执行到的代码位置也未可知。

四、每种类型的线程阻塞的点还不能随机。因为线程在阻塞前需要更新OopMap。OopMap是什么?你可以理解成是记录这个线程一路跑下来经历过的所有Java对象的集合。为什么要有OopMap呢?因为没有的话,你就得扫描整个栈,去查找根对象。这里说的只是查找根对象的一种情况哈,勿抬杆,我会记仇。^_^

如何暂停线程

听我这么一分析,好像确实很复杂哈。那如果是你来实现,你会怎么解决呢?小伙伴门可以想一想。经常想这样有深度的问题,有利于提高你的思考深度。我们还是来看看JVM是如何高明地解决的吧。

如果线程随便哪个位置阻塞都合适,这个问题就会简单一百倍。但是这里简单了,给其他地方就带来了灾难。就是说线程阻塞前需要更新OopMap,如果不更新,没有这个数据的话,GC时就需要扫描所有线程的所有栈的所有栈帧来查找根对象。OopMap的存在,其实又是一种空间换时间的策略。因为相比内存的价格,降低GC延时明显更重要。

但是JVM的执行流那么多,何时?在什么地方?更新OopMap呢?这就是安全点存在的意义。安全点同时解决了STW及更新OopMap。其实也可以这样说,不理解安全点就无法理解STW,甚至于无法理解GC。

安全点

安全点涉及的知识点非常多、非常底层。本篇文章就讲安全点中与STW相关的知识点。其他的知识点后面会写系列文章展开讲。感兴趣的小伙伴可以关注我公众号关注我的发文动态:硬核子牙。

这段代码是大家看GC源码时经常看到的
image.png

我把hotspot源码中核心的代码粘过来
image.png

这段代码到底做了哪些事情呢:

  1. 告诉JVM马上要开始GC(下雨)了,开始做准备工作了(准备收衣服了)。本质就是修改一些属性位。比如第5行代码,通知解释器做好准备工作,迎接GC到来。
  2. 将polling_page对应的物理页设置成不可读状态。这步非常非常重要。等下说。
  3. 不停检测,确定是否所有的线程都已进入安全点。只有都已进入安全点,才能执行GC逻辑。

STW的真面目

安全点是如何解决让所有的线程都阻塞的呢?开启安全点为什么要将物理页的属性改为不可读呢?

因为JVM在生成执行流代码的时候,都会在适合作为安全点的地方插入一段代码
image.png

这段代码就是安全点的本质,也是触发STW的本质。什么意思呢?如果os::_polling_page对应的物理页属性是可读的,这段代码并没什么特殊意义。但是如果是不可读的,读的时候就会触发段异常,对应的操作系统信号:SIGSEGV。JVM捕获了这个异常,并进行了处理。所有的线程都是在这个地方STW的。
image.png

这就是安全点难的地方,涉及到的知识点太多太底层!其实我搞手写JVM小班的核心目的不是带你写一个JVM,其一是让你通过手写JVM了解hotspot的体系,你才能看得懂hotspot源码。其二,也是最核心的,掌握底层。因为掌握了底层,你对技术就没有恐惧之心了,你会觉得你无所不能。事实上,相对的无所不能是可以做到的,只是需要时间沉淀。啰嗦了两句哈。

GC结束后唤醒所有阻塞的线程,小伙伴们应该能想到是在哪里?如何唤醒的了吧
image.png

结语

我是子牙老师,喜欢钻研底层,深入研究Windows、Linux内核、JVM。喜欢分享硬核知识,如果你也喜欢研究底层,喜欢硬核知识,关注我。

相关文章
|
3月前
|
Java 编译器 Linux
JVM/编译器/CPU,究竟谁是卧底?一个曾经困扰我一个月的 bug
任何复杂的系统都可能因为一个小小的疏漏而无法运转,本文记录了一个困扰作者一个月的 bug 最终拨云见日的过程。
|
3月前
|
存储 算法 Oracle
不好意思!耽误你的十分钟,JVM内存布局还给你
先赞后看,南哥助你Java进阶一大半在2006年加州旧金山的JavaOne大会上,一个由顶级Java开发者组成的周年性研讨会,公司突然宣布将开放Java的源代码。于是,下一年顶级项目OpenJDK诞生。Java生态发展被打开了新的大门,Java 7的G1垃圾回收器、Java 8的Lambda表达式和流API…大家好,我是南哥。一个Java学习与进阶的领路人,相信对你通关面试、拿下Offer进入心心念念的公司有所帮助。
不好意思!耽误你的十分钟,JVM内存布局还给你
|
6月前
|
中间件 程序员 Go
更文的快乐又体会到了 解决了困扰读者半天的问题
更文的快乐又体会到了 解决了困扰读者半天的问题
|
安全 NoSQL 程序员
我就算跳出去死外边也不会学【实用调试技巧/程序员内功修炼】
我就算跳出去死外边也不会学【实用调试技巧/程序员内功修炼】
37 1
|
JavaScript 算法 前端开发
阿秀思考了很久,最后还是决定啦!
五月份离职后,我给自己放了半个月的假才去入职的新公司,emm,截止目前为止暂时还没后悔,如果以后真的发生北京hulu那样被一锅端的事就算我倒霉好了。
76 1
|
大数据 程序员 开发者
程序员有周末吗?
程序员有周末吗?
|
编译器 程序员 C语言
重生之我要学C++第二天
重生之我要学C++第二天
101 0
|
监控 算法 Java
花了好几个晚上整理的JVM知识点,吐血献出(一)(下)
花了好几个晚上整理的JVM知识点,吐血献出(一)(下)
153 0
花了好几个晚上整理的JVM知识点,吐血献出(一)(下)
|
存储 缓存 安全
花了好几个晚上整理的JVM知识点,吐血献出(一)(上)
花了好几个晚上整理的JVM知识点,吐血献出(一)
121 0
花了好几个晚上整理的JVM知识点,吐血献出(一)(上)
|
IDE Java API
脑袋抽筋了的我非要调试OpenJdk,且看他怎么虐我
前言 说来话长,最近又闲的无聊,在看JDK的源码,但是很多关键的地方都是native方法,这就导致需要在往深处看,也就是需要看openjdk源码了,但是c++代码又谈何容易,况且也不怎么会。 但是想来想去,决定还是要研究一下的,在以前的文章中已经编译过了openjdk11,虽然过程坎坷,但也是成功了,那么接下来就是导入openjdk源码到ide中,在下面会以clion作为示例,clion下载安装就不说了。 激动的心,颤抖的手,Deepin下成功编译OpenJdk11!!! 光这导入就花了我1天,毕竟openjdk很复杂,而且参考的文章有的说只导入hotspot目录,有的说导入openjd
脑袋抽筋了的我非要调试OpenJdk,且看他怎么虐我