Персональные инструменты
 

DIY Java Profiling (Роман Елизаров, ADD-2011)

Материал из CustisWiki

Перейти к: навигация, поиск

Аннотация

Докладчик
Роман Елизаров

В докладе пойдет речь о методиках изучения производительности Java приложений без использования готовых сторонних инструментов профилирования, а используя не так широко известные встроенные в JVM возможности (threaddumps, java agents, bytecode manipulation).

Видео

Скачать
http://ftp.linux.kiev.ua/pub/conference/peers/addconf/2011/2c5-diy-java-profiling-elizarov.avs.avi


Конференция «Application Developer Days-2011» приглашает участников и докладчиков!

Конференция Application Developer Days-2011 приглашает участников и докладчиков!

Для этого доклада нужен подкаст (аудиозапись)?

  •  Да, многое понятно и без видео части, есть смысл его прослушать.
  •  Нет, аудиозапись бесполезна (не понять без видео или вообще мало смысла в докладе).

Стенограмма

Иллюстрированную статью-стенограмму по видеозаписи составил Стас Фомин.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

У меня сегодня будет доклад про «Сделай себе сам профилирование на Яве». Слайды будут на английском, доклад я буду делать на русском. Слайдов очень-очень много, а времени не так много, поэтому я буду какие-то очень быстро проскакивать, и постараюсь оставить больше времени в конце на вопросы, и может быть даже где-то в середине. В принципе, не стесняйтесь, если захочется вдруг что-то спросить или уточнить, и даже меня прервать. Я лучше подробнее освещу то, что вам интересно, чем буду просто рассказывать то, что я хотел рассказать.

Доклад основан на реальном опыте, мы в компании больше десяти лет занимаемся созданием сложных, очень высоконагруженных финансовых приложений, работающих с большими массивами данных, с миллионами котировок в секунду, c десятками тысяч пользователей онлайн, и там, при такой работе, всегда речь идет о профилировании приложения.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Профилирование приложения — неизбежный компонент любой оптимизации, оптимизация без профилирования невозможна. Вы профилируете, находите узкие места, оптимизируете-профилируете, это постоянный цикл.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Почему доклад называется «Do It Yourself Java Profiler», зачем что-то делать самому? Есть же огромное количество готовых инструментов, которые помогают профилировать — собственно профилировщики, и подобные инструменты.

Но дело в том, что во-первых, может быть проблема со сторонним инструментом. Вы просто по каким-то соображениям — надежности или безопасности, можете не иметь возможности запустить сторонний инструмент на каком-то живом окружении. А к сожалению, частенько приходится профилированием заниматься не только на тестовой платформе, но и на живой платформе, и не всегда для высоконагруженной платформе у вас есть возможность и ресурсы сделать идентичную копию системы и запустить под такой же нагрузкой. А многие узкие места могут выявится только под большой нагрузкой, только при очень специфичных условиях. Вы видите, что система работает не так, но вы не понимаете, почему. Какой именно шаблон нагрузки нужно для него создать, чтобы проблема проявилась — поэтому профилировать часто нужно именно живую систему.

Мы, когда пишем финансовые приложения, у нас есть еще задача обеспечить надежность системы. И мы делаем не «банки», где главное — не потерять ваши деньги, но которые могут быть часами недоступны. Мы делаем брокерские системы онлайн-торговли, где постоянно 24×7 доступность систем, это одна из ключевых их качеств, они никогда не должны падать.

И я уже говорил, что вся индустрия зарегулирована, и мы иногда не можем какой-то сторонний продукт использовать на реальной системе.

Но и инструменты частенько непрозрачны. Да, есть документация, которая описывает «что», но она не описывает, как именно он достает эти результаты, и не всегда можно понять, что он собственно намерял.

Причем даже если инструмент с открытым кодом, это ничего не меняет, потому что этого кода много, вы убъете кучу времени в нем разбираясь. Инструменты надо учиться, а делать что-то самому, конечно, намного приятней.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

