Java:重复调用函数会导致性能提升的原因是什么?

时间:2012-12-30 20:41:37

标签: java performance garbage-collection instrumentation jvm-hotspot

当我遇到一个奇怪的结果时,我正在做一些关于对象分配的性能测试。我有以下java代码:

public static long TestMethod(){
    int len = 10000000;
    Object[] obs = new Object[len];
    long t = System.nanoTime();
    for (int i = 0; i < len; i++) {
        obs[i] = new Object();
    }
    return System.nanoTime() - t;
}

public static void main(String... args) throws InterruptedException {
    for(int i = 0; i < 10; i++){
        System.gc();
        System.gc();//Wait for the gc to be finished
        Thread.sleep(1000);
        System.out.println(TestMethod());
    }
}

期望: 由于请求操作系统和热点增强的更大内存空间,第一次调用将比第二次调用慢。但第二和第三将几乎相同。

观察结果

11284734000
799837000
682304000
304736000
380770000
392786000
374279000
381611000
379174000
407256000

第三次和第四次迭代之间仍然相当快。是什么导致这种加速?在测试其他功能时,如何确保测量结果准确无误,在测量之前是否需要调用该功能四次以上?

2 个答案:

答案 0 :(得分:4)

  

导致此加速的原因是什么?

很可能是JIT编译,但也可能是代码加载和/或堆热效应的贡献。

  

在测试其他功能时,如何确保测量结果准确无误,在测量之前是否需要调用该功能四次以上?

你需要做那样的事情。没有其他方法可以消除测量中的JVM预热效果,并且仍然可以获得具有代表性的结果。为Java编写有效的“微基准”很困难,您需要在尝试之前阅读所有问题。从这开始:How do I write a correct micro-benchmark in Java?


我还会注意到其他一些事情:

  1. 您尝试从测量中删除垃圾收集的成本(我认为这就是您的目标)出现失败。在执行testMethod期间,您似乎收到了较少的收藏。这将导致“稳态”结果变化约为7%。

  2. 将分配对象的成本与释放对象的成本分开可能会给您带来误导性的结果。分配对象的“总”成本包括在回收时将内存归零的成本......这是由垃圾收集器完成的。

  3. 实际上,最有用的衡量指标是分配/收集周期的每个对象的摊余成本。而且(令人惊讶地)取决于GC运行时的非垃圾量......这是您的基准测试未考虑的因素。

答案 1 :(得分:2)

如您所述,有多种因素可以加快功能的执行:

  • 大多数JIT可能会在您申请期间的不同深度或时刻发生(这就是为什么在没有给JVM足够时间进行预热的情况下进行分析会导致误导性结果)
  • 对操作系统的内存请求
  • 在堆上为您正在分配的对象重用内存

您无法确定每个步骤何时发生,并且您无法知道何时将代码段编译为本机代码,但假设这在第一次调用不正确之前发生,则可能恰好在时间丢失时发生在第三次和第四次迭代之间或其他什么。不幸的是,这些细节隐藏在JVM实现中。