Thread.sleep(0)阻止GC引起的等待

看到一段出自 RocketMQ 的源码

在这里插入图片描述

问题来了,Thread.sleep(0)能阻止GC?

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

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

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

所以为什么想要尝试“触发”GC 呢?

答案:safepoint,安全点。

关于安全点的描述,我们可以看看《深入理解JVM虚拟机(第三版)》的 3.4.2 小节:
在这里插入图片描述
换言之:没有到安全点,是不能 STW,从而进行 GC 的。

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

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

里面有这样一段话:
在这里插入图片描述
意思就是在可数循环(Counted Loop)的情况下,HotSpot 虚拟机搞了一个优化,就是等循环结束之后,线程才会进入安全点。

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

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

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

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

原理就是让其变为不可数循环(Uncounted Loop),从而不用等循环结束,在循环期间就能进入 Safepoint。
在这里插入图片描述
这个循环也是一个可数循环。

sleep和Safepoint的关系

Safepoint源码解释https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/tip/src/share/vm/runtime/safepoint.cpp

When returning from the native code, a Java thread must check the safepoint _state to see if we must block
意思就是一个线程在运行 native 方法后,返回到 Java 线程后,必须进行一次 safepoint 的检测。

在这里插入图片描述
因此调用 sleep 方法的线程会进入 Safepoint
在这里插入图片描述
回看代码就可以发现,每迭代1000次进入safepoint,避免等循环结束。

举例

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);
    }
}

在这里插入图片描述
主线程一直在等待 t1,t2 执行结束才继续执行。

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

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

1.启动了两个长的、不间断的循环(内部没有安全点检查)。
2.主线程进入睡眠状态 1 秒钟。
3.在1000 ms之后,JVM尝试在Safepoint停止,以便Java线程进行定期清理,但是直到可数循环完成后才能执行此操作。
4.主线程的 Thread.sleep 方法从 native 返回,发现安全点操作正在进行中,于是把自己挂起,直到操作结束。

使用Thread.sleep(0)

在这里插入图片描述
主线程无需等待。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值