В чем проблема с изучением? Естественно, если вы используете какой-то инструмент часто, то стоит его учить. Если вы программируете каждый день в какой-то любимой вами среде разработки, вы ее знаете вдоль и поперек, и это знание, естественно, окупается вам сторицей.

А если вам нужно что-то сделать раз в месяц, например, раз в месяц вам нужно ради бага с производительностью заниматься профилированием, то не факт, что изучение соответствующего инструмента окупится. Конечно же, если нет ситуации, когда инструмент решает задачу в разы быстрее.

Делая что-то своими руками, вы можете переиспользовать ваши знания. Например, у вас есть знания языков программирования, своих инструментов, вы можете их углубить, расширить, уточнить, глубже изучив те инструменты, которые у вас уже есть, вместо изучения нового.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Почему доклад о Java? Ну мало того, что наша компания программирует на Java, это ведущий язык 2001 года по индексу TIOBE, отлично подходит для enterprise приложений. А для данной конкретной лекции вообще замечательно — потому что Java — это managed язык, работает в виртуальной машине, и именно профилирование в Java делается очень легко.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Я буду рассказывать во-первых, просто о том, как решить многие проблемы профилирования, написав некий код на Java. Скажу о возможностях Java-машин, которые можно использовать, и расскажу о такой технике, как манипулирование байт-кодом.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Мы посмотрим сегодня на профилирование как CPU, так и памяти. Я расскажду про разные техники.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Профилирование CPU. Самый простой способ, это просто взять и запрограммировать. Если вам нужно разобраться, куда, сколько, чего, в вашей программе занимает времени, и сколько раз вызывается, то самый-самый простой способ: не надо никаких инструментов, ничего — написать просто несколько строчек кода.

В Java есть замечательная функция «currentTimeMillis», которая возвращает текущее время. Можно ее замерять в одном месте, замерять в другом, а дальше можно посчитать, сколько раз это сделано, суммарное время, минимальное и максимальное время, все что угодно. Самый такой простой способ. DIY в своей максимальной простоте и примитивизме.

Как ни странно, на практике способ отлично работает, приносит кучу пользы — потому что быстр, удобен и эффективен.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Когда этот метод хорошо работает? Это замечательно работает для бизнес-методов — бизнес-метод большой, вызывается не очень часто, и вам нужно что-то про него измерить. Более того, написав этот код, раз уж вы его написали — он становится частью вашего приложения и частью функционала. Более менее любое современное большое приложение содержит интерфейсы управления, какие-то статистики, … и в общем производительность приложения — это одна из трех вещей, которую часто хочется видеть, чтобы приложение о себе выдавало, просто как часть своего функционала.

В этом смысле, запрограммировать приложение, чтобы оно само себя профилировало, является логичным шагом. Вы увеличиваете функционал приложения, профилирование приложения становится частью его функционала. Особенно, если таким образом вы расставляете определения в ваших бизнес-методах, которых конечный пользователь вызывает, то конечному пользователю эта информация тоже будет осмысленна. Сколько раз и какие методы вызывались, сколько по времени отрабатывали и так далее. Информация, которую вы собираете, в данном случае, при данном подходе, полностью под вашим контролем.

Можете замерять количество вызовов, минимальное время, максимальное время, среднее считать, можете строить гистограммы распределения времени выполнения, считать медианы и персентили. Можете разные пути исполнения в коде отслеживать по разному, как в этом примере, кто успел разглядеть, пока я говорил, обратил внимание, что в зависимости от пути исполнения, мы записываем разную статистику: как часто результат запроса попал в кеш, и сколько времени это заняло, и как часто запросу пришлось лезть в базу данных, и сколько времени это заняло.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Это возможно, если вы этот код пишете сами, собираете статистику, встраиваете сами в свое приложение.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Более того, вам, как человеку, который занимается циклом профилирования-оптимизации, вы потом эту информацию используете — что же в вашем приложении происходит? Эта информация всегда находится внутри вашего приложения, код работает в живой системе. У вас произошел какой-то неудачный день, что-то система не так работала, вы можете посмотреть в логах эту статистику, разобраться, и так далее.

