Статьи

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

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

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

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

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

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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 предоставленную jcmd для устранения тупика.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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 «позволяет отправлять« команды »для выполнения в 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 Discovery Protocol (предвидя предстоящий JEP для этого).

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

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

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

Ссылка: JavaOne 2012: диагностика вашего приложения в JVM от нашего партнера по JCG Дастина Маркса в блоге Inspired by Actual Events .