Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/391.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 没有对象分配的for循环如何导致JVM抖动?_Java_Jvm_Jvm Hotspot_Microbenchmark - Fatal编程技术网

Java 没有对象分配的for循环如何导致JVM抖动?

Java 没有对象分配的for循环如何导致JVM抖动?,java,jvm,jvm-hotspot,microbenchmark,Java,Jvm,Jvm Hotspot,Microbenchmark,我一直在对下面的代码进行微基准测试,我注意到了一些有趣的东西,我希望有人能提供更多的信息。它会导致这样一种情况,即for循环可以继续快速运行,同时阻塞JVM中的其他线程。如果这是真的,那么我想理解为什么,如果这不是真的,那么任何对我可能缺失的东西的洞察都将被感激 为了建立这种情况,让我带您浏览一下我正在运行的基准及其结果 代码非常简单,迭代数组中的每个元素,对其内容求和。重复“targetCount”次 public class UncontendedByteArrayReadBM extend

我一直在对下面的代码进行微基准测试,我注意到了一些有趣的东西,我希望有人能提供更多的信息。它会导致这样一种情况,即for循环可以继续快速运行,同时阻塞JVM中的其他线程。如果这是真的,那么我想理解为什么,如果这不是真的,那么任何对我可能缺失的东西的洞察都将被感激

为了建立这种情况,让我带您浏览一下我正在运行的基准及其结果

代码非常简单,迭代数组中的每个元素,对其内容求和。重复“targetCount”次

public class UncontendedByteArrayReadBM extends Benchmark {

private int arraySize;
private byte[] array;

public UncontendedByteArrayReadBM( int arraySize ) {
    super( "array reads" );

    this.arraySize = arraySize;
}

@Override
public void setUp() {
    super.setUp();

    array = new byte[arraySize];
}

@Override
public void tearDown() {
    array = null;
}

@Override
public BenchmarkResult invoke( int targetCount ) {
    long sum = 0;
    for ( int i=0; i<targetCount; i++ ) {
        for ( int j=0; j<arraySize; j++ ) {
            sum += array[j];
        }
    }

    return new BenchmarkResult( ((long)targetCount)*arraySize, "uncontended byte array reads", sum );
}

}
在这个问题的末尾,我已经包含了如何测量抖动的代码

然而,有趣的是,它确实发生在“JVM预热”期间,因此不是“正常”,但我想更详细地了解以下内容:

2.4519521584902644 uncontended byte array reads/ns  [maxJitter=2561.222ms totalTestRun=4078.383ms]
请注意,抖动超过2.5秒。通常我会把这记在GC上。但是,在测试运行之前,我确实触发了一个System.gc(),并且-XX:+PrintGCDetails此时不显示gc。事实上,在任何测试运行期间都没有GC,因为在对预先分配的字节进行求和的测试中几乎没有对象分配。每次我运行一个新的测试时,它都会发生,因此我并不怀疑它是来自随机发生的其他进程的干扰

我的好奇心猛增,因为当我注意到抖动非常高时,总的运行时间,以及每纳秒读取数组元素的次数几乎保持不变。因此,在这种情况下,一个线程在一个4核机器上严重滞后,而工作线程本身没有滞后,并且没有GC在进行

进一步调查后,我查看了Hotspot编译器的工作,并通过-XX:+PrintCompilation发现了以下内容:

2632   2%      com.mosaic.benchmark.datastructures.array.UncontendedByteArrayReadBM::invoke @ 14 (65 bytes)
6709   2%     made not entrant  com.mosaic.benchmark.datastructures.array.UncontendedByteArrayReadBM::invoke @ -2 (65 bytes)
打印出来的这两行之间的延迟约为2.5秒。当包含big for循环的方法将其优化代码标记为不再是进入者时,就正确了

我的理解是Hotspot在后台线程上运行,当它准备好交换新版本的代码时,它会等待已经运行的代码到达安全点,然后再交换。对于位于每个循环体末端的大for循环(可能已经展开了一些)。我不希望有2.5s的延迟,除非这个交换必须在JVM上执行stop the world事件。在对以前编译的代码进行去优化时,它会这样做吗

因此,我要问JVM内部专家的第一个问题是,我在这方面走对了吗?2.5秒的延迟是否是由于将该方法标记为“非参赛者”造成的;如果是这样,为什么它会对其他线程产生如此巨大的影响?如果这不太可能是原因,那么关于调查其他方面的任何想法都将是非常好的

(为了完整起见,下面是我用来测量抖动的代码)


抖动的原因有很多

  • 睡眠在毫秒级是非常不可靠的
  • 上下文开关
  • 打断
  • 由于运行其他程序而导致的缓存未命中
即使您忙着等待,将线程绑定到已隔离的cpu(例如,使用ISOCUS),并将所有中断从该cpu上移开,您仍然可以看到少量的抖动。你所能做的就是减少它


顺便说一句:你所做的就是测量系统的抖动。

这花了一些时间才找到冒烟的枪,但经验教训是有价值的;特别是如何证明和隔离原因。所以我觉得把它们记录在这里很好

JVM确实在等待执行停止世界事件。Alexey Ragozin在上有一篇关于这个话题的非常好的博客文章,这篇文章让我走上了正确的道路。他指出,安全点在JNI方法边界和Java方法调用上。因此,我这里的for循环中没有安全点

要理解Java中的停止世界事件,请使用以下JVM标志:
-XX:+PrintGCApplicationStoppedTime-XX:+PrintSafepointStatistics-XX:PrintSafepointStatisticsCount=1

第一个打印出停止世界活动的总持续时间,它不仅限于GC。在我的例子中,打印出:

Total time for which application threads were stopped: 2.5880809 seconds
这证明了我在线程等待到达安全点时遇到了问题。接下来的两个参数打印出JVM希望等待全局安全点到达的原因

         vmop                    [threads: total initially_running wait_to_block]    [time: spin block sync cleanup vmop] page_trap_count
4.144: EnableBiasedLocking              [      10          1              1    ]      [  2678     0  2678     0     0    ]  0   
Total time for which application threads were stopped: 2.6788891 seconds
这说明JVM在尝试启用有偏锁定时等待了2678ms。为什么这是一个世界性的活动?谢天谢地,马丁·汤普森过去也遇到过这个问题,他已经记录了这个问题。事实证明,Oracle JVM在启动期间有相当多的线程争用,在这段时间内,有偏差的锁定成本非常高,因此它会将优化延迟四秒钟。所以这里发生的事情是,我的微基准测试超过了4秒,然后它的循环中没有安全点。因此,当JVM试图打开偏向锁定时,它必须等待

所有对我有效的候选解决方案包括:

  • -XX:-使用偏置锁定(关闭偏置锁定)
  • -XX:BiasedLockingStartupDelay=0(立即启用偏置锁定)
  • 将循环更改为在其中有一个安全点(例如未优化或内联的方法调用)

  • 同意,这个建议在过去的十五年里对我很有帮助:)但现在我渴望更深入地理解。我知道在Java中测量时间会遇到相当大的时钟粒度问题和延迟,System.currentTimeMillis()的延迟可能高达16或32毫秒,这就是为什么我对常见的.4ms抖动不感兴趣的原因。与此相比,2.5秒是主要的。我目前认为上下文切换和中断来自外部事件
    Total time for which application threads were stopped: 2.5880809 seconds
    
             vmop                    [threads: total initially_running wait_to_block]    [time: spin block sync cleanup vmop] page_trap_count
    4.144: EnableBiasedLocking              [      10          1              1    ]      [  2678     0  2678     0     0    ]  0   
    Total time for which application threads were stopped: 2.6788891 seconds