Где проводится профессиональная переподготовка "Системное администрирование Windows"? Что-то я не совсем понял как проводится обучение. |
Практическое применение DTrace
Вглубь Java
Мы уже знаем, что с помощью DTrace можно выяснить, какие конкретно функции вызываются, в каком порядке, какие аргументы передаются и сколько времени потрачено на их выполнение. Все это могло вдохновить системных администраторов и разработчиков на языках С и С++, а интересы программистов на Java оставались в стороне. В этой статье мы рассмотрим, как можно использовать DTrace для отладки приложений на Java.
Для предоставления такой возможности Sun Microsystems встроила в код JVM (начиная с JDK 6.0) датчики двух провайдеров – hotspot и hotspot_jni. В JDK 5.0 был провайдер dvm. Для тех, кто привык им пользоваться, есть хорошая новость – датчики провайдера hotspot носят такие же имена, как и датчики провайдера dvm.
Провайдер hotspot имеет ряд датчиков, с помощью которых можно отслеживать запуск и работу сборщика мусора в JVM (garbage collector). Если количество запусков сборщика мусора растет (особенно, если быстро растет) во время работы приложения или время работы постоянно увеличивается – это верный признак утечки памяти в приложении.
Провайдер hotspot_jni требуется для отслеживания событий, связанных с обращениями через JNI (Java native interface – интерфейс Java с машинным кодом, т.е. ранее написанными и скомпилированными методами, реализованными на языке С). Строго говоря, это может быть программа не только на языке С, ибо JNI описывает интерфейс между программой на Java и машинным кодом, и в нем не указано, компилятор с какого языка должен создать этот код.
Уже было упомянуто, что провайдеры hotspot и hotspot_jni основаны на провайдере sdt и на их датчики можно ссылаться только с упоминанием идентификатора процесса самой машины Java (JVM). Вот пример скрипта, который показывает частоту вызова GC:
hotspot$target:::gc-begin { printf("GC called at %Y\n", walltimestamp); }
Если запустить выполнение программы на java, скажем, демонстрационный пример /usr/jdk/instances/jdk1.6.0/demo/jfc/Java2D/Java2Demo.jar, а затем этот скрипт, то он покажет, в какие моменты запускался сборщик мусора:
# java -jar /usr/jdk/instances/jdk1.6.0/demo/jfc/Java2D/Java2Demo.jar & [1] 1147 # dtrace -qn 'hotspot$target:::gc-begin { printf("GC called at %Y\n", walltimestamp); }' -p 1147 GC called at 2008 Feb 7 01:50:15 GC called at 2008 Feb 7 01:50:16 GC called at 2008 Feb 7 01:50:17 GC called at 2008 Feb 7 01:50:18 GC called at 2008 Feb 7 01:50:19 GC called at 2008 Feb 7 01:50:20 GC called at 2008 Feb 7 01:50:21 GC called at 2008 Feb 7 01:50:25
А что, если нам надо выяснить, какие методы вызываются в приложении Java? Возьмем вот такое приложение:
# cat Greeting.java public class Greeting { public void greet() { System.out.println("Hello DTrace!"); } } # cat TestGreeting.java public class TestGreeting { public static void main(String[] args) { Greeting hello = new Greeting(); while (true) { hello.greet(); try { Thread.currentThread().sleep(1000); } catch (InterruptedException e) { } } } }
Скомпилируем его:
javac TestGreeting.java
Теперь надо запустить получившееся приложение (мирно лежащее в TestGreeting.class ). Однако прежде надо разобраться, как включать датчики в Java SE 6. По умолчанию в виртуальной машине Java доступен лишь небольшой набор датчиков – тех, которые практически не влияют на производительность. Эти датчики позволяют отследить не все события: сборку мусора, компиляцию методов, создание нового потока команд и загрузку класса.
Вызов метода, например, в этот список не входит, потому что соответствующий датчик оказывает значимое влияние на производительность и оттого по умолчанию недоступен. К таким датчикам, кроме датчиков вызова методов, также относятся датчики создания объектов и датчики событий, связанных с мониторами Java. Чтобы сделать такой датчик доступным и "видимым" для dtrace, надо запускать java с ключами, разрешающими использование этих датчиков, или (если JVM уже запущена) использовать команду jinfo для управления JVM.
Доступны следующие ключи при запуске java:
-XX:+DTraceAllocProbes -XX:+DTraceMethodProbes -XX:+DTraceMonitorProbes -XX:+ExtendedDTraceProbes
Первые три ключа разрешают использование датчиков создания объектов, вызова методов и датчиков событий, связанных с мониторами Java соответственно, а четвертый разрешает использование всех датчиков в JVM. Фактическое влияние включенных датчиков на производительность может быть различным в зависимости от частоты вызова кода, содержащего встроенный датчик.
Запускаем наше приложение:
# java -XX:+ExtendedDTraceProbes TestGreeting & [1] 1448 Hello DTrace! Hello DTrace! Hello DTrace! Hello DTrace!
Возьмем скрипт hs.d, вот такой:
# cat hs.d hotspot$target:::method-entry { printf("%s.\%s %s\n",copyinstr(arg1,arg2),copyinstr(arg3,arg4), copyinstr(arg5,arg6)); } tick-5ms { exit(0); }
Конструкция hotspot$target означает, что вместо $target будет подставлен аргумент, переданный с ключом p при запуске dtrace (это будет идентификатор процесса java).
Из описания датчиков провайдера hotspot (http://java.sun.com/ javase/6/docs/technotes/guides/vm/dtrace.html) известно, что аргументы датчика method-entry следующие:
args[0] | идентификатор того потока команд (Java thread ID), который вызывает метод |
args[1] | имя класса метода, указатель на строку в кодировке UTF-8 |
args[2] | длина имени класса метода (в байтах) |
args[3] | имя метода, указатель на строку в кодировке UTF-8 |
args[4] | длина имени метода (в байтах) |
args[5] | сигнатура метода, указатель на строку |
args[6] | длина сигнатуры метода (в байтах) |
Сигнатура метода – это совокупность имени функции, типа возвращаемого значения и списка аргументов с указанием порядка их следования и типов. Подробнее об обозначениях, применяемых в сигнатурах, можно прочесть, например, в разделе Type Signatures в спецификации JNI, размещенной по адресу http://java.sun.com/j2se/1.3/docs/guide/jni/spec/types.doc.html.
Наш скрипт должен при каждом срабатывании датчика methodentry, т.е. при каждом вызове любого метода в нашем приложении на Java, выводить имя класса, имя метода и подпись. Обратите внимание на использование функции copyinstr для вывода строк.
Для автоматического завершения скрипта через 5 миллисекунд добавлена конструкция tick-5ms.
Запустим скрипт в соседнем окне терминала:
# dtrace -qs hs.d -p 1448 Greeting.\greet ()V java/io/PrintStream.\println (Ljava/lang/String;)V java/io/PrintStream.\print (Ljava/lang/String;)V java/io/PrintStream.\write (Ljava/lang/String;)V java/io/PrintStream.\ensureOpen ()V java/io/Writer.\write (Ljava/lang/String;)V java/io/BufferedWriter.\write (Ljava/lang/String;II)V java/io/BufferedWriter.\ensureOpen ()V java/io/BufferedWriter.\min (II)I java/lang/String.\getChars (II[CI)V java/lang/System.\arraycopy (Ljava/lang/Object;ILjava/lang/Object;II)V java/io/BufferedWriter.\flushBuffer ()V java/io/BufferedWriter.\ensureOpen ()V java/io/OutputStreamWriter.\write ([CII)V sun/nio/cs/StreamEncoder.\write ([CII)V sun/nio/cs/StreamEncoder.\ensureOpen ()V sun/nio/cs/StreamEncoder.\implWrite ([CII)V java/nio/CharBuffer.\wrap ([CII)Ljava/nio/CharBuffer; java/nio/HeapCharBuffer.\<init> ([CII)V java/nio/CharBuffer.\<init> (IIII[CI)V java/nio/Buffer.\<init> (IIII)V java/lang/Object.\<init> ()V