Java 分配延迟似乎很高,为什么?

Java 分配延迟似乎很高,为什么?,java,latency,allocation,low-level,jvm-hotspot,Java,Latency,Allocation,Low Level,Jvm Hotspot,我有一个在低延迟环境中运行的(java)应用程序,它通常以大约600微秒(+/-100)的速度处理指令。当然,随着我们进一步深入到微秒空间,您会看到延迟成本发生了变化,现在我们注意到,其中2/3的时间用于分配2个核心域对象 基准测试已经将有问题的代码部分从现有的引用中分离出来,从字面上来说就是对象的构造,也就是说,基本上是一个引用负载(每个类中约15个)和几个新的列表,不过请参见下面关于这里测量的确切内容的注释 每一个都持续地需要约100微米,这是我无法解释的,我正在努力寻找原因。一个快速的基准

我有一个在低延迟环境中运行的(java)应用程序,它通常以大约600微秒(+/-100)的速度处理指令。当然,随着我们进一步深入到微秒空间,您会看到延迟成本发生了变化,现在我们注意到,其中2/3的时间用于分配2个核心域对象


基准测试已经将有问题的代码部分从现有的引用中分离出来,从字面上来说就是对象的构造,也就是说,基本上是一个引用负载(每个类中约15个)和几个新的列表,不过请参见下面关于这里测量的确切内容的注释

每一个都持续地需要约100微米,这是我无法解释的,我正在努力寻找原因。一个快速的基准测试表明,一个大小相似、充满字符串的对象需要大约2-3微米的时间才能重新创建,显然这种基准测试充满了困难,但认为它可能是一个有用的基准测试

这里有两个Q

  • 如何调查这种行为
  • 分配缓慢有什么解释
注意:涉及的硬件是Sun X4600s上的Solaris 10 x86,具有8*双核Opteron@3.2GHz

我们看到的东西包括

  • 检查PrintTLAB stats,会显示一些慢速alloc,因此那里应该没有争用
  • PrintCompilation表明,其中一段代码对JIT不友好,尽管Solaris在这里似乎有一些不寻常的行为(即,相对于现代linux,目前还没有与solaris10类似的linux版本)
  • 日志编译。。。分析起来有点困难,至少可以说这是一项正在进行的工作,到目前为止还没有什么明显的变化
  • JVM版本。。。6u6和6u14一致,尚未尝试6u18或最新的7
感谢您的任何想法

总结各种帖子上的评论,试图让事情更清楚
  • 我所度量的成本是创建对象的总成本,该对象是通过一个构建器(比如一个)构建的,其私有构造函数多次调用新的ArrayList以及设置对现有对象的引用。测量的成本包括设置生成器以及将生成器转换为域对象的成本
  • 编译(通过hotspot)有显著的影响,但仍然相对缓慢(在本例中,编译将从100微秒减少到~60微秒)
  • 在我的NaiveBenchmark上编译(通过热点)会将分配时间从~2毫秒减少到~300ns
  • 延迟不随young gen收集算法(ParNew或并行清除)而变化

内存分配可能会导致副作用。内存分配是否可能导致堆被压缩?您是否查看了内存分配是否导致GC同时运行


您是否已分别计算了创建新ArrayList所需的时间?

即使使用如此强大的硬件,在通用操作系统上运行的通用VM也可能无法保证微秒级的延迟。巨大的吞吐量是您所能期望的最好结果。如果你需要一个实时虚拟机(我说的是RTSJ等等),切换到实时虚拟机怎么样


…我的两分钱

因为你的问题更多的是关于如何着手调查问题,而不是“我的问题是什么”,我会坚持使用一些工具来尝试

这是一个非常有用的工具,可以更好地了解正在发生的事情以及发生的时间。它类似于DTrace,但它是一个纯java工具。在这一点上,我假设你知道DTrace,如果不知道的话,它也很有用,如果不是迟钝的话。这些将让您了解JVM和OS中正在发生的事情以及发生的时间


哦,在你原来的帖子里还有一件事需要澄清。你在做什么?我假设您使用的是CMS这样的低暂停采集器存在高延迟问题。如果是,您是否尝试过任何调整?

