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