Замечательная методика, нет никаких сторонних инструментов, только немножко кода на яве.


Что же делать, если методы короче, и вызываются чаще?

Дело в том, что такой прямой метод уже не подойдет, потому что метод «currentTimeMillis» не быстрый, и меряет только в миллисекундах.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Если вам нужно замерить только количество вызовов, то достаточно быстро можно это сделать используя Java-класс «AtomicLong». С его помощью, вы можете, внося минимальный вклад в производительность, посчитать количество вызовов какого-нибудь интересующего вас метода. Это будет работать до десятков тысяч вызов в секунду, не сильно искажая работу самого приложения.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Что же делать, если вам нужно еще замерить время выполнения? Замер времени исполнения коротких методов — это очень сложная тема. Стандартными никакими методами она не решается, несмотря на то, что в Java есть метод «systemNanoTime», он эти проблемы не решает, он сам по себе медленный, и с помощью него что-то быстрое сложно замерить.

Единственный реальный выход — это использовать нативный код, для x86 процессора есть такая замечательная инструкция rdtsc, которая возращает счетчик количества тактов процессора. Напрямую к ней доступа нет, можно написать на C однострочный метод, который вызывает «rdtsc», а дальше слинковать его с Java-кодом, и вызывать из Java. Этот вызов вам займет сто тактов, и если вам нужно замерить кусок кода, который занимают тысячу-другую тактов, то это осмысленно, если у вас идет оптимизация каждого машинного такта, и вы хотите понять, «плюс-минус», «быстрее-медленнее», как вы работаете. Это действительно редкий случай, когда вам нужно оптимизировать каждый такт.

Чаще всего, когда речь идет о каких-то более коротких кусках кода, и более часто вызываемых, используют другой подход, который называется «семплирование». Вместо точных замеров, сколько раз и чего вызывается, вы периодически анализируете выполнение программы, смотрите, где она исполняется, в произвольные моменты времени, например — раз в секунду, или раз в десять секунд.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Смотрите, где происходит исполнение, и считаете, в каких местах вы застаете свою программу часто. Если у вас в программе есть строчка, в которой она тратит все, или по крайней мере 90% своего времени, например, какой-нибудь цикл, а там, в глубине, какая-то строчка, то скорее всего, при остановке исполнения вы ее в этой строчке и застанете.

Такое место в программе называется «горячей точкой». Это всегда замечательный кандидат для оптимизации. Что классно — есть встроенная фукнция под названием «thread dump», чтобы получить дамп всех потоков. В Windows она делается путем нажатия CTRL-Break на консоли, а на Linux и других юниксах это делается посылкой третьего сигнала, командой «kill -3».

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

В этом случае, Java-машина на консоль выводит подробное описание состояния всех потоков. И если у вас действительно есть горячее место в коде, то скорее всего программу там и застанете. Поэтому опять же, когда у вас проблема производительности с кодом, не надо бежать к профилировщику, не надо ничего делать. Видите, что тормозит, сделайте хоть один thread dump, и посмотрите. Если у вас одно горячее место, в котором программа тратит все время, вы в thread dump и увидите эту строчку, в своей любимой среде разработки, без использования каких-нибудь сторонних, дополнительных инструментов. Посмотрите этот код, изучите, оптимизируйте — либо он слишком часто вызывается, либо он медленно работает, дальше уже разбор полетов, оптимизация, либо дальнейшее профилирование.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Также в современных Java-машинах есть замечательная утилита «jstack», ей можно передать идентификатор процесса, и получить на выходе thread dump.

Сделайте не один thread dump, сделайте несколько thread dumpов. Если первый ничего не выловит, посмотрите еще на пару-тройку. Может у вас в горячей точке не сто процентов времени программа проводит, а 50%. Сделав несколько thread dumpов, вы явно в хоть какой-то из этих моментов, достанете ваш код из горячей точки, и глазками посмотрите те места, где вы застали ваш код.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Эту идею можно развить дальше. Можно, запустив ява-машину, перенаправить ее выход в файл, и запустить скрипт, который делает thread dump каждые три секунды. Это можно делать совершенно спокойно на живой системе, без какого либо риска что-то с ней сделать. Потому что сам сбор thread dump досточно быстрая процедура, занимает от силы 100 миллисекунд, даже при очень большом количестве тредов.

