Статьи

Мониторинг ключевых характеристик JVM с помощью Groovy, JMX и RuntimeMXBean

Начиная с J2SE 5 , доступны платформенные MBean-компоненты , которые позволяют отслеживать некоторые ключевые характеристики, касающиеся JVM, и (в некоторых случаях даже управлять ими) через JMX . Кроме того, многие приложения на основе JVM добавляют свои собственные функции с поддержкой JMX для мониторинга и управления. В блоге Groovy, JMX и Attach API я рассмотрел, как отобразить многие MBean-компоненты, предоставляемые платформой, используя Groovy, JMX и Attach API . В этой статье я рассмотрю более конкретные сценарии, которые обращаются к узко сфокусированному подмножеству этих доступных для платформы значений, доступных в RuntimeMXBean .

Мой предыдущий пост продемонстрировал способность просматривать широкий спектр деталей JVM с помощью Groovy и JMX. В большинстве случаев мне не нужны все эти детали сразу, а просто

нужен один или два предмета. Хотя я мог бы использовать такой инструмент, как JConsole , VisualVM или даже современную Java IDE, чтобы увидеть эти подробности, я иногда хотел бы запустить простой сценарий для получения точных результатов, которые я ищу, вместо того, чтобы использовать общий клиент JMX, который я нужно запустить и перейти к этой информации. Вот где простые сценарии, такие как показанные в этом посте, особенно удобны.

Все мои примеры в этом посте предполагают, что человек, выполняющий процесс Java, который будет отслеживаться / управляться, — это тот же человек (с тем же именем пользователя), который запускает сценарии, и что они используются для мониторинга процессов Java, работающих на одном и том же локальном компьютере. Это предположение позволяет использовать API Присоединения. Если сценарий выполнялся другим пользователем, а не процессами Java, или сценарии должны были выполняться на удаленных процессах Java, вместо этого использовались бы методы для удаленного JMX . Поскольку в этих примерах будет использоваться API присоединения, я поместил код, который использует каждый пример, в файл сценария Groovy, который вызывается каждым примером. Этот файл, который будет использоваться всеми моими сценариями, называется JmxServer.groovy и показан далее.

JmxServer.groovy

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
 * JmxServer.groovy
 *
 * Functions meant to be called by other scripts or tools that need to use
 * the Attach API to access an MBeanServerConnection to use to manage and
 * monitor a particular JVM (and possibly the application hosted in that JVM)
 * identified by the provided Process ID (pid)
 */
 
import javax.management.MBeanServerConnection
import javax.management.ObjectName
import javax.management.remote.JMXConnector
import javax.management.remote.JMXConnectorFactory
import javax.management.remote.JMXServiceURL
 
import com.sun.tools.attach.VirtualMachine
 
/**
 * Provide an MBeanServerConnection based on the provided process ID (pid).
 *
 * @param pid Process ID of Java process for which MBeanServerConnection is
 *    desired.
 * @return MBeanServerConnection connecting to Java process identified by pid.
 */
def static MBeanServerConnection retrieveServerConnection(String pid)
{
   def connectorAddressStr = "com.sun.management.jmxremote.localConnectorAddress"
   def jmxUrl = retrieveUrlForPid(pid, connectorAddressStr)
   def jmxConnector = JMXConnectorFactory.connect(jmxUrl)
   return jmxConnector.getMBeanServerConnection()
}
 
/**
 * Provide JMX URL for attaching to the provided process ID (pid).
 *
 * @param @pid Process ID for which JMX URL is needed to connect.
 * @param @connectorAddressStr String for connecting.
 * @return JMX URL to communicating with Java process identified by pid.
 */
def static JMXServiceURL retrieveUrlForPid(String pid, String connectorAddressStr)
{
   // Attach to the target application's virtual machine
   def vm = VirtualMachine.attach(pid)
 
   // Obtain Connector Address
   def connectorAddress =
      vm.getAgentProperties().getProperty(connectorAddressStr)
 
   // Load Agent if no connector address is available
   if (connectorAddress == null)
   {
      def agent = vm.getSystemProperties().getProperty("java.home") +
          File.separator + "lib" + File.separator + "management-agent.jar"
      vm.loadAgent(agent)
 
      // agent is started, get the connector address
      connectorAddress =
         vm.getAgentProperties().getProperty(connectorAddressStr)
   }
 
   return new JMXServiceURL(connectorAddress);
}

Наиболее важный метод, определенный в JmxServer.groovy , с точки зрения клиента сценария, — это метод retrieveServerConnection(String) . Этот метод исключает pid в качестве идентификатора процесса и использует его и вызов другого метода retrieveUrlForPid(String,String) для подключения к процессу Java, идентифицированному этим pid, и предоставления связанного экземпляра MBeanServerConnection . Самыми простыми подходами для поиска подходящего Java-pid являются использование jps (до Java 7 и Java 7) или jcmd (только Java 7+), как я кратко описал в своей недавней публикации « Загрузка AMX в GlassFish 3 с помощью Groovy» .

