没有二十年功力,写不出 Thread.sleep(0) 这一行“看似无用”的代码

news2024/11/13 16:25:35

这篇文章要从一个奇怪的注释说起,就是下面这张图:

我们可以不用管具体的代码逻辑,只是单单看这个 for 循环。

在循环里面,专门有个变量 j,来记录当前循环次数。

第一次循环以及往后每 1000 次循环之后,进入一个 if 逻辑。

在这个 if 逻辑之上,标注了一个注释:prevent gc.

prevent,这个单词如果不认识的同学记一下,考试肯定要考的:

这个注释翻译一下就是:防止 GC 线程进行垃圾回收。

具体的实现逻辑是这样的:

核心逻辑其实就是这样一行代码:

Thread.sleep(0);

这样就能实现 prevent gc 了?

懵逼吗?

懵逼就对了,懵逼就说明值得把玩把玩。

这个代码片段,其实是出自 RocketMQ 的源码:

org.apache.rocketmq.store.logfile.DefaultMappedFile#warmMappedFile

事先需要说明的是,我并没有找到写这个代码的人问他的意图是什么,所以我只有基于自己的理解去推测他的意图。如果推测的不对,还请多多指教。

虽然这是 RocketMQ 的源码,但是基于我的理解,这个小技巧和 RocketMQ 框架没有任何关系,完全可以脱离于框架存在。

我给出的修改意见是这样的:

把 int 修改为 long,然后就可以直接把 for 循环里面的 if 逻辑删除掉了。

这样一看是不是更加懵逼了?

不要慌,接下来,我给你抽丝剥个茧。

另外,在“剥茧”之前,我先说一下结论:

  • 提出这个修改方案的理论立足点是 Java 的安全点相关的知识,也就是 safepoint。

  • 官方最后没有采纳这个修改方案。

  • 官方采没采纳不重要,重要的是我高低得给你“剥个茧”。

探索

当我知道这个代码片段是属于 RocketMQ 的时候,我想到的第一个点就是从代码提交记录中寻找答案。

看提交者是否在提交代码的时候说明了自己的意图。

于是我把代码拉了下来,一看提交记录是这样的:

我就知道这里不会有答案了。

因为这个类第一次提交的时候就已经包含了这个逻辑,而且对应这次提交的代码也非常多,并没有特别说明对应的功能。

从提交记录上没有获得什么有用的信息。

于是我把目光转向了 github 的 issue,拿着关键词 prevent gc 搜索了一番。

除了第一个链接之外,没有找到什么有用的信息:

而第一个链接对应的 issues 是这个:

github.com/apache/rock…

这个 issues 其实就是我们在讨论这个问题的过程中提出来的,也就是前面出现的修改方案:

也就是说,我想通过源码或者 github 找到这个问题权威的回答,是找不到了。

于是我又去了这个神奇的网站,在里面找到了这个 2018 年提出的问题:

stackoverflow.com/questions/5…

问题和我们的问题一模一样,但是这个问题下面就这一个回答:

这个回答并不好,因为我觉得没答到点上,但是没关系,我刚好可以把这个回答作为抓手,把差的这一点拉通对齐一下,给它赋能。

先看这个回答的第一句话:It does not(它没有)。

问题就来了:“它”是谁?“没有”什么?

“它”,指的就是我们前面出现的代码。

“没有”,是说没有防止 GC 线程进行垃圾回收。

这个的回答说:通过调用 Thread.sleep(0) 的目的是为了让 GC 线程有机会被操作系统选中,从而进行垃圾清理的工作。它的副作用是,可能会更频繁地运行 GC,毕竟你每 1000 次迭代就有一次运行 GC 的机会,但是好处是可以防止长时间的垃圾收集。

换句话说,这个代码是想要“触发”GC,而不是“避免”GC,或者说是“避免”时间很长的 GC。从这个角度来说,程序里面的注释其实是在撒谎或者没写完整。

不是 prevent gc,而是对 gc 采取了“打散运行,削峰填谷”的思想,从而 prevent long time gc。

但是你想想,我们自己编程的时候,正常情况下从来也没冒出过“这个地方应该触发一下 GC”这样想法吧?

因为我们知道,Java 程序员来说,虚拟机有自己的 GC 机制,我们不需要像写 C 或者 C++ 那样得自己管理内存,只要关注于业务代码即可,并没有特别注意 GC 机制。

那么本文中最关键的一个问题就来了:为什么这里要在代码里面特别注意 GC,想要尝试“触发”GC 呢?

先说答案:safepoint,安全点。

关于安全点的描述,我们可以看看《深入理解 JVM 虚拟机(第三版)》的 3.4.2 小节:

注意书里面的描述:

有了安全点的设定,也就决定了用户程序执行时并非在代码指令流的任意位置都能够停顿下来开始垃圾收集,而是强制要求必须执行到达安全点后才能够暂停。

