热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JVMsafepoint安全点和countedloop可数循环

JVMsafepoint安全点和countedloop可数循环前言authorJellyfishMIX-githubblog.jellyfishmix.comLICENSEGP




JVM safe point 安全点和 counted loop 可数循环

前言


  1. @author JellyfishMIX - github / blog.jellyfishmix.com
  2. LICENSE GPL-2.0
  3. 本文基于 jdk 8 写作。

问题引出

public class JvmCountedLoop {
private static AtomicInteger num = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
for (int i &#61; 0; i < 100_000_000; i&#43;&#43;) {
num.getAndAdd(1);
}
// safe point
System.out.println(Thread.currentThread().getName() &#43; "执行结束!");
};
Thread t1 &#61; new Thread(runnable);
Thread t2 &#61; new Thread(runnable);
t1.start();
t2.start();
long startTime &#61; System.currentTimeMillis();
Thread.sleep(1000);
// safe point
long endTime &#61; System.currentTimeMillis();
System.out.println("num &#61; " &#43; num);
System.out.format("consume time: %d ms\n", endTime - startTime);
}
}

输出结果&#xff1a;

image-20220908220335657

这段代码按照平常的思维&#xff0c;consume time 应该输出 1000 ms。但实际并没有按照期望输出 1000 ms&#xff0c;而是 3270 ms。

long startTime &#61; System.currentTimeMillis();
Thread.sleep(1000);
// safe point
long endTime &#61; System.currentTimeMillis();

JVM safe point 安全点

JVM 会在一些适当的位置插入安全点&#xff0c;当程序执行到安全点时&#xff0c;称为进入安全点。

Thread.sleep() 方法是一个 native 方法&#xff0c;在从 native 方法返回后&#xff0c;JVM 会在当前线程设置一个安全点&#xff0c;让线程进入这个安全点。

关于安全点的介绍可以看这篇文章&#xff1a;https://www.javatt.com/p/47329

简单地说&#xff0c;每一个线程进入安全点后&#xff0c;会检测一个标志位&#xff0c;来决定自己是否暂停执行。

请注意&#xff1a;


  1. 在本文中我们称其为 STW 标志位&#xff0c;请注意&#xff0c;这不是官方称呼。
  2. 我们为了方便理解&#xff0c;暂且称 STW 标志位为 true 时&#xff0c;线程会暂停执行。

当一个线程在 safe point 由于 STW 标志位为 true 而暂停时&#xff0c;需要等待一次 GC 执行后才能离开安全点。

图中主线程在进入 safe point 后&#xff0c;发现 STW 标志位为 true&#xff08;为什么 STW 标志位为 true 后面再分析&#xff09;&#xff0c;阻塞在安全点&#xff0c;需要等待一次 GC 执行后才能离开安全点。这也是为什么 consume time 多花了两秒钟。

为什么这一次 GC 等待的时间&#xff08;STW&#xff09;很久呢&#xff1f;因为 GC 触发的条件是所有线程都进入安全点后才会触发。


JVM counted loop 机制

除了主线程外&#xff0c;另外两个线程在 for 循环里兜圈呢&#xff0c;没有进入到安全点。另外两个线程在 for 循环中不会进入安全点的原因是&#xff0c;hotspot 的 counted loop 机制。

《深入理解JVM虚拟机(第三版)》5.2.8 小节&#xff1a;

img

简单总结一下&#xff0c;当 for 循环中索引值是 int 类型&#xff0c;称为 counted loop。hotsopt 不会在这个循环里放置 safe point。要等待 counted loop 结束后才会放置 safe point。

Runnable runnable &#61; () -> {
for (int i &#61; 0; i < 100_000_000; i&#43;&#43;) {
num.getAndAdd(1);
}
// safe point
System.out.println(Thread.currentThread().getName() &#43; "执行结束!");
};

破坏 counted loop

我们把 for 循环中的 int 改为 long&#xff0c;可以破坏 counted loop&#xff0c;变成 uncounted loop&#xff1a;

public class JvmCountedLoop {
private static AtomicInteger num &#61; new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Runnable runnable &#61; () -> {
for (long i &#61; 0; i < 100_000_000; i&#43;&#43;) {
num.getAndAdd(1);
}
// safe point
System.out.println(Thread.currentThread().getName() &#43; "执行结束!");
};
Thread t1 &#61; new Thread(runnable);
Thread t2 &#61; new Thread(runnable);
t1.start();
t2.start();
long startTime &#61; System.currentTimeMillis();
Thread.sleep(1000);
// safe point
long endTime &#61; System.currentTimeMillis();
System.out.println("num &#61; " &#43; num);
System.out.format("consume time: %d ms\n", endTime - startTime);
}
}

输出结果&#xff1a;

image-20220909001509582

可以看到&#xff0c;本次 consume time 耗时符合预期&#xff0c;1004 ms

4 ms 偏差解释一下&#xff1a;

线程苏醒后只是切换为了 Runnable 状态&#xff0c;还是要等操作系统调度&#xff0c;操作系统给分配 cpu 时间片才能运行。

比如操作系统就是过了 4 ms 才给分配 cpu 时间片&#xff0c;那就会有 4 ms 偏差。

