Статьи

JavaOne 2012: диагностика вашего приложения в JVM

Стоило посетить презентацию Стаффана Ларсена (Oracle Java Serviceability Architect) « Диагностика вашего приложения в JVM » (Hilton Plaza A / B) только для изучения нового инструмента командной строки jcmd, поставляемого с Oracle JVM 7. Остальные презентация была для меня «бонусом», что было приятно для последней сессии, которую я посетил в среду JavaOne 2012

Oracle HotSpot JDK предоставляет jcmd, инструмент командной строки, разработанный для обеспечения обратной совместимости и прямой адаптации для будущих версий Java. Он предназначен для поддержки новых инструментов и функций, которые поставляются с новыми SDK в стандартизированном подходе. На следующем снимке экрана показано, что он используется для большинства основных jps- подобных функций (Ларсен упоминал jps почти так же кратко, как я только что говорил, и называл jcmd «как jps, но более мощный»).

Как показано на рисунке выше, jcmd можно использовать как jps.

Ларсен показал некоторые удобные функции команды jcmd. У него было несколько небольших примеров приложений Java, которые помогли ему продемонстрировать jcmd. В моих целях я запускаю jconsole в одном терминале на моей машине, а затем запускаю команды jcmd для той JVM, в которой работает jconsole. На следующем снимке экрана показано, как базовый (без аргументов) вызов jcmd предоставляет информацию об этом процессе JConsole.

jcmd поддерживает выполнение процессов JVM либо по идентификатору процесса (pid), либо по имени процесса. На следующем снимке экрана показано, как запустить jcmd для процесса JConsole с этим именем и передать его, чтобы узнать, какие параметры можно запустить для этого конкретного процесса. Обратите внимание, что я безуспешно пытался запустить это с «dustin» (без существующего процесса), чтобы доказать, что jcmd действительно показывает опции, доступные для запуска процессов.

Функция, продемонстрированная на последнем снимке экрана, является одной из наиболее убедительных причин перехода от существующих инструментов командной строки, поставляемых с Oracle JDK, к jcmd. Это изображение показывает, как jcmd может предоставить список доступных параметров для каждого процесса, что обеспечивает максимальную гибкость в плане поддержки предыдущих версий или будущих версий Java, которые поддерживают разные / новые команды.

Подобно тому, как справка jcmd <pid> (или заменяет pid на имя процесса) перечисляет доступные операции, которые могут быть выполнены jcmd для определенного процесса JVM, этот же механизм справки может быть запущен для любой из этих перечисленных команд [с синтаксисом jcmd <pid> <command_name> help (или используйте имя процесса вместо pid)], хотя я не мог заставить это работать должным образом на моей машине Windows.

На следующем изображении показано, как на самом деле запустить эту команду для этого процесса JVM, а не просто попросить о помощи.

На двух снимках экрана, показанных выше, я запустил jcmd для pid вместо имени процесса, просто чтобы показать, что он работает как с идентификатором процесса, так и с именем. На следующем снимке экрана показано выполнение jcmd для процесса JVM для получения флагов виртуальной машины и параметров командной строки из процесса JVM (pid этого экземпляра процесса JConsole — 3556).

Выполнение команды Thread.print в jcmd для поддержки процесса JVM упрощает просмотр потоков целевой JVM. Следующий вывод генерируется при запуске jcmd JConsole Thread.print против моего запущенного процесса JConsole.

3556:
2012-10-04 23:39:36
Full thread dump Java HotSpot(TM) Client VM (23.2-b09 mixed mode, sharing):

"TimerQueue" daemon prio=6 tid=0x024bf000 nid=0x1194 waiting on condition [0x069af000]
   java.lang.Thread.State: WAITING (parking)
 at sun.misc.Unsafe.park(Native Method)
 - parking to wait for  <0x23cf2db0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
 at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
 at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
 at java.util.concurrent.DelayQueue.take(DelayQueue.java:209)
 at javax.swing.TimerQueue.run(TimerQueue.java:171)
 at java.lang.Thread.run(Thread.java:722)

"DestroyJavaVM" prio=6 tid=0x024be400 nid=0x1460 waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

"AWT-EventQueue-0" prio=6 tid=0x024bdc00 nid=0x169c waiting on condition [0x0525f000]
   java.lang.Thread.State: WAITING (parking)
 at sun.misc.Unsafe.park(Native Method)
 - parking to wait for  <0x291a90b0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
 at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
 at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
 at java.awt.EventQueue.getNextEvent(EventQueue.java:521)
 at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:213)
 at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:163)
 at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:151)
 at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:147)
 at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:139)
 at java.awt.EventDispatchThread.run(EventDispatchThread.java:97)