换言之:没有到安全点,是不能 STW,从而进行 GC 的。

如果在你的认知里面 GC 线程是随时都可以运行的。那么就需要刷新一下认知了。

接着,让我们把目光放到书的 5.2.8 小节:由安全点导致长时间停顿。

里面有这样一段话:

我把划线的部分单独拿出来,你仔细读一遍:

是 HotSpot 虚拟机为了避免安全点过多带来过重的负担,对循环还有一项优化措施,认为循环次数较少的话,执行时间应该也不会太长,所以使用 int 类型或范围更小的数据类型作为索引值的循环默认是不会被放置安全点的。这种循环被称为可数循环(Counted Loop),相对应地,使用 long 或者范围更大的数据类型作为索引值的循环就被称为不可数循环(Uncounted Loop),将会被放置安全点。

意思就是在可数循环(Counted Loop)的情况下,HotSpot 虚拟机搞了一个优化,就是等循环结束之后,线程才会进入安全点。

反过来说就是:循环如果没有结束,线程不会进入安全点,GC 线程就得等着当前的线程循环结束,进入安全点,才能开始工作。

什么是可数循环(Counted Loop)?

书里面的这个案例来自于这个链接:

juejin.cn/post/684490…HBase 实战:记一次 Safepoint 导致长时间 STW 的踩坑之旅

如果你有时间,我建议你把这个案例完整的看一下,我只截取问题解决的部分:

截图中的 while(i < end) 就是一个可数循环,由于执行这个循环的线程需要在循环结束后才进入 Safepoint,所以先进入 Safepoint 的线程需要等待它。从而影响到 GC 线程的运行。

所以,修改方案就是把 int 修改为 long。

原理就是让其变为不可数循环(Uncounted Loop),从而不用等循环结束,在循环期间就能进入 Safepoint。

接着我们再把目光拉回到这里:

这个循环也是一个可数循环。

Thread.sleep(0) 这个代码看起来莫名其妙,但是我是不是可以大胆的猜测一下:故意写这个代码的人,是不是为了在这里放置一个 Safepoint 呢,以达到避免 GC 线程长时间等待,从而加长 stop the world 的时间的目的?

所以,我接下来只需要找到 sleep 会进入 Safepoint 的证据,就能证明我的猜想。

你猜怎么着?

本来是想去看一下源码,结果啪的一下,在源码的注释里面,直接找到了:

hg.openjdk.java.net/jdk8u/jdk8u…

注释里面说,在程序进入 Safepoint 的时候, Java 线程可能正处于框起来的五种不同的状态,针对不同的状态有不同的处理方案。

本来我想一个个的翻译的,但是信息量太大,我消化起来有点费劲儿,所以就不乱说了。

主要聚焦于和本文相关的第二点:Running in native code。

When returning from the native code, a Java thread must check the safepoint _state to see if we must block.

第一句话,就是答案,意思就是一个线程在运行 native 方法后,返回到 Java 线程后,必须进行一次 safepoint 的检测。

同时我在知乎看到了 R 大的这个回答,里面有这样一句,也印证了这个点:

www.zhihu.com/question/29…

那么接下来,就是见证奇迹的时刻了:

根据 R 大的说法:正在执行 native 函数的线程看作“已经进入了 safepoint”,或者把这种情况叫做“在 safe-region 里”。

sleep 方法就是一个 native 方法,你说巧不巧?

所以,到这里我们可以确定的是:调用 sleep 方法的线程会进入 Safepoint。

另外,我还找到了一个 2013 年的 R 大关于类似问题讨论的帖子:

hllvm-group.iteye.com/group/topic…

这里就直接点名道姓的指出了:Thread.sleep(0).

这让我想起以前有个面试题问:Thread.sleep(0) 有什么用。

当时我就想:这题真难(S)啊(B)。现在发现原来是我道行不够,小丑竟是我自己。

还真的是有用。

实践

前面其实说的都是理论。

这一部分我们来拿代码实践跑上一把,就拿我之前分享过的《真是绝了!这段被JVM动了手脚的代码!》文章里面的案例。

public class MainTest {
    public static AtomicInteger num = new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {        Runnable runnable=()->{            for (int i = 0; i < 1000000000; i++) {                num.getAndAdd(1);            }            System.out.println(Thread.currentThread().getName()+"执行结束!");        };
        Thread t1 = new Thread(runnable);        Thread t2 = new Thread(runnable);        t1.start();        t2.start();        Thread.sleep(1000);        System.out.println("num = " + num);    }}复制代码

复制代码

这个代码,你直接粘到你的 idea 里面去就能跑。

按照代码来看,主线程休眠 1000ms 后就会输出结果,但是实际情况却是主线程一直在等待 t1,t2 执行结束才继续执行。

