JMX Hello World
Рубрика: Java | 8 November 2007, 15:41 | juriy
Давно хотел набросать пару строк про JMX, но никак не доходили руки. Теперь, как говорится, жизнь заставила.
В первый раз я работал с этой технологией в проекте, где обязательным требованием было использование Java 1.4. А в этой версии, как известно, встроенной поддержки JMX нету. Благодаря этому факту, и еще нескольким нюансам у меня сложилось впечатление, что JMX это до ужаса сложная штуковина и работать с ней нормальный человек все равно не сможет.
На самом деле в JMX нету абсолютно ничего страшного. На то чтобы начать использовать эту технологию в своем проекте потребуется не больше трех часов. А с моей заметкой – максимум 40 минут ;-).
Итак, начнем сначала. JMX – Java Management Extensions – API при помощи которого можно контролировать работу приложений и управлять различными параметрами удаленно в реальном времени. JMX вошла в поставку Java начиная с версии 5.
Начнем с простой демонстрации. Выполняем следующие действия:
>cd %JAVA_HOME%/demo/jfc/Notepad
>java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8085 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -jar Notepad.jar
Не пугайтесь изобилия параметров, они не страшные, если вчитаться в имена. Мы указываем виртуальной машине, что необходимо “подключить” поддержку JMX, использовать для общения порт 8085, отключить аутентификацию и ssl.
Должен запуститься пример из поставки Java – блокнот, написанный с использованием SWING. Заметим, что в самом приложении Notepad нет ни строки кода, который работает с JMX.
Во втором терминале, или вообще на другом компьютере, запускаем jconsole. Эта утилита тоже входит в стандартную поставку и служит для работы с JMX серверами (а запущенный блокнот как раз таковым и является, благодаря установленным параметрам). В Java 6 jconsole стала намного удобнее и приятнее на вид, так что, если есть возможность, я бы посоветовал использовать именно ее, а не jconsole из версии Java 5.
В открывшемся окне выбираем Remote Process и вводим localhost:8085. Для тех, кто использует Java 5: заходим на вкладку Remote, указываем host: localhost, port: 8085.
Jconsole теперь должна подключиться к витруальной машине и отобразить массу полезной информации. Подождите немного, пока графики наполнятся реальными данными.
Зайдите на вкладку MBeans и поиграйтесь с параметрами виртуальной машины. Найдите, к примеру, ветку java.lang->ClassLoading->Attributes и посмотрите, сколько классов загрузила виртуальная машина. Во вкладке java.lang->Memory->Operations можно выполнить операцию gc – вызвать на удаленной виртуальной машине garbage collector.
Когда надоест играться – возвращайтесь к чтению, я покажу как вызывать методы и получать свойства собственных классов, а не только стандартных.
Начнем с простого Java приложения и посмотрим, что может дать использование JMX.
Приложение, которое я использовал для теста – сервер, который запускает пул потоков, которые поочередно обрабатывают простые задачи. Иногда задач может быть больше чем свободных потоков, в таком случае некоторые задачи не будут выполняться вовсе. Иногда потоков может оказаться больше чем нужно для выполнения задачи – тогда “лишние” потоки будут остановлены.
Модель приложения похожа на обычный HTTP сервер или сервлет контейнер вроде Tomcat. В Tomcat потоки обрабатывают запросы пользователей, а мой сервер будет обрабатывать работы-болванки.
Вот исходник:
[java]
package com.juriy.server;
import com.juriy.mbeans.*;
import javax.management.*;
import java.util.concurrent.*;
import java.util.*;
import java.lang.management.*;
/**
* @author Yuriy Bura
* @version $Id: $
*/
public class TestServer {
private ThreadPoolExecutor executor;
private int rejected = 0;
public TestServer(ThreadPoolExecutor executor) {
this.executor = executor;
}
private static class Task implements Runnable {
public void run() {
Random random = new Random();
int numLoops = Math.abs(random.nextInt()%5);
for (int i = 0; i < numLoops; i++) {
try {
System.out.println("Running in [" + Thread.currentThread().getName() + "]");
Thread.sleep(Math.abs(random.nextInt()%5000));
} catch(Exception e) {
System.out.println("Interrupted");
}
}
}
}
public void runServer() {
executor.prestartAllCoreThreads();
while (true) {
try {
executor.execute(new Task());
Thread.sleep(1000);
} catch (InterruptedException e) {}
} catch(RejectedExecutionException e) {
rejected++;
}
}
}
public ThreadPoolExecutor getExecutor() {
return executor;
}
public int getRejected() {
return rejected;
}
public void flushRejected() {
rejected = 0;
}
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
3, 5, 10L,
TimeUnit.SECONDS,
new SynchronousQueue < Runnable > ()
);
executor.prestartAllCoreThreads();
TestServer server = new TestServer(executor);
server.runServer();
}
}
[/java]
Обратите внимание: эта программа ничего не делает для работы с JMX.
Основа JMX – это Managed Beans или просто MBeans. Чтобы “опубликовать” методы через JMX необходимо сделать 3 шага:
Шаг 1. Описать интерфейс MBean’а в интерфейсе. По принятым соглашениям интерфейс должен называться InterfaceNameMBean. В нашем примере, стоит задача управления сервером, поэтому интерфейс будет называться ServerControllerMBean.
Вот так будет выглядеть интерфейс:
[java]
package com.juriy.mbeans;
/**
* @author Yuriy Bura
* @version $Id: $
*/
public interface ServerControllerMBean{
public int getCorePoolSize();
public void setCorePoolSize(int corePoolSize);
public int getMaxPoolSize();
public void setMaxPoolSize(int maxPoolSize);
public int getRejectedCount();
public int getActiveThreads();
public int getPassiveThreads();
public int getTotalThreads();
public void flushRejected();
}
[/java]
Как видно, MBean предоставляет полный доступ к двум переменным сorePoolSize и maxPoolSize. Кроме того, есть 4 переменных, которые можно считывать: rejectedCount, activeThreads, passiveThreads, totalThreads и метод flushRejected, который, естественно, можно вызывать.
Второй шаг – написать реализацию интерфейса. Тут тоже нет ничего сверхъестественного:
[java]
package com.juriy.mbeans;
import com.juriy.server.*;
/**
* @author Yuriy Bura
* @version $Id: $
*/
public class ServerController implements ServerControllerMBean {
private TestServer server;
public ServerController(TestServer server) {
this.server = server;
}
public int getCorePoolSize() {
return server.getExecutor().getCorePoolSize();
}
public void setCorePoolSize(int corePoolSize) {
server.getExecutor().setCorePoolSize(corePoolSize);
}
public int getMaxPoolSize() {
return server.getExecutor().getMaximumPoolSize();
}
public void setMaxPoolSize(int maxPoolSize) {
server.getExecutor().setMaximumPoolSize(maxPoolSize);
}
public int getRejectedCount() {
return server.getRejected();
}
public int getActiveThreads() {
return server.getExecutor().getActiveCount();
}
public int getPassiveThreads() {
return server.getExecutor().getPoolSize() - server.getExecutor().getActiveCount();
}
public int getTotalThreads() {
return server.getExecutor().getPoolSize();
}
public void flushRejected() {
server.flushRejected();
}
}
[/java]
Как видно, ServerController это просто обертка вокруг класса TestServer, единственное назначение которой – обеспечить публикацию методов и переменных через JMX.
Третий шаг – зарегистрировать только что созданный MBean. Для этого в метод main добавим такие строки:
[java]
ServerController serverController = new ServerController(server);
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("com.juriy.mbeans:type=ServerController");
mbs.registerMBean(serverController, name);
[/java]
Вот, собственно, и все. Запускаем программку с теми-же параметрами что и блокнот:
>java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8085 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false com.juriy.server.TestServer
Подключаемся к localhost:8085 при помощи jconsole. Во вкладке MBeans выбираем com.juriy.mbeans->ServerController->Attributes. Смотрим на значение rejectedCount и ужасаемся, увеличиваем размер пула. Устанавливаем maxPoolSize = 10 и выполняем flushRejected (com.juriy.mbeans->ServerController->Operations). Убеждаемся, что сервер больше не “отбрасывает” задачи.
Вот и все, задача выполнена минимальными усилиями. Управлять таким сервером можно как с локальной так и с удаленной машины. Обратите внимание: класс сервера так и не узнал о том, что кто-то будет им управлять. Никаких дополнительных GUI утилит для управления сервером писать не пришлось – хватило стандартной jconsole.
В JMX есть еще масса полезных возможносей. То что я показал – только вершина айсберга. Но это уже совсем другая история.
Пример из статьи:
JMX demo application
Чтобы запустить пример, разархивируйте jmxtest.rar и выполните ant run.
Tweet
Классика – JMS is not JMX, проверь текст статьи.
Второе – упорное желание авторов пост-сов пространства (не нашел лучшего слова) навязать rar всему миру. Дело не в том, кто лучше, а в том, кто был первым и какие инструменты доступны. Многие просто не станут скачивать rar, потому что нужет “еще один” распаковщик. Не очень критично в этой статье, но в целом это нужно учитывать.
Да, и добро пожаловать в JMX world :)
Андрей, судя по всему уже поправил опечатки :)
> Обратите внимание: эта программа ничего не делает для работы с JMX.
Первый листинг уже сожержит четвертый листинг.
Спасибо всем за комменты:
> JMS is not JMX
Согласен на 100% – вот так бывает, когда пишешь об одном, а думаешь о другом.
> Первый листинг уже сожержит четвертый листинг.
Поправили, ошибочка вышла, спасибо.
Спасибо. Супер.
Спасибо, очень помогло.
Во вкладке MBeans выбираем com.juriy.mbeans->ServerController->Attributes.
после запуска приложения и подключения к серверу в jconsole во вкладке Mbeans я так и не обнаружил классов com.juriy.mbeans – что я сделал не так ? )
Чтобы их обнаружить, метод main должен выглядеть примерно так:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
3, 5, 10L,
TimeUnit.SECONDS,
new SynchronousQueue ()
);
executor.prestartAllCoreThreads();
TestServer server = new TestServer(executor);
ServerController serverController = new ServerController(server);
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName(“TestServer:name=sevcontrol”);
mbs.registerMBean(serverController, name);
server.runServer();
Т.е. вызов server.runServer(); должен быть в самом конце.
Сам уйму времени убил, чтобы понять в чем ошибка.
я просто оставлю здесь свое спасибо