只是一些胡乱猜测:

据我所知,Java虚拟机处理短期对象的内存与处理长期对象的内存不同。在我看来,一个对象从一个函数本地引用变为全局堆中的引用将是一个大事件,这似乎是合理的。它现在必须由GC跟踪,而不是在函数退出时进行清理


或者,从一个引用到单个对象的多个引用必须更改GC记帐。只要一个对象有一个引用,就很容易清理。多个引用可能有引用循环和/或GC可能必须在所有其他对象中搜索引用。

当您多次重复同一任务时,您的CPU往往会非常高效地运行。这是因为缓存未命中时间和CPU预热并不是一个因素。也有可能您也没有考虑您的JVM热身时间

如果在JVM和/或CPU未预热时尝试相同的操作。你会得到完全不同的结果

试着做同样的事情,比如说25次(小于编译阈值)和在测试之间休眠(100)。您应该期望看到更高的次数,更接近您在实际应用程序中看到的次数

你的应用程序的行为会有所不同,但为了说明我的观点。我发现等待IO比单纯的睡眠更具破坏性

当您执行基准测试时,您应该尝试确保您正在比较同类

import java.io.*;
import java.util.Date;

/**
Cold JVM with a Hot CPU took 123 us average
Cold JVM with a Cold CPU took 403 us average
Cold JVM with a Hot CPU took 314 us average
Cold JVM with a Cold CPU took 510 us average
Cold JVM with a Hot CPU took 316 us average
Cold JVM with a Cold CPU took 514 us average
Cold JVM with a Hot CPU took 315 us average
Cold JVM with a Cold CPU took 545 us average
Cold JVM with a Hot CPU took 321 us average
Cold JVM with a Cold CPU took 542 us average
Hot JVM with a Hot CPU took 44 us average
Hot JVM with a Cold CPU took 111 us average
Hot JVM with a Hot CPU took 32 us average
Hot JVM with a Cold CPU took 96 us average
Hot JVM with a Hot CPU took 26 us average
Hot JVM with a Cold CPU took 80 us average
Hot JVM with a Hot CPU took 26 us average
Hot JVM with a Cold CPU took 90 us average
Hot JVM with a Hot CPU took 25 us average
Hot JVM with a Cold CPU took 98 us average
 */
public class HotColdBenchmark {
    public static void main(String... args) {
        // load all the classes.
        performTest(null, 25, false);
        for (int i = 0; i < 5; i++) {
            // still pretty cold
            performTest("Cold JVM with a Hot CPU", 25, false);
            // still pretty cold
            performTest("Cold JVM with a Cold CPU", 25, true);
        }

        // warmup the JVM
        performTest(null, 10000, false);
        for (int i = 0; i < 5; i++) {
            // warmed up.
            performTest("Hot JVM with a Hot CPU", 25, false);
            // bit cold
            performTest("Hot JVM with a Cold CPU", 25, true);
        }
    }

    public static long performTest(String report, int n, boolean sleep) {
        long time = 0;
        long ret = 0;
        for (int i = 0; i < n; i++) {
            long start = System.nanoTime();
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(baos);
                oos.writeObject(new Date());
                oos.close();
                ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
                Date d = (Date) ois.readObject();
                ret += d.getTime();
                time += System.nanoTime() - start;
                if (sleep) Thread.sleep(100);
            } catch (Exception e) {
                throw new AssertionError(e);
            }
        }
        if (report != null) {
            System.out.printf("%s took %,d us average%n", report, time / n / 1000);
        }
        return ret;
    }
}
import java.io.*;
导入java.util.Date;
/**
使用热CPU的冷JVM平均花费123美元
使用冷CPU的冷JVM平均花费403美元
使用热CPU的冷JVM平均花费314美元
使用冷CPU的冷JVM平均花费510美元
使用热CPU的冷JVM平均花费316美元
使用冷CPU的冷JVM平均花费514美元
使用热CPU的冷JVM平均花费315美元
使用冷CPU的冷JVM平均花费545美元
使用热CPU的冷JVM平均花费321美元
带冷CPU的冷JVM平均花费542 us