为什么VisualVM Sampler不提供有关CPU负载的完整信息(方法时间执行)?

fed*_*lov 15 profiler visualvm

问题是:VisualVM采样器按时间显示调用树.对于某些方法,采样器仅显示"自我时间",因此我看不出是什么使这种方法变慢.这是一个例子.
如何增加剖析的深度?

thk*_*ala 44

不幸的是,由于多种原因,采样分析器在进行深入分析时相当有限:

  • 采样器受采样周期的限制:例如,VisualVM目前的最小采样周期为20ms.现代处理器在那段时间内可以执行数百万条指令 - 当然,它足以调用几种简短的方法并从中返回.

    虽然一个明显的解决方案是减少采样周期,但这也会增加分析器对您的应用程序的影响,提供了不确定性原则的一个很好的例子.

  • 采样器很容易被内联代码混淆: JVM和任何体面的编译器都会内联琐碎和/或频繁调用的方法,从而将代码合并到调用者的代码中.采样分析器无法分辨每种方法的哪些部分实际属于它,哪些部分属于内联调用.

    在VisualVM的情况下,自我时间实际上包括方法任何内联代码的执行时间.

  • 采样器可能会被高级VM弄糊涂:例如,在现代JVM实现中,方法没有稳定的表示.想象一下例如以下方法:

    void A() {
        ...
        B();
        ...
    }
    
    Run Code Online (Sandbox Code Playgroud)

    当JVM启动时B()直接从字节码解释,因此花费了相当多的时间使得它对采样器可见.然后,在一段时间后,JVM决定B()优先选择它并将其编译为本机代码,从而使其更快.在又一段时间之后,JVM可能会决定内联调用B(),并将其代码合并到其中A().

    最好的情况是,采样分析器将显示首次运行的成本,然后任何后续运行的成本将包含在调用者花费的时间内.遗憾的是,这可能会使缺乏经验的开发人员混淆低估内联方法的成本.

    在最坏的情况下,可以将该成本分配给兄弟呼叫,而不是呼叫者.例如,我目前正在使用VisualVM分析应用程序,其中热点似乎ArrayList.size()方法.在我的Java实现中,该方法是一个简单的字段getter,任何JVM都应该快速内联.然而,剖析器显示它是一个主要的时间消费者,完全忽略了一堆附近的HashMap电话,这些电话显然要贵得多.

避免这些弱点的唯一方法是使用仪器分析器,而不是采样分析器.检测分析器(例如VisualVM中的Profiler选项卡提供的分析器)实质上记录了所选代码中的每个方法入口和出口.不幸的是,检测分析器对分析代码的影响相当大:

  • 它们围绕每个方法插入监视代码,这完全改变了JVM处理方法的方式.由于额外的代码,甚至简单的字段getter/setter方法也不会被内联,从而扭曲任何结果.探查器通常会尝试考虑这些更改,但并不总是成功.

  • 它们会导致配置文件代码大量减速,这使得它们完全不适合监视完整的应用程序.

由于这些原因,仪器分析器主要适用于分析已经使用其他方法(例如采样分析器)检测到的热点.通过仅检测一组选定的类和/或方法,可以将分析副作用限制为应用程序的特定部分.


Tom*_*rka 1

示例中没有任何错误。看起来就像updateInfoInDirection()调用new SequenceInfo()SequenceInfo.next()。“Self time”意味着时间花费在方法本身的代码中(updateInfoInDirection()在获取线程样本时该方法位于堆栈底部)。