在截图中&#xff0c;我们把 for 循环中的 int 改为 long&#xff0c;破坏 counted loop&#xff0c;变成 uncounted loop。此时 JVM 允许在循环中插入 safe point。

这样主线程在到达 safe point 后&#xff0c;发现 STW 标志位为 true&#xff0c;阻塞在安全点。很快其他线程也会到达 safe point&#xff0c;发生一次 GC&#xff0c;然后各线程离开安全点继续执行。


演示一个对比实验&#xff0c;把 sleep 睡觉时间调小

我们试试&#xff0c;把 sleep 睡觉的时间调小一些会怎么样。输出结果如下&#xff1a;

image-20220909003547936

consume time 10 ms。主线程没有被阻塞在安全点。

主线程从 sleep native 方法返回后进入 safe point&#xff0c;检查 STW 标志位&#xff0c;发现不为 true&#xff0c;离开安全点继续执行。

对实验结果解释一下原因&#xff1a;

应该是在 10 ms - 1000 ms 之间的某个时刻&#xff0c;JVM 做出了判断&#xff0c;要把 STW 标志位置为 true。

如果是 10 ms&#xff0c;sleep 结束&#xff0c;进入了安全点&#xff0c;但是此时 JVM 还没做出置 STW 标志位为 true 的决定。

因此主线程不会阻塞住。


演示另一个对比实验&#xff0c;把 for 循环次数调大

我们试试&#xff0c;把 for 循环次数调大一些会怎么样。输出结果如下&#xff1a;

image-20220909003709395

consume time 6564 ms。同样符合之前的结论&#xff0c;主线程被阻塞在安全点&#xff0c;等待 GC 执行后才能离开安全点。

而且由于 counted loop 花费的时间更长了&#xff0c;导致主线程要在安全点等更久&#xff0c;其他线程才能完成 counted loop&#xff0c;进入安全点。所有线程都到达安全点&#xff0c;主线程才等到 GC 触发&#xff0c;然后离开安全点继续执行。


JVM 何时决定把 STW 标志位置为 true

线程进入 safe point 后&#xff0c;不一定会阻塞&#xff0c;他只是去检测 STW 标志位&#xff0c;来决定自己是否要暂停。

STW 标志位是由 JVM 进行维护的&#xff0c;JVM 会在运行时做出判断&#xff0c;是否要将 STW 标志位置为 true。

JVM 何时决定置 STW 标志位为 true&#xff0c;准确判断标准目前未知。猜测&#xff1a;当 JVM 结合运行时环境&#xff0c;预估稍后一段时间内&#xff0c;是否会产生大量可回收对象&#xff0c;来决定是否要把 STW 标志位置为 true。比如存在较长时间的循环时&#xff0c;JVM 可能会认为将产生大量可回收对象&#xff0c;决定把 STW 标志位置为 true。来让各线程在下一次进入安全点时&#xff0c;进行一次 GC。

当然这是猜想&#xff0c;准确的原因由于笔者水平不够&#xff0c;暂不知道。


线上环境 counted loop 导致的问题

关于 counted loop 会导致其他线程进入安全点后等待 GC 时间过长&#xff0c;这种担忧在线上环境真的存在。

这篇文章是小米云技术官方发表的&#xff1a;HBase实战&#xff1a;记一次Safepoint导致长时间STW的踩坑之旅

image-20220909002609107

小米的工程师亲身实战过&#xff0c;总结的经验&#xff0c;解决方案有两种&#xff1a;


  1. UseCountLoopSafepoints JVM 参数&#xff0c;允许 counted loop 循环中插入安全点。
  2. for 循环 index 类型从 int 改成 long&#xff0c;破坏 counted loop&#xff0c;变成 uncounted loop&#xff0c;来允许在循环中插入安全点。






推荐阅读
  • 本文为Codeforces 1294A题目的解析,主要讨论了Collecting Coins整除+不整除问题。文章详细介绍了题目的背景和要求,并给出了解题思路和代码实现。同时提供了在线测评地址和相关参考链接。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文讨论了一个数列求和问题,该数列按照一定规律生成。通过观察数列的规律,我们可以得出求解该问题的算法。具体算法为计算前n项i*f[i]的和,其中f[i]表示数列中有i个数字。根据参考的思路,我们可以将算法的时间复杂度控制在O(n),即计算到5e5即可满足1e9的要求。 ... [详细]
  • NN,NearestNeighbor,最近邻KNN,K-NearestNeighbor,K最近邻KNN分类的思路:分类的过程其实是直接将测试集的每一个图片和训练集中的所有图片进行比 ... [详细]
  • 路径查找基础知识-动画演示
    这是教程教你建立路径查找算法的第一步。路径查找就是在两点之间查找最短路径的算法,你可以在很多地方应用,例如:玩家控制角色时通过点击设置目的地时,就需要用到。在开始前,我们需要明确一点:路径查找是在终点 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • SayIhaveabytearraywith100,000bytesinit.Iwanttoconverteachbyteintoitstextualrepre ... [详细]
author-avatar
绝恋love2502860291
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有