А если вы пишите на яве, то скорее всего ваша система не hard real time, и вам там наносекунды не важны, потому что у вас уже периодически встречается сборка мусора и так далее. Т.е. вам лишнее засыпание системы на сто миллисекунд катастрофы не создает.

И даже в нашей, финансовой области, большинство систем, которые мы пишем, мы пишем все-таки для людей, с ними работают люди, да, там миллионы котировок в секунду, да, есть роботы (это отдельная история), но чаще всего на эти котировки смотрит человек, который плюс-минус 100 миллисекунд в глаза не заметит. Человек заметит, если будет торможение на двести миллисекунд, это уже будет заметная для человека задержка, но сто миллисекунд — нет.

Поэтому можно не переживать о лишних ста миллисекундах, и раз в три секунды делать thread dump, можно совершенно безопасно, даже на живой системе. При этом thread dump, это часть java-машины, хорошо оттестированная — я за весь свой опыт работы ни разу не видел, чтобы попытка сделать на Java-машине thread dump сделала с ней что-то плохое.

Т.е. это совершенно безопасный инструмент профилирования живых и работающих систем. После этого, получив файл thread dump, его можно посмотреть глазками, а можно написать несложный кусок кода, который анализирует, считает какие-нибудь статистики → хотя бы тупо, посмотреть, какие методы проявились и сколько раз, посмотреть, в каком состоянии находились потоки.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

При этом если стандартные инструменты профилирования, действительно смотрят на состояние потока, которое выводит Java-машина («RUNNABLE»), то в реальности, ваше состояние ни о чем не говорит, потому что если ваша программа много работает с базой данных, и много работает с какими-то внешними… сетью, то ваш код может ожидать получение данных по сети, при этом java-машина его считает «RUNNABLE», и вы ничего не сможете понять — какие у вас реально методы, и какие ждут данных от сети. С другой стороны, если вы сами анализируете стеки, — вы можете написать, вы-то знаете, что ваша программа делает, что вот это — вызов в базу данных, что этот метод в стеке означает, что вы вошли в базу данных, можете посчитать, сколько процентов времени вы проводите в базе данных, и так далее, тому подобное. Вы можете знать, что вот этот метод на самом деле CPU не жрет, хотя java-машина думает, что он «RUNNABLE».

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Более того, thread dump можно интегрировать в приложения, в Java есть замечательный метод

Thread.getAllStackTraces

который позволяет получить информацию о stacktrace программно.

Таким образом, вы можете интегрировать профилирование, как функциональную часть этого приложения, и распространять приложение вашим клиентам, с уже встроенным профилированием. Тем самым, у вас будет постоянный поток информации, который вы сможете анализировать для улучшения вашего приложения.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Но есть проблема. Дело в том, что когда вы Java-машину просите сделать thread-dump, она не просто процесс останавливает, и делает стек, она включает флажок, что ява-машине надо бы остановится в следующем безопасном месте. «Безопасное место» — это специальные места, которые расставляет компилятор по коду, где у программы есть определенное состояние, где понятно, что у нее в регистрах, понятна точка исполнения, и так далее. Если брать кусок последовательного кода, где нет никаких обратных переходов, никаких циклов, то там «safe point» может не оказаться вообще. Причем неважно, что вызовы методов могут заинлайнится hotspot-ом, и savepoint-ов тоже не будет.

Поэтому если вы видите в thread dumpe какую-то строчку — это совершенно не значит, что это горячая строчка вашего кода, это просто ближайший savepoint к горячей точке. Потому что когда вы нажимаете «CTRL-BREAK», Java всем потокам выставляет флажок «остановиться на ближайшем savepointе», и только когда они останавливаются, Java-машина анализирует, в каком состоянии они это делают.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Теперь перейдем к профилированию памяти, как это делается.

