问题是:VisualVM采样器按时间显示调用树。对于某些方法,采样器仅显示“自我时间”,因此我看不出是什么使这种方法变慢。 Here is an example。
如何增加剖析的深度?
答案 0 :(得分:44)
不幸的是,由于多种原因,采样分析器在进行深入分析时相当有限:
采样器受采样周期的限制:例如,VisualVM目前的最小采样周期为20ms。现代处理器在那段时间内可以执行数百万条指令 - 当然只需要调用几种简短的方法并从中返回。
虽然一个明显的解决方案是减少采样周期,但这也会增加探查器对您的应用程序的影响,提供uncertainty principle的一个很好的例子。
采样器容易被内联代码混淆: JVM和任何体面的编译器都会内联琐碎和/或频繁调用的方法,从而将代码合并到调用者的代码中。采样分析器无法分辨每种方法的哪些部分实际属于它,哪些部分属于内联调用。
对于VisualVM 自我时间实际上包括方法和任何内联代码的执行时间。
采样器可能会被高级VM弄糊涂:例如,在现代JVM实现中,方法没有稳定的表示。想象一下例如以下方法:
void A() {
...
B();
...
}
当JVM启动B()
时,直接从字节码解释,因此需要花费相当多的时间才能使采样器看到它。然后,在一段时间后,JVM决定B()
是优化的良好候选者,并将其编译为本机代码,从而使其更快。又过了一段时间,JVM可能会决定内联B()
的调用,将其代码合并到A()
。
最好的情况是,采样分析器将显示首次运行的成本,然后任何后续运行的成本将包含在调用者花费的时间内。不幸的是,这可能会让没有经验的开发人员误解低估内联方法的成本。
最糟糕的是,该费用可能会分配给兄弟电话,而不是来电者。例如,我目前正在使用VisualVM分析应用程序,其中热点似乎是ArrayList.size()
方法。在我的Java实现中,该方法是一个简单的字段getter,任何JVM都应该快速内联。然而,个人资料显示它是一个主要的时间消费者,完全忽略了一堆明显要贵得多的HashMap
电话。
避免这些弱点的唯一方法是使用仪器分析器,而不是采样分析器。检测分析器(例如VisualVM中 Profiler 选项卡提供的分析器)实质上记录了所选代码中的每个方法入口和出口。不幸的是,检测分析器对分析代码的影响相当大:
他们在每个方法周围插入监控代码,这完全改变了JVM处理方法的方式。由于额外的代码,甚至简单的字段getter / setter方法也不会被内联,从而扭曲任何结果。分析器通常会尝试考虑这些变化,但并不总是成功。
它们导致大量减速到配置文件代码,这使得它们完全不适合监视完整的应用程序。
由于这些原因,仪器分析器主要适用于分析已使用其他方法(例如采样分析器)检测到的热点。通过仅检测一组选定的类和/或方法,可以将分析副作用限制为应用程序的特定部分。
答案 1 :(得分:1)
示例中没有任何错误。看起来updateInfoInDirection()
调用了new SequenceInfo()
和SequenceInfo.next()
。 “自我时间”意味着时间花费在方法本身的代码中(方法updateInfoInDirection()
在获取线程样本时位于堆栈的底部)。