Инкапсулировав логику для получения конкретного экземпляра MBeanServerConnection JVM (процесса Java) на основе предоставленного pid, легко начать создавать простые скрипты Groovy, которые используют этот предоставленный MBeanServerConnection чтобы предоставить конкретные желательные подробности о рассматриваемом JVM (процессе Java). Обратите внимание, что все эти примеры сценариев находятся в том же каталоге, что и файл JmxServer.groovy и поэтому не нужно явно указывать детали пакета или области видимости.

Обычно требуется знать, что находится на пути к классам конкретной JVM по различным причинам, включая обнаружение причин ClassNotFoundException и NoClassDefFoundError . Путь к классу конкретной JVM предоставляется одним из его платформ MBean ( атрибут ClassPath в RuntimeMXBean ) и может быть легко обнаружен с помощью следующего скрипта Groovy:

displayClasspath.groovy

1
2
3
4
5
#!/usr/bin/env groovy
def pid = args[0]
def javaRuntime = new javax.management.ObjectName('java.lang:type=Runtime')
def classpath = JmxServer.retrieveServerConnection(pid).getAttribute(javaRuntime, 'ClassPath')
println '\nClasspath for Java pid (${pid}):\n${classpath}\n'

Тот же RuntimeMXBean также предоставляет путь к загрузочному классу и путь к библиотеке Java . В следующих двух листингах кода показаны скрипты Groovy для доступа к обоим из них.

displayBootClasspath.groovy

1
2
3
4
5
#!/usr/bin/env groovy
def pid = args[0]
def javaRuntime = new javax.management.ObjectName('java.lang:type=Runtime')
def bootClasspath = JmxServer.retrieveServerConnection(pid).getAttribute(javaRuntime, 'BootClassPath')
println '\nBoot Classpath for Java pid (${pid}):\n${bootClasspath}\n'

displayLibraryPath.groovy

1
2
3
4
5
#!/usr/bin/env groovy
def pid = args[0]
def javaRuntime = new javax.management.ObjectName('java.lang:type=Runtime')
def libraryPath = JmxServer.retrieveServerConnection(pid).getAttribute(javaRuntime, 'LibraryPath')
println '\nLibrary Path for Java pid (${pid}):\n${libraryPath}\n'

Сценарии Groovy, показанные до сих пор для отображения деталей, связанных с классами поиска (classpath, boot classpath и путь к библиотеке), по сути, являются одним и тем же сценарием, но с каждым атрибутом, который RuntimeMXBean в RuntimeMXBean в каждом случае. Все три сценария выполняются для запущенного экземпляра GlassFish в следующем снимке экрана. Снимок включает команды jps и jcmd для определения pid (5352) для экземпляра GlassFish.

RuntimeMXBean может предложить больше, чем просто информацию о том, откуда были загружены классы. Он также включает атрибуты, связанные с поставщиком JVM, такие как имя и версия , но другие три метода, которые мне нравятся, чтобы использовать сценарии, это входные аргументы , системные свойства и время работы JVM (и время запуска). В следующих трех списках кодов Groovy показаны три сценария для отображения этих сведений, а за каждым фрагментом кода следует снимок экрана, демонстрирующий этот сценарий в действии с тем же экземпляром GlassFish, который использовался в последних примерах.

displayInputArguments.groovy

1
2
3
4
def pid = args[0]
def javaRuntime = new javax.management.ObjectName('java.lang:type=Runtime')
def inputArguments = JmxServer.retrieveServerConnection(pid).getAttribute(javaRuntime, 'InputArguments')
println '\nInput Arguments for Java pid (${pid}):\n${inputArguments}\n'

displaySystemProperties.groovy

1
2
3
4
5
#!/usr/bin/env groovy
def pid = args[0]
def javaRuntime = new javax.management.ObjectName('java.lang:type=Runtime')
def systemProperties = JmxServer.retrieveServerConnection(pid).getAttribute(javaRuntime, 'SystemProperties')
println '\nSystem Properties for Java pid (${pid}):\n${systemProperties}\n'

displayJvmUptime.groovy

1
2
3
4
5
6
7
#!/usr/bin/env groovy
def pid = args[0]
def javaRuntime = new javax.management.ObjectName('java.lang:type=Runtime')
def server = JmxServer.retrieveServerConnection(pid)
def startTime = server.getAttribute(javaRuntime, 'StartTime')
def uptime = server.getAttribute(javaRuntime, 'Uptime')
println '\nJava process pid (${pid}) was started at ${new Date(startTime)} and has been up for ${uptime} ms.\n'

Я использовал этот пост для демонстрации простых скриптов Groovy, которые получают информацию из RuntimeMXBean платформы JVM, такую ​​как время запуска и время работы JVM, системные свойства и входные аргументы для процесса JVM, а также информацию о пути, такую ​​как classpath, boot classpath и library путь. Хотя эта информация доступна через такие инструменты, как JConsole, VisualVM и Java IDE (все они получают из того же источника, что и эти сценарии!), Сценарии иногда могут быть полезны (быстрее запускаться и могут быть включены в другие сценарии, например).

Ссылка: Мониторинг ключевых характеристик JVM с помощью Groovy, JMX и RuntimeMXBean от нашего партнера по JCG Дастина Маркса из блога Inspired by Actual Events .