Во-первых, есть замечательные, готовые фичи Java-машина. Есть отличный инструмент jmap, который выводит гистограмму того, чем у вас забита память, какие объекты и сколько памяти занимают. Это замечательный инструмент для общего обзора, что же у вас происходит, и чем забита память.

Опять же, если вы никогда не профилировали программу, то чаще всего вы сразу найдете проблемы, и у вас будет пища для дальнейшей оптимизации вашего использования памяти.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Проблема в том, что вы таким образом получите информацию о всех объектах, даже тех, которые сейчас в данный момент не используются, находятся в мусоре.

Поэтому у jmap есть специальная опция «live», которая перед тем, как сделать гистограмму делает сборку мусора, оставляет только используемые объекты, и только после этого строит гистограмму.

Проблема, что уже с этой опцией, большую, живую систему работающую с многими гигабайтами памяти, запрофилировать нельзя, потому что сборка мусора системы, работающей с десятком гигабайт памяти занимает десяток-другой секунд, и это может быть неприемлимо… в любой системе, если ваша система работает с конечными людьми, человек, которому система дольше трех секунд не отвечает, считает, что она зависла. Нельзя живую, работающую с человеком систему останавливать дольше чем на секунду, на самом деле. Даже секунда уже будет человеку заметна, но еще не катастрофа, но если вы подключите какой-то инструмент, который остановит на 10 секунд — то это будет катастрофа. Поэтому часто приходится довольствоваться на живых системах jmap-ов тех объектов, которые есть, и в целом, неважно, мусор это или нет.


DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Также дополнительно полезно знать дополнительные опции Java-машин. Например, у Java-машин можно попросить отпечатать гистограмму классов, когда вы делаете thread dump → «PrintClassHistogram».

Ява-машину можно попросить при исчерпании памяти записывать свое состояние на диск. Это очень полезно, потому что обычно, люди начинают оптимизировать потребление памяти только тогда, когда она заканчивается почему-то. Никто не занимается профилированием, когда все хорошо, а вот когда программе начинает памяти не хватать, она вылетает, начинают думать, как бы так соптимизировать. Поэтому это опцию полезно всегда иметь включенной. Тогда в плохом случае Java-машина вам запишет бинарный дамп, который вы можете потом, не на живой системе, любыми инструментами проанализировать. При этом, этот дамп можно в любой момент взять с Java-машины, тем же jmap-ом, с опцией «-dump», но это, опять же, останавливает Java-машину на долгое время, на живой системе вы это вряд ли заходите делать.

Реплика из зала: Там есть свойство, что этот «HeapDumpOutOfMemory» оптимизирован для тех случаев, когда память уже кончилась.

Да, конечно. «HeapDumpOutOfMemory» очень полезная опция, причем хотя она «-XX», не надо этих опций бояться, хотя этот «XX» подчеркивает их мегаспециальность, но это не экспериментальные опции, это нормальные production-опции ява-машины, они стабильные, надежные, их можно использовать на живой, реальной системе.

Это не экспериментальные опции! В java-машине есть четкое деление, но деление на экспериментальные и неэкспериментальные опции не зависит от числа X-ов.

Реплика из зала: Эта опция иногда не откладывают дампы…

Ну, в java-машине тоже бывают баги, все зависит от того, … бывают разные причины исчерпания памяти, я не буду на этом останавливаться, у нас времени мало.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Я хочу остановится на очень важном моменте, в оставшееся время, а именно, на профилировании выделения памяти.

Одно дело — чем память занята, как вы ее вообще используете. Если у вас где-то в коде есть излишнее выделение временной памяти, т.е. вы выделяете, … что-то с ним делаете, этом методе и потом забываете, он уходит в мусор, и garbage collector потом его забирает. И так вы делаете снова и снова, ваша программа будет работать медленнее, чем бы она работала с этим … но вы никаким профилировщиком по CPU это место кода не найдете, потому что сама операция выделения в Java-машине памяти, работает фантастически быстро, быстрее, чем в любом не-managed языке, C/C++, потому что в Java выделение памяти это банально увеличение одного указателя. Все. Это несколько ассемблерных инструкций, все очень быстро происходит. Она уже предварительно обнулена, все уже выделено и подготовлено. Вы этого времени при анализе горячих точек вашего кода не найдете — оно не высветится у вас никогда, ни в каком thread dumpe, ни в каком профилировщике, что это у вас горячая точка. Хотя у вас это все будет жрать ваше время, работы вашего приложения — почему? Потому что потом, сборщик мусора будет тратить время, чтобы этот мусор собрать. Поэтому смотрите, на то, сколько процентов времени ваше приложение тратит на сборку мусора.

Это полезная опция «-verbose:gc», «+PrintGC», «+PrintGCDetails», которые позволят вам разобраться, сколько времени вашего приложения уходит на сборку мусора. Если вы видите, что на сборку мусора уходит существенный процент времени, значит вы где-то в программе много выделяете памяти, вы это место не найдете, нужно искать, кто выделяет память.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Как искать? Есть встроенный в Java-машину способ, ключик «-Xaprof». Он вам, к сожалению, только при завершении процесса, выводит так называемый allocation profile, который говорит не о содержимом памяти, а о статистике выделяемых объектов → какие объекты и как часто выделялись.

Если у вас это действительно часто происходит, вы скорее всего увидите, что где-то заведен какой-то временный класс, который действительно очень часто выделяется. Попробуйте сразу сделать «aprof» — может вы сразу найдете вашу проблему.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Но не факт. Может получится ситуация, что вы увидите выделения большого количества массивов character-ов, каких-то string-ов, или чего-нибудь, и непонятно где.

Понятно, что у вас могут быть подозрения — где. Может какое-нибудь недавнее изменение, это могло внести. В конце-концов, вы можете добавить в том месте, где выделяется память слишком часто, используя ту же технику изменерения кода в atomiс longах, посчитайте, сколько раз в этом месте происходит выделение — посмотрите статистику, подозрительные места вы сами сможете проинициировать и найти.

А что делать, если у вас нет идеи, где это происходит? Ну надо как-то добавить сбор статистики всюду, по всем местам, где выделяется память. Для такого рода задач отлично подходит аспектно-ориентированное программирование, либо прямое использование манипуляций байт-кодом.

Я сейчас за оставшееся время попробую остановится как раз на технике манипуляции байт-кодом, которая как раз подходит, для решения задач типа «вот я хочу во всех местах, где выделяется массив, посчитать, сколько раз в этом месте это происходит, по всему своему коду, чтобы найти то самое место, в котором я почему-то очень много массивов int-ов выделяю». Т.е. я вижу, что их много выделяется, но я просто хочу найти где.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Манипулирование байткодом позволяет не только эти задачи решить, она позволяет любые изменения, нефункциональные, в код вносить, уже после его компиляции. Тем самым, этот способ декомпозировать профилирование от бизнес-логики. Если в начале я говорил, что часто профилирование может быть логичной частью вашего функционала, то бывают случаи, когда это не нужно, когда вам нужно найти проблему, решить, и чтобы никаких строк не осталось. В этом случае подходит такая замечательная техника, как манипулирование байт-кодом.

Причем это можно делать как с посткомпиляцией выкладки кода, так и на лету, с кодом.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Самый лучший способ, который я знаю, это использовать библиотечку ASM, ObjectWebовскую. Это open-source библиотека, которая позволяет очень легко заниматься манипуляцией байткодом, и она фантастически быстрая — можно манипулировать кодом на лету, не сильно замедляя время загрузки приложения.

ASM устроен очень просто. У него есть класс под названием class-reader, который читает .class-файлы, и преобразовывает байтики, используя шаблон Visitor, в набор вызовов вида «я вижу метод», «я вижу поле с такими-то полями в этом классе» и так далее. Когда он видит метод, он начинает с помощью «MethodVisitor» сообщать, какой он там видит байт-код.

А потом есть, с другой стороны, такая штука, как «ClassWriter», который наоборот, превращает класс в массив байтиков, который нужен ява-машине.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Чтобы например, с помощью ASM-а, отследить все выделения массивов… в-общем, это делается примитивно. Вам нужно всего парочку классов сделать. Вам нужно определить свой класс-адаптер, который, когда ему говорят, что виден метод, перекрывает, и возвращает свой собственный метод-visitor, чтобы оузнать, что в этом методе происходит.

А когда внутри метода ему говорят, что вот, есть целочисленная инструкция с байт-кодом выделения массива («NEWARRAY»), то в этот момент, он имеет возможность … вставить свои какие-нибудь байткоды в восходящий поток и все. И вы отследили все места, где у вас выделяются массивы, и поменяли соответствующий байт-код.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

Дальше — что делать, если вы хотите эти изменения делать на лету.

Если у вас есть набор скомпилированных классов — то это легко → вы как бы обработали этим инструментом, и все.

Если вам это нужно делать на лету, то в java-машине есть замечательная опция — javaagent. Вы делаете специальный jar-файл, у которого в манифесте указываете опцию «Premain-Class», и указываете там имя своего класса. Потом … метод «premain» по определенному шаблону, и тем самым, вы еще до запуска основного кода, c main-методом, получаете управление, и получаете указатель на interface instrumentation. Этот интерфейс замечательный, он позволяет вам, на лету, менять классы в java-машине. Он позволяет вам поставить свой собственный class-file трансформер, который Java-машина будет вызывать для каждой попытки загрузки класса.

И вы сможете классы подменять. Т.е. загружать не только те классы, которые на самом деле лежат, а с помощью того же ObjectWebASM, анализировать что-то, менять, и подменять их на лету… можете узнать размер выделенного объекта.

Замечательный инструмент такого профилирования на коленке, когда у вас есть конкретная задача, которую нужно решить.

DIY Java Profiling (Роман Елизаров, ADD-2011).pdf

В завершении скажу, что совершенно не обязательно, для решения каких-то задач профилирования владеть каким-нибудь инструментом, достаточно знать байт-код, знать опции ява-машины, и знать стандатные ява-библиотеки, тот же javalang-инструмент. Это позволяет решить огромное число специфичных проблем, с которыми вы сталкиваетесь. Мы за десять лет у себя в компании разработали несколько доморощенных инструментов, которые решают проблемы не специфичные нам, которые не решают специфичные профилировщики. Начиная с того, что у нас в процессе работы появился развесистый, но тем не менее простой инструмент который анализирует thread dump, и выдает по ним статистику, это тем не менее простая утилитка, которую нельзя назвать инструментом. Классик на несколько страничек, который собирает статистику и выдает ее в красивом виде. Дико полезен, потому что нам не надо в production систему подключать какие-то профилировщики, просто thread dump, и все…

И кончая тем, что у нас есть свой собственный инструмент профилирования памяти, который опять же, маленький, его сложно назвать инструментом, который отслеживает, где и что выделяется, причем делает он это, практически не замедляя программы. Причем как коммерческие, так и открытые профилировщики, они тоже умеют отслеживать выделение памяти, но они пытаются решить проблему более сложную, и универсальную. Они пытаются узнать, в каком месте выделение памяти происходит, с полным stack-trace'ом. Это долго, это сильно замедляет. Не используют то же семплирование. Не всегда собирают, тем самым получая не всю статистику, и так далее.

Идут на какие-то свои компромиссы, которые в вашей предметной области, они вам не нужны, у вас есть какие-то свои задачи, которых вы хотите решить при анализе производительности ваших систем.

Теперь я будут отвечать на вопросы (ответы на вопросы с 30:06).

Презентация

Примечания и отзывы

Роман Елизаров про техники померять что-нить на jvm без профайлера. Сугубо технический доклад, полезно, в принципе. ©



Внимание! Данная статья выбрана для репликации во внешнюю базу знаний компании. Пожалуйста, не допускайте в этой статье публикацию конфиденциальной информации, ведения обсуждений в теле статьи, и более ответственно относитесь к качеству самой статьи — проверяйте орфографию, пишите по-русски, избегайте непроверенной вами информации.