这个循环就属于前面说的可数循环(Counted Loop)。

这个程序发生了什么事情呢?

  • 1.启动了两个长的、不间断的循环(内部没有安全点检查)。

  • 2.主线程进入睡眠状态 1 秒钟。

  • 3.在 1000 ms 之后,JVM 尝试在 Safepoint 停止,以便 Java 线程进行定期清理,但是直到可数循环完成后才能执行此操作。

  • 4.主线程的 Thread.sleep 方法从 native 返回,发现安全点操作正在进行中,于是把自己挂起,直到操作结束。

所以,当我们把 int 修改为 long 后,程序就表现正常了:

受到 RocketMQ 源码的启示,我们还可以直接把它的代码拿过来:

这样,即使 for 循环的对象是 int 类型,也可以按照预期执行。因为我们相当于在循环体中插入了 Safepoint。

另外,我通过不严谨的方式测试了一下两个方案的耗时:

在我的机器上运行了几次,时间上都差距不大。

但是要论逼格的话,还得是右边的 prevent gc 的写法。没有二十年功力,写不出这一行“看似无用”的代码!

额外提一句

再说一个也是由前面的 RocketMQ 的源码引起的一个思考:

这个方法是在干啥?

预热文件,按照 4K 的大小往 byteBuffer 放 0,对文件进行预热。

byteBuffer.put(i, (byte) 0);

为什么我会对这个 4k 的预热比较敏感呢?

去年的天池大赛有这样的一个赛道:

tianchi.aliyun.com/competition…

其中有两个参赛选大佬都提到了“文件预热”的思路。

我把链接放在下面了,有兴趣的可以去细读一下:

tianchi.aliyun.com/forum/postD…

tianchi.aliyun.com/forum/postD…

最后,谢谢你“点赞”、“评论”我的文章,给我满满的正反馈。谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/80000.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ssm+vue基本微信小程序的校园二手商城系统 计算机毕业设计

在当今社会的高速发展过程中&#xff0c;产生的劳动力越来越大&#xff0c;提高人们的生活水平和质量&#xff0c;尤其计算机科技的进步&#xff0c;数据和信息以人兴化为本的目的&#xff0c;给人们提供优质的服务&#xff0c;其中网上购买二手商品尤其突出&#xff0c;使我们…

211大数据专业大四学生,放弃字节转正,选择老家大型国企,听听他怎么说?...

点击上方 "大数据肌肉猿"关注, 星标一起成长点击下方链接&#xff0c;进入高质量学习交流群今日更新| 1052个转型案例分享-大数据交流群分享学习群一位大数据专业同学的秋招学习和求职经历&#xff0c;他是211大四学生&#xff0c;年初才开始学习&#xff0c;但还好赶…

181.基于Django的云文件存储使用方式——七牛云存储

1.文件云存储 1.1 概述 在Django项目中&#xff0c;用户上传的文件以及项目中使用的静态文件&#xff0c;默认读书存储本地&#xff0c;保存在服务器中&#xff0c;但是&#xff0c;其实我们也可以将他们保存在云存储中&#xff0c;譬如七牛云存储、阿里云存储、亚马逊云存储…

【网络安全】提防黑客来“敲门”

前言 互联网在给我们带来便捷高效的同时&#xff0c;也给一些不法分子提供了可乘之机。网络诈骗、窃取个人信息等花样层出不穷&#xff0c;骚扰电话、垃圾短信扰乱着我们的正常生活&#xff0c;使网络空间抹上一笔灰色。网络安全与每个人都息息相关&#xff0c;所以我们必须理…

Python测试进阶(三)

文章目录性能测试JMeter测试计划模拟并发结果分析分布式性能监控grafanaFluxPrometheus小结性能测试 为什么做性能测试&#xff1f;主要是解决这些问题 什么是性能测试 模拟多个用户的操作&#xff0c;看对服务器性能的影响 指标 TPS&#xff1a;transaction per secondRT&…

基于Kubeadm快速部署一个K8s集群

目录kubeadm概述安装要求准备环境安装kubelet、kubeadm、kubectl使用kubeadm引导集群下载各个机器需要的镜像初始化主节点安装网络组件常用shell命令测试kubernetes集群部署dashboardkubeadm概述 kubeadm是官方社区推出的一个用于快速部署kubernetes集群的工具。 这个工具能通…

索引创建、删除的sql语句

目录 创建索引 使用ALTER TABLE 语句创建索引 使用CREATE TABLE 语句创建索引 删除索引 使用ALTER TABLE 语句删除索引 使用DROP INDEX 语句删除索引 创建索引 1、创建表的同时&#xff0c;指定给某个字段创建索引&#xff08;name&#xff09; create table cat(id …

SaaS 产品的文档策略

作者&#xff1a;Vaijayanti Nerkar 和 Priya Shetye&#xff0c;BMC Software 几年前&#xff0c;当 BMC Software 决定进军 SaaS 市场时&#xff0c;该公司开始投资开发基于云的产品。20多年来&#xff0c;BMC Software 产品都是典型的本地产品&#xff0c;因此&#xff0c;…

2022.12.11-YOLOv5使用NCNN将模型部署到Android端教程(1)部署自己的训练模型到Android实现静态图片检测

文章目录1. 前言2. 模型转换2.1. NCNN2.1.1. 简介2.1.2. ncnn2.1.3. ncnn-android-yolov52.2. 项目准备2.2.1. 安装Android studio2.2.2. 下载解压源码2.3. 安卓源码重新编译2.3.1. 构建工程2.3.2. 修改源码2.3.2.1. 修改CMakeLists.txt中的路径2.3.2.2. 重新重新ysnc project2…

【华为上机真题 2022】相对开音节

&#x1f388; 作者&#xff1a;Linux猿 &#x1f388; 简介&#xff1a;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我&#xff0c;关注我&#xff0c;有问题私聊&#xff01; &…

【无需注册账号】只需两步, Ai Studio上也可以玩[ChatGPT]了

☆ 只需两步&#xff0c; Ai Studio上也可以玩[ChatGPT]了 ☆ 无需账号即可体验喽~ 体验地址 只需两步&#xff0c; Ai Studio上也可以玩[ChatGPT]了 文章目录☆ 只需两步&#xff0c; Ai Studio上也可以玩[ChatGPT]了 ☆0 Fork后进入项目应用中心体验☆本页面下方体验☆注意…

5G无线技术基础自学系列 | 勘测准备

素材来源&#xff1a;《5G无线网络规划与优化》 一边学习一边整理内容&#xff0c;并与大家分享&#xff0c;侵权即删&#xff0c;谢谢支持&#xff01; 附上汇总贴&#xff1a;5G无线技术基础自学系列 | 汇总_COCOgsta的博客-CSDN博客 基站在详细的勘测之前需要做的准备包括…

CN_UDP协议

文章目录UDP协议UDP概述UDP的首部格式UDP数据报封装入IP数据报UDP校验伪首部真首部UDP数据报处理例UDP vs TCPUDP协议 User Datagram Protocol - Wikipedia 1Attributes2Ports3UDP datagram structure4Checksum computation 4.1IPv4 pseudo header4.2IPv6 pseudo header 5Reli…

百度安全查询,查询网址是否存在百度安全风险的方法

如果网站被百度安全识别为风险网站&#xff0c;或者是提示该页面可能存在虚假信息&#xff0c;该页面可能已被非法篡改&#xff0c;那么就不妙了。 怎样才能知道自己的网是否存在百度安全风险&#xff1f; 查询网站百度安全的方法: 第一步、打开SEO综合查询工具 第二步、添加…

[附源码]Python计算机毕业设计SSM基于的校园失物招领平台(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

线程池 (通俗易懂)

线程池一、线程池是什么二、标准库中的线程池三、实现线程池3.1 思路与细节3.2 完整代码四、标准库里的构造方法一、线程池是什么 之前我们已经认识过"池"&#xff1a;String&#xff0c;字符串常量池&#xff1b;MySQL JDBC&#xff0c;数据库连接池(DataSource)……

【案例实践】EKMA曲线及大气O3来源解析

【查看原文】EKMA曲线及大气O3来源解析实践技术应用 目前&#xff0c;大气臭氧污染成为我国“十四五”期间亟待解决的环境问题。臭氧污染不仅对气候有重要影响&#xff0c;而且对人体健康、植物生长均有严重损害。为了高效、精准地治理区域大气臭氧污染&#xff0c;首先需要了…

【OpenCV学习】第14课:边缘检测与自定义线性滤波(卷积, Rebert算子, Sobel算子, 拉普拉斯算子)

仅自学做笔记用,后续有错误会更改 参考文章&#xff1a;http://t.zoukankan.com/whw1314-p-12007928.html 理论 卷积的概念&#xff1a; 在图像上使用卷积的目的&#xff1a;模糊图像&#xff0c; 提取边缘轮廓&#xff0c; 图像锐化等 卷积如何工作&#xff1a; 下边给出…

MATLB|基于燃料电池混合动力汽车双层凸优化

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清…

算法基础篇-01-时间复杂度和空间复杂度

1. 用什么表示算法的运行快慢&#xff1f; n 代表是算法里面的问题规模&#xff0c;n越大&#xff0c;运算越慢&#xff0c;比如n1和n100肯定是不一样的&#xff1b;机器不一样&#xff0c;相同的算法代码运行所消耗的时间也不一样&#xff1b; 2. 借用生活中的案例 类比生活中…