Сэмплеры легко путаются с помощью встроенного кода: И JVM, и любой приличный компилятор будут встроять тривиальные и / или часто вызываемые методы, таким образом, включая свой код в код своего вызывающего. Профилировщики выборки не могут определить, какие части каждого метода действительно принадлежат ему, а какие - встроенным вызовам.
В случае VisualVM Собственное время фактически включает время выполнения как метода , так и любого встроенного кода.
Сэмплеры могут быть сбиты с толку продвинутой виртуальной машиной: Например, в современных реализациях JVM методы не имеют стабильного представления. Представьте, например, следующий метод:
void A() {
...
B();
...
}
Когда JVM запускается, B()
интерпретируется прямо из байт-кода, что занимает довольно много времени, что делает его видимым для сэмплера. Затем, через некоторое время JVM решает, что B()
является хорошим кандидатом для оптимизации, и компилирует его в собственный код, что делает его намного быстрее. И еще через некоторое время JVM может решить встроить вызов в B()
, включив его код в A()
.
В лучшем случае профилировщик выборки покажет стоимость этих первых запусков, а затем стоимость всех последующих запусков будет включена в время, потраченное вызывающим абонентом. К сожалению, это может запутать неопытного разработчика в недооценке стоимости встроенного метода.
В худшем случае эта стоимость может быть отнесена к вызову одного брата, а не к вызывающему абоненту. Например, в настоящее время я профилирую приложение с использованием VisualVM, где точка доступа кажется методом ArrayList.size()
. В моей реализации Java этот метод представляет собой простой метод получения полей, который любая JVM должна быстро встроить. Тем не менее, профилировщик показывает, что он является основным потребителем времени, полностью игнорируя кучу ближайших HashMap
вызовов, которые, очевидно, намного дороже.