"Thread-2" prio=6 tid=0x024bd800 nid=0x4a8 in Object.wait() [0x04bef000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
 at java.lang.Object.wait(Native Method)
 - waiting on <0x2917ed80> (a java.io.PipedInputStream)
 at java.io.PipedInputStream.read(PipedInputStream.java:327)
 - locked <0x2917ed80> (a java.io.PipedInputStream)
 at java.io.PipedInputStream.read(PipedInputStream.java:378)
 - locked <0x2917ed80> (a java.io.PipedInputStream)
 at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:283)
 at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:325)
 at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:177)
 - locked <0x29184e28> (a java.io.InputStreamReader)
 at java.io.InputStreamReader.read(InputStreamReader.java:184)
 at java.io.BufferedReader.fill(BufferedReader.java:154)
 at java.io.BufferedReader.readLine(BufferedReader.java:317)
 - locked <0x29184e28> (a java.io.InputStreamReader)
 at java.io.BufferedReader.readLine(BufferedReader.java:382)
 at sun.tools.jconsole.OutputViewer$PipeListener.run(OutputViewer.java:109)

"Thread-1" prio=6 tid=0x024bd000 nid=0x17dc in Object.wait() [0x047af000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
 at java.lang.Object.wait(Native Method)
 - waiting on <0x29184ee8> (a java.io.PipedInputStream)
 at java.io.PipedInputStream.read(PipedInputStream.java:327)
 - locked <0x29184ee8> (a java.io.PipedInputStream)
 at java.io.PipedInputStream.read(PipedInputStream.java:378)
 - locked <0x29184ee8> (a java.io.PipedInputStream)
 at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:283)
 at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:325)
 at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:177)
 - locked <0x2918af80> (a java.io.InputStreamReader)
 at java.io.InputStreamReader.read(InputStreamReader.java:184)
 at java.io.BufferedReader.fill(BufferedReader.java:154)
 at java.io.BufferedReader.readLine(BufferedReader.java:317)
 - locked <0x2918af80> (a java.io.InputStreamReader)
 at java.io.BufferedReader.readLine(BufferedReader.java:382)
 at sun.tools.jconsole.OutputViewer$PipeListener.run(OutputViewer.java:109)

"AWT-Windows" daemon prio=6 tid=0x024bc800 nid=0x16e4 runnable [0x0491f000]
   java.lang.Thread.State: RUNNABLE
 at sun.awt.windows.WToolkit.eventLoop(Native Method)
 at sun.awt.windows.WToolkit.run(WToolkit.java:299)
 at java.lang.Thread.run(Thread.java:722)

"AWT-Shutdown" prio=6 tid=0x024bc400 nid=0x157c in Object.wait() [0x04c6f000]
   java.lang.Thread.State: WAITING (on object monitor)
 at java.lang.Object.wait(Native Method)
 - waiting on <0x2918b098> (a java.lang.Object)
 at java.lang.Object.wait(Object.java:503)
 at sun.awt.AWTAutoShutdown.run(AWTAutoShutdown.java:287)
 - locked <0x2918b098> (a java.lang.Object)
 at java.lang.Thread.run(Thread.java:722)

"Java2D Disposer" daemon prio=10 tid=0x024bbc00 nid=0x3b8 in Object.wait() [0x0482f000]
   java.lang.Thread.State: WAITING (on object monitor)
 at java.lang.Object.wait(Native Method)
 - waiting on <0x2918b128> (a java.lang.ref.ReferenceQueue$Lock)
 at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
 - locked <0x2918b128> (a java.lang.ref.ReferenceQueue$Lock)
 at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
 at sun.java2d.Disposer.run(Disposer.java:145)
 at java.lang.Thread.run(Thread.java:722)

"Service Thread" daemon prio=6 tid=0x024bb800 nid=0x1260 runnable [0x00000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread0" daemon prio=10 tid=0x024c6400 nid=0x120c waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

"Attach Listener" daemon prio=10 tid=0x024bb000 nid=0x1278 waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" daemon prio=10 tid=0x024bac00 nid=0xe3c runnable [0x00000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" daemon prio=8 tid=0x024a9c00 nid=0x15c4 in Object.wait() [0x046df000]
   java.lang.Thread.State: WAITING (on object monitor)
 at java.lang.Object.wait(Native Method)
 - waiting on <0x2918b358> (a java.lang.ref.ReferenceQueue$Lock)
 at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
 - locked <0x2918b358> (a java.lang.ref.ReferenceQueue$Lock)
 at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
 at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:177)

"Reference Handler" daemon prio=10 tid=0x024a4c00 nid=0xe40 in Object.wait() [0x0475f000]
   java.lang.Thread.State: WAITING (on object monitor)
 at java.lang.Object.wait(Native Method)
 - waiting on <0x2917e9c0> (a java.lang.ref.Reference$Lock)
 at java.lang.Object.wait(Object.java:503)
 at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
 - locked <0x2917e9c0> (a java.lang.ref.Reference$Lock)

"VM Thread" prio=10 tid=0x024a3800 nid=0x164c runnable 

"VM Periodic Task Thread" prio=10 tid=0x024e7c00 nid=0xcf0 waiting on condition 

JNI global references: 563

Ларсен показал, как использовать информацию о потоках, предоставленную jcmd, для устранения тупика.

Ларсен показал получение гистограммы классов из запущенного процесса JVM с помощью jcmd. Это делается с помощью команды jcmd <pid> GC.class_histogram. Далее показано очень маленькое подмножество его вывода (pid этого процесса JConsole 4080 на этот раз).

4080:

 num     #instances         #bytes  class name
----------------------------------------------
   1:          1730        3022728  [I
   2:          5579         638168  
   3:          5579         447072  
   4:           645         340288  
   5:          4030         337448  [C
   6:           645         317472  
   7:           602         218704  
   8:           942         167280  [B
   9:           826          97720  java.lang.Class
  10:          3662          87888  java.lang.String
  11:          2486          79552  javax.swing.text.html.parser.ContentModel
  12:          3220          77280  java.util.Hashtable$Entry
  13:          1180          67168  [S
  14:          2503          60072  java.util.HashMap$Entry
  15:           181          59368  
  16:           971          43584  [Ljava.lang.Object;
  17:          1053          41160  [[I
  18:           206          29040  [Ljava.util.HashMap$Entry;
  19:           111          27880  [Ljava.util.Hashtable$Entry;
  20:           781          18744  java.util.concurrent.ConcurrentHashMap$HashEntry
  21:          1069          17104  java.lang.Integer
  22:           213           9816  [Ljava.util.concurrent.ConcurrentHashMap$HashEntry;
  23:           202           9696  java.util.HashMap
  24:           201           9280  [Ljava.lang.String;
  25:            24           8416  [[I

Ларсен также продемонстрировал jstat и несколько его полезных функций. Он продемонстрировал использование jstat -gcnew (поведение нового поколения), jstat -precompilation (статистика метода компиляции) и jstat -options (параметры отображения).

В ходе своей презентации Ларсену нужно было преобразовать десятичное число (pid?) В его шестнадцатеричное представление для сравнения его с выводом другого инструмента. Он использовал удобную команду printf «% x \ n» <pid>, чтобы получить шестнадцатеричное представление pid.

Ларсен продемонстрировал использование VisualVM для сравнения двух дампов кучи и просмотра дампов кучи . Он также продемонстрировал VisualVM Profiler .

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

Ларсен говорил об удаленном доступе к информации JVM через JMX и такие инструменты, как jconsole и jvisualvm. Он продемонстрировал, что jcmd также может быть использован для запуска JMX: ManagementServer.start «с набором параметров». Ларсен считает, что VisualVM и JConsole будут использовать ManagementServer.start, а не Attach API, если они будут реализованы сегодня.

jstat также может подключаться к демону удаленно с помощью jstatd . Нет шифрования или аутентификации с помощью jstatd.

jps и jcmd находят то, что работает в системе, используя «известный файл для каждой JVM»: / hsperfdata_ <пользователь> / <pod> Этот файл создается при запуске JVM и удаляется при завершении работы JVM. Неиспользуемые предыдущие файлы удаляются при запуске, поэтому jps и jcmd, как и сами Java-программы, очистят эти старые.

Attach API «позволяет посылать„команды“для executionin в JVM» , но работает только на локальном компьютере , так и для текущего / того же пользователя. Это то, что используют jcmd и jstack. Затем Ларсен объяснил различные механизмы использования Attach API для Linux / BSD / Solaris (использует создание временных файлов) и Windows (использует внедрение кода). Я использовал Attach API в своем посте Groovy, JMX и Attach API .

Диагностические команды — это «вспомогательные процедуры внутри JVM», которые производят «вывод текста». Они могут быть выполнены с помощью утилиты jcmd (и вскоре через JMX). У каждого из них есть средство с самоописанием: jcmd PerfCounter.print для просмотра необработанного содержимого.

Ларсен показал информативную таблицу, в которой сравниваются подходы «взаимодействия с JVM»: attach , jvmstat , JMX , jstatd и Serviceability Agent (SA). SA «должен использоваться как последнее средство (» обычно для JVM, которая зависла «) и использует» отладчик для чтения информации «.

Ларсен перешел к разговору о будущих инструментах. Он начал эту часть презентации с освещения Java Flight Recorder . Java Flight Recorder — это «встроенный в JVM профилировщик и трассировщик» с «низкими издержками» и «всегда включен». Другие инструменты включают в себя Java Mission Control («графический инструмент, предоставляющий очень подробные сведения о мониторинге во время выполнения»), дополнительные диагностические команды для jcmd («в конечном итоге заменяющих jstack, jmap, jinfo» по разным причинам ), JMX 2.0 («что-то, что мы подбираем» снова; это было начато очень давно «), улучшено ведение журнала для JVM (Предложение по расширению JVM [JEP] 158 ),и протокол обнаружения Java (предвидение предстоящего JEP для этого).

Был задан один вопрос: можно ли увидеть MBeans в VisualVM, как это можно сделать в JConsole. Как я уже писал , есть плагин VisualVM для этого.

Хотя я чувствовал себя несколько комфортно с инструментами командной строки Oracle HotSpot JDK, я был незнаком с jcmd и ценил освещение Ларсена этого. Я узнал и кое-что еще по пути. Моя единственная жалоба заключается в том, что презентация Ларсена (особенно демонстрация) была настолько быстрой и содержательной, что мне хотелось бы увидеть ее снова.

Связанная (но более старая) презентация с некоторым содержимым доступна по адресу http://www.oracle.com/javaone/lad-en/session-presentations/corejava/22260-enok-1439100.pdf.