Viele wichtige Systemdaten und Einstellungen von JVMs und modernen Java EE Application Servern können über MBeans (Managed Beans) per JMX (Java Management Extensions) abgefragt und geändert werden.
Es wird beschrieben, wie eigene MBeans erstellt und MBean-Attribute gelesen und geschrieben werden können und MBean-Methoden aufgerufen werden können, sowohl innerhalb derselben JVM als auch von einem Remote-Client aus.
Remote Remote HTML Viewer, JMX Client JMX Client SNMP Client | | | service:jmx:rmi| JNDI| HTTP, SNMP| ......|..............|..............|............................ | | | Distributed Layer Connector MEJB Adapter | | | ......|..............|..............|............................ | | | Agent Layer Zugriff | | JMX Agent | aus | | | Agent derselben--- MBean Server -- Services JVM | | | | | | ......|..............|..............|............................ | | | Instrumentation Layer MBean A MBean B MBean C | | | Ressource A Ressource B Ressource C
Damit der Java EE Application Server (oder jede andere beliebige JMX-fähige Anwendung) über Remote-Zugriff per JMX angesprochen werden kann, muss die jeweilige Anwendung (oder die darin enthaltene JVM) oft mit bestimmten Kommandozeilenparametern gestartet werden. Außerdem müssen Sie die JMX-Portnummer in Erfahrung bringen (oder definieren).
Erläuterungen zur Sun JVM finden Sie hierzu unter http://docs.oracle.com/javase/7/docs/technotes/guides/management/agent.html und zu JRockit unter http://edocs.bea.com/jrockit/jrdocs/refman/optionX.html.
Beispiele:
Java-Kommandozeilenprogramm:
Bestimmen Sie selbst die JMX-Portnummer, zum Beispiel so:
java -Dcom.sun.management.jmxremote.port=4711 MeinJavaProgramm
GlassFish 2:
Zeigt beim Start auf der Konsole eine Ausgabe ähnlich zu:
Domain listens on at least following ports for connections: [8080 8181 4848 3700 3820 3920 8686 ].
Im Logfile erscheint:
Here is the JMXServiceURL for the Standard JMXConnectorServer: [service:jmx:rmi:///jndi/rmi://localhost:8686/jmxrmi].
In der Webkonsole (http://localhost:4848) finden Sie die Portnummer unter
'Configuration' | 'Admin Service' | 'system' | 'JMX Connector' | 'Port: 8686'.
WebLogic 10 mit Sun JVM:
Starten Sie WebLogic mit einer vorgeschalteten Batchdatei mit zum Beispiel folgenden zwei Zeilen:
set JAVA_OPTIONS=-Djavax.management.builder.initial=weblogic.management.jmx.mbeanserver.WLSMBeanServerBuilder -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=7091 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false
C:\WebLogic\user_projects\domains\MeineDomain\bin\startWebLogic.cmd
Weiteres hierzu finden Sie in der Doku zum -Dcom.sun.management.jmxremote-Parameter.
WebLogic 10 mit JRockit:
Um den Standard-Port zu verwenden, starten Sie WebLogic mit einer vorgeschalteten Batchdatei mit zum Beispiel folgenden zwei Zeilen:
set JAVA_OPTIONS=-Djavax.management.builder.initial=weblogic.management.jmx.mbeanserver.WLSMBeanServerBuilder -Xmanagement:ssl=false,authenticate=false,autodiscovery=true
C:\WebLogic\user_projects\domains\MeineDomain\bin\startWebLogic.cmd
Um einen anderen Port zu konfigurieren, erweitern Sie den -Xmanagement:...-Eintrag zu:
-Xmanagement:ssl=false,authenticate=false,autodiscovery=true,port=7091
Beim Start erscheint auf der Konsole eine Ausgabe ähnlich zu:
Management server started on port 7091 oder
[INFO ][mgmnt ] Remote JMX connector started at address <MeinPC>:7091
Um den konfigurierten Port zu ermitteln, fragen Sie die übergebenen Kommandozeilenparameter ab: Über die WebLogic-Console über <Domainname> | Umgebung | Server | <Servername> | Konfiguration | Serverstart | Argumente.
Weitere Infos finden Sie in der Doku zum -Xmanagement-Parameter.
Grundsätzliches zu JMX mit WebLogic finden Sie in Developing Custom Management Utilities With JMX for Oracle WebLogic Server, The WebLogic Server MBean Reference und Understanding WebLogic Server MBeans.
Remote-Debugging:
Falls Sie den Java EE Application Server remote debuggen wollen, fügen Sie Folgendes Parameter beim Java-Aufruf hinzu:
-agentlib:jdwp=transport=dt_socket,address=8001,server=y,suspend=n
Die aktiven MBeans (Managed Beans) können mit dem JConsole-Programm (jconsole.exe im JDK-bin-Verzeichnis) gelesen und bearbeitet werden. Der Screenshot zeigt beispielsweise einige Attribute der GarbageCollector-MBean:
JConsole kann nicht nur die Standard-JVM-MBeans anzeigen, sondern auch beliebige andere, zum Beispiel Java-EE-Application-Server-spezifische, wie folgender Screenshot für WebLogic 10.3.5 zeigt (ab dem Zweig "com.bea", statt nach "AdminServer" müssen Sie nach Ihrem Servernamen suchen):
Für die drei im Screenshot markierten MBeans "ServerRuntime", "JMSRuntime" und "JTARuntime" werden weiter unten exemplarisch Zugriffsmöglichkeiten beschrieben (siehe JmxMBeanLesenAufrufen, Weblogic10ServerRuntimeMBean und Weblogic8ServerRuntimeMBean).
Falls Sie WebLogic verwenden und den "com.bea"-Zweig nicht angezeigt bekommen, untersuchen Sie Folgendes:
Das folgende Programmierbeispiel geht von folgender Verzeichnisstruktur aus:
[MeinWorkspace] `- [MeinMBeanProjekt] |- [bin] `- [src] `- [meinembeans]
Um eine
"Standard MBean"
zu erzeugen benötigen Sie ein Interface, dessen Name mit "MBean" endet und
worin die Getter- und Setter-Methoden für die MBean-Attribute und die (per invoke) aufrufbaren Methoden deklariert sind.
Erzeugen Sie im meinembeans-Package-Unterverzeichnis folgende MeinMBeanTestMBean.java-Datei:
package meinembeans; // MBean-Interface: public interface MeinMBeanTestMBean { // Getter und Setter für die MBean-Attribute: public String getDatumUndUhrzeit(); public int getMeinReadWriteWert(); public void setMeinReadWriteWert( int neuerWert ); // Per invoke() aufrufbare MBean-Methoden: public Long addiere( Integer x, Integer y ); public long subtrahiere( int x, int y ); }
Die das MBean-Interface implementierende Klasse muss so wie das MBean-Interface heißen, aber ohne die MBean-Endung.
Erzeugen Sie im meinembeans-Package-Unterverzeichnis folgende MeinMBeanTest.java-Datei:
package meinembeans; import java.text.SimpleDateFormat; import java.util.Date; // MBean-Implementierung: public class MeinMBeanTest implements MeinMBeanTestMBean { private int meinReadWriteWert = 0; public String getDatumUndUhrzeit() { String s = (new SimpleDateFormat("yyyy-MM-dd, HH:mm:ss")).format(new Date()) + " h"; System.out.println( "getDatumUndUhrzeit() --> " + s ); return s; } public int getMeinReadWriteWert() { System.out.println( "getMeinReadWriteWert() --> " + meinReadWriteWert ); return meinReadWriteWert; } public void setMeinReadWriteWert( int neuerWert ) { meinReadWriteWert = neuerWert; System.out.println( "\nsetMeinReadWriteWert( " + neuerWert + " )\n" ); } public Long addiere( Integer x, Integer y ) { long z = x.intValue() + y.intValue(); System.out.println( "\naddiere( " + x + ", " + y + " ) --> " + z + "\n" ); return new Long( z ); } public long subtrahiere( int x, int y ) { long z = x - y; System.out.println( "\nsubtrahiere( " + x + ", " + y + " ) --> " + z + "\n" ); return z; } }
Die System.out.println()-Zeilen sind natürlich nur zur besseren Nachvollziehbarkeit während der Entwicklung sinnvoll und sollten in Anwendungen entfernt werden.
Um die MBean zu testen, wird sie erzeugt, registriert und verwendet.
Erzeugen Sie im meinembeans-Package-Unterverzeichnis folgende MBeanTest.java-Datei:
package meinembeans; import java.lang.management.ManagementFactory; import javax.management.*; // MBean-Testprogramm: public class MBeanTest { static final String O_NAME = "meine.mbeans:type=MeineErsteMBean"; public static void main( String[] args ) throws Exception { erzeugeUndRegistriereMBean(); verwendeMBean( 7, 42 ); System.out.println( "\nBitte in JConsole das Programm 'meinembeans.MBeanTest' oeffnen\n" + "und die MBean 'meine.mbeans - MeineErsteMBean' bearbeiten ...\n" + "Anschliessend MBeanTest mit 'Strg+C' beenden.\n" ); Thread.sleep( Long.MAX_VALUE ); } static void erzeugeUndRegistriereMBean() throws Exception { MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); ObjectName oname = new ObjectName( O_NAME ); MeinMBeanTest mbean = new MeinMBeanTest(); mbs.registerMBean( mbean, oname ); } static void verwendeMBean( int x, int y ) throws Exception { MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); ObjectName oname = new ObjectName( O_NAME ); // Schreibe und lies MBean-Attribut: mbs.setAttribute( oname, new Attribute( "MeinReadWriteWert", new Integer( 4711 ) ) ); System.out.println( "verwendeMBean(): MeinReadWriteWert = " + mbs.getAttribute( oname, "MeinReadWriteWert" ) ); // Rufe die "addiere"-MBean-Methode auf (mit "Integer"-Parametern): Long sum = (Long) mbs.invoke( oname, "addiere", new Object[] { new Integer( x ), new Integer( y ) }, new String[] { Integer.class.getName(), Integer.class.getName() } ); System.out.println( "verwendeMBean(): addiere( " + x + ", " + y + " ) = " + sum + "\n" ); // Rufe die "subtrahiere"-MBean-Methode auf (mit "int"-Parametern): Long sub = (Long) mbs.invoke( oname, "subtrahiere", new Object[] { new Integer( x ), new Integer( y ) }, new String[] { int.class.getName(), int.class.getName() } ); System.out.println( "verwendeMBean(): subtrahiere( " + x + ", " + y + " ) = " + sub + "\n" ); } }
Ihre Verzeichnisstruktur sieht jetzt so aus (überprüfen Sie es mit "tree /F"):
[MeinWorkspace] `- [MeinMBeanProjekt] |- [bin] `- [src] `- [meinembeans] |- MBeanTest.java |- MeinMBeanTest.java `- MeinMBeanTestMBean.java
Starten Sie das Testprogramm und JConsole über folgende Kommandozeilenbefehle:
cd \MeinWorkspace\MeinMBeanProjekt tree /F del bin\meinembeans\*.class javac -d bin src/meinembeans/*.java start java -cp bin meinembeans.MBeanTest jconsole
Öffnen Sie in JConsole das Programm "meinembeans.MBeanTest" und klicken Sie auf den Tabulatorreiter "MBeans" und auf die MBean 'meine.mbeans - MeineErsteMBean'. Lesen Sie unter "Attributes" "DatumUndUhrzeit" und ändern Sie "MeinReadWriteWert". Testen Sie unter "Operations" die "addiere"- und "subtrahiere"-Methoden.
Beachten Sie die unterschiedlichen Parametertypen der addiere()- und subtrahiere()-Methoden in MeinMBeanTest und die entsprechend unterschiedlichen Aufrufe in MBeanTest.verwendeMBean(): Werden statt Parameter-Klassen primitive Datentypen verwendet (im Beispiel int statt Integer), dann muss der invoke()-Methode als Object ein Wrapper-Objekt, aber als Signatur-Klassenname der korrekte Klassenname des primitiven Datentyps übergeben werden.
Das MBeanTest-Programmierbeispiel greift auf die MBean innerhalb derselben JVM über die ManagementFactory-Klasse und die MBeanServer.getAttribute()-, setAttribute()- und invoke()-Methoden zu. Einen Remote-Zugriff (aus einer anderen JVM) zeigt die im Folgenden vorgestellte Klasse JmxMBeanLesenAufrufen.
Ein sehr einfaches generisches Programm für JMX-Remote-Zugriff zum Auslesen von MBean-Attributen und Aufrufen von MBean-Methoden könnte folgendermaßen aussehen:
import java.util.*; import javax.management.*; import javax.management.remote.*; import javax.naming.Context; // Per JMX MBean-Attribute auslesen und MBean-Methoden aufrufen public class JmxMBeanLesenAufrufen { @SuppressWarnings("fallthrough") public static void main( String[] args ) throws Exception { String attributeName = "Uptime"; String objectName = "java.lang:type=Runtime"; String url = "localhost:8686"; String usr = null; String pwd = null; String opt = null; switch( Math.min( args.length, 6 ) ) { case 6: opt = args[5]; case 5: pwd = args[4]; case 4: usr = args[3]; case 3: url = args[2]; case 2: objectName = args[1]; case 1: attributeName = args[0]; } JMXConnector jmxConnector = null; try { jmxConnector = getJMXConnector( url, usr, pwd ); MBeanServerConnection mBeanServerConn = jmxConnector.getMBeanServerConnection(); if( opt != null && opt.equals("all") ) showAllObjectNames( mBeanServerConn ); createJRockitConsoleMBean( objectName, mBeanServerConn ); Set<ObjectName> objectNames = mBeanServerConn.queryNames( new ObjectName( objectName ), null ); for( ObjectName on : objectNames ) { if( attributeName.equals("invoke") && args.length > 5 ) { String methodName = args[5]; String[] parms = null; if( args.length > 6 ) { parms = new String[args.length - 6]; System.arraycopy( args, 6, parms, 0, parms.length ); } System.out.println( "" + invoke( methodName, parms, on, mBeanServerConn ) + " = invoke " + methodName + " " + Arrays.toString( parms ) ); } else { Object attr = mBeanServerConn.getAttribute( on, attributeName ); System.out.println( "" + attr + " = " + attributeName + " (" + on + ")" ); } } } finally { if( jmxConnector != null ) jmxConnector.close(); } } // Rufe MBean-Methode auf // parms: paarweise Klasse und Parameterwert, z.B.: // [Integer, 12, Integer, 34] // oder // [int, 12, int, 34] // Die Klassen (bzw. Wrapper-Klassen) muessen einen Konstruktor mit String-Parameter haben static Object invoke( String methodName, String[] parms, ObjectName on, MBeanServerConnection mBeanServerConn ) throws Exception { if( methodName == null || on == null || mBeanServerConn == null ) return null; Object[] oa = null; String[] sa = null; if( parms != null && parms.length >= 2 ) { final Map<String,Class<?>[]> PRIMITIVE_TYPEN = new HashMap<String,Class<?>[]>(); PRIMITIVE_TYPEN.put( "boolean", new Class<?>[] { boolean.class, Boolean.class } ); PRIMITIVE_TYPEN.put( "int", new Class<?>[] { int.class, Integer.class } ); PRIMITIVE_TYPEN.put( "long", new Class<?>[] { long.class, Long.class } ); PRIMITIVE_TYPEN.put( "double", new Class<?>[] { double.class, Double.class } ); oa = new Object[parms.length / 2]; sa = new String[parms.length / 2]; for( int i = 0; i < parms.length - 1; i++ ) { // Sind Parameter primitive Typen? Class<?>[] classForSigAndObj = PRIMITIVE_TYPEN.get( parms[i] ); // Klassen als Parameter-Typen: if( classForSigAndObj == null ) { classForSigAndObj = new Class<?>[2]; try { classForSigAndObj[0] = Class.forName( "java.lang." + parms[i] ); } catch( ClassNotFoundException ex ) { classForSigAndObj[0] = Class.forName( parms[i] ); } classForSigAndObj[1] = classForSigAndObj[0]; } oa[i/2] = classForSigAndObj[1].getConstructor( String.class ).newInstance( parms[++i] ); sa[i/2] = classForSigAndObj[0].getName(); } } return mBeanServerConn.invoke( on, methodName, oa, sa ); } // Zeige alle vorhandenen ObjectNames static void showAllObjectNames( MBeanServerConnection mBeanServerConn ) throws Exception { System.out.println( "\n---- showAllObjectNames ----" ); Iterator<?> itr = mBeanServerConn.queryNames( null, null ).iterator(); while( itr.hasNext() ) System.out.println( itr.next() ); System.out.println( "----------------------------\n" ); } // Vor einer Abfrage von JRockit-Attributen muss die JRockitConsole-MBean instanziiert werden static void createJRockitConsoleMBean( String objectName, MBeanServerConnection mBeanServerConn ) throws Exception { if( objectName == null || !objectName.startsWith( "oracle.jrockit.management" ) ) return; try { mBeanServerConn.getMBeanInfo( new ObjectName( "oracle.jrockit.management:type=JRockitConsole" ) ); } catch( InstanceNotFoundException ex ) { mBeanServerConn.createMBean( "oracle.jrockit.management.JRockitConsole", null ); } } // JMX-Connection static JMXConnector getJMXConnector( String url, String usr, String pwd ) throws Exception { String serviceUrl = "service:jmx:rmi:///jndi/rmi://" + url + "/jmxrmi"; if( usr == null || usr.trim().length() <= 0 || pwd == null || pwd.trim().length() <= 0 ) { return JMXConnectorFactory.connect( new JMXServiceURL( serviceUrl ) ); } Map<String,Object> envMap = new HashMap<String,Object>(); envMap.put( "jmx.remote.credentials", new String[] { usr, pwd } ); envMap.put( Context.SECURITY_PRINCIPAL, usr ); envMap.put( Context.SECURITY_CREDENTIALS, pwd ); return JMXConnectorFactory.connect( new JMXServiceURL( serviceUrl ), envMap ); } }
Während das Auslesen von MBean-Attributen mit "getAttribute( objectName, attributeName )" sehr einfach geht, ist das Aufrufen von MBean-Methoden mit invoke( objectName, operationName, paramsArray, klassenArray ) etwas komplizierter, wenn unterschiedliche Parametertypen und das Weglassen des Packages bei java.lang-Typen ermöglicht werden sollen (z.B. die Typangaben int, Integer, java.lang.Integer). Außer dem ObjectName und dem Methodennamen müssen zwei Arrays übergeben werden. Das erste Array enthält die Parameterobjekte und das zweite die Klassen der Parameter. Damit das Programm generisch durch Kommandozeilenparameter gesteuert werden kann, werden auf der Kommandozeile außer den Parameterwerten auch die Parameterklassennamen übergeben, wie weiter unten im Aufrufbeispiel gezeigt wird ("... invoke ... addiere Integer 12 Integer 34"). Die angegeben Klassen werden über "Class.forName()" gesucht und die Parameterobjekte werden über "Class.getConstructor( String.class ).newInstance( "Parameterwert" )" erzeugt. Dies funktioniert allerdings nur für Klassen, die einen Konstruktor mit String-Parameter haben, wie zum Beispiel String, Boolean, Integer, Long, Double, BigDecimal, SimpleDateFormat etc. Um außer Parameter-Klassen auch primitive Datentypen (boolean, int, long, double) als Parameter zu ermöglichen, ist hierfür eine Sonderbehandlung notwendig, da Class.forName() nicht funktioniert und außerdem der Wert in eine Wrapper-Klasse verpackt werden muss.
Oben wurde vorgestellt, wie die eigene MBean "MeinMBeanTest" über das Testprogramm MBeanTest erstellt und registriert werden kann. Öffnen Sie in dem Verzeichnis von JmxMBeanLesenAufrufen.java ein Kommandozeilenfenster und führen Sie folgende Kommandozeilen aus (am besten in einer Batchdatei), um MBeanTest zu starten, MBean-Methoden auszuführen ("... invoke ... addiere Integer 12 Integer 34") und MBean-Attribute auszulesen (passen Sie den Pfad /MeinWorkspace/MeinMBeanProjekt/bin an):
start java -Dcom.sun.management.jmxremote.port=4711 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -cp /MeinWorkspace/MeinMBeanProjekt/bin meinembeans.MBeanTest javac JmxMBeanLesenAufrufen.java @echo. @echo JmxMBeanLesenAufrufen: @java JmxMBeanLesenAufrufen "invoke" "meine.mbeans:type=MeineErsteMBean" "localhost:4711" "" "" addiere Integer 12 Integer 34 @java JmxMBeanLesenAufrufen "invoke" "meine.mbeans:type=MeineErsteMBean" "localhost:4711" "" "" subtrahiere int 34 int 12 @java JmxMBeanLesenAufrufen "DatumUndUhrzeit" "meine.mbeans:type=MeineErsteMBean" "localhost:4711" @java JmxMBeanLesenAufrufen "MeinReadWriteWert" "meine.mbeans:type=MeineErsteMBean" "localhost:4711"
Natürlich können auch JVM-MXBeans ausgelesen werden:
@java JmxMBeanLesenAufrufen "Uptime" "java.lang:type=Runtime" "localhost:4711" @java JmxMBeanLesenAufrufen "ProcessCpuTime" "java.lang:type=OperatingSystem" "localhost:4711" @java JmxMBeanLesenAufrufen "AvailableProcessors" "java.lang:type=OperatingSystem" "localhost:4711"
Die Ausgabe kann zum Beispiel so aussehen:
46 = invoke addiere [Integer, 12, Integer, 34] 22 = invoke subtrahiere [int, 34, int, 12] 2009-01-01, 01:23:45 h = DatumUndUhrzeit (meine.mbeans:type=MeineErsteMBean) 4711 = MeinReadWriteWert (meine.mbeans:type=MeineErsteMBean) 2166 = Uptime (java.lang:type=Runtime) 405602600 = ProcessCpuTime (java.lang:type=OperatingSystem) 8 = AvailableProcessors (java.lang:type=OperatingSystem)
Mit folgenden Kommandos (am besten in einer Batchdatei) können Sie beispielsweise Runtime-, Garbage-Collection-, CPU-Last-, Transaktions-, JMS- und andere Attribute auslesen. Bitte passen Sie die anfänglichen set ...-Parameter an (URL = Url inkl. Portnummer, SRVNAM = vergebener Name des Servers, APPSRV = BEA falls WebLogic, JVM = Sun oder JRockit, JDKBIN = Pfad zum JDK-bin-Verzeichnis) und fügen Sie falls erforderlich Benutzername und Kennwort hinzu:
@echo. @setlocal set URL=localhost:7091 set SRVNAM=AdminServer set APPSRV=BEA set JVM=JRockit set JDKBIN=C:\Programme\Java\jdk1.6\bin @set PATH=%JDKBIN%;%PATH% @set CLASSPATH=. @echo. javac JmxMBeanLesenAufrufen.java @echo. @java JmxMBeanLesenAufrufen "Uptime" "java.lang:type=Runtime" "%URL%" @java JmxMBeanLesenAufrufen "ProcessCpuTime" "java.lang:type=OperatingSystem" "%URL%" @java JmxMBeanLesenAufrufen "AvailableProcessors" "java.lang:type=OperatingSystem" "%URL%" @java JmxMBeanLesenAufrufen "CollectionCount" "java.lang:type=GarbageCollector,*" "%URL%" @java JmxMBeanLesenAufrufen "CollectionTime" "java.lang:type=GarbageCollector,*" "%URL%" @if not "%JVM%" == "JRockit" goto noJRockit @java JmxMBeanLesenAufrufen "VMGeneratedCPULoad" "oracle.jrockit.management:type=Runtime" "%URL%" @java JmxMBeanLesenAufrufen "CPULoad" "oracle.jrockit.management:type=Runtime" "%URL%" :noJRockit @if not "%APPSRV%" == "BEA" goto noBea @java JmxMBeanLesenAufrufen "State" "com.bea:Name=%SRVNAM%,Type=ServerRuntime" "%URL%" @java JmxMBeanLesenAufrufen "ListenAddress" "com.bea:Name=%SRVNAM%,Type=ServerRuntime" "%URL%" @java JmxMBeanLesenAufrufen "ListenPort" "com.bea:Name=%SRVNAM%,Type=ServerRuntime" "%URL%" @java JmxMBeanLesenAufrufen "DefaultURL" "com.bea:Name=%SRVNAM%,Type=ServerRuntime" "%URL%" @java JmxMBeanLesenAufrufen "ActivationTime" "com.bea:Name=%SRVNAM%,Type=ServerRuntime" "%URL%" @java JmxMBeanLesenAufrufen "ActiveTransactionsTotalCount" "com.bea:ServerRuntime=%SRVNAM%,Name=JTARuntime,Type=JTARuntime" "%URL%" @java JmxMBeanLesenAufrufen "TransactionTotalCount" "com.bea:ServerRuntime=%SRVNAM%,Name=JTARuntime,Type=JTARuntime" "%URL%" @java JmxMBeanLesenAufrufen "ConnectionsTotalCount" "com.bea:ServerRuntime=%SRVNAM%,Name=%SRVNAM%.jms,Type=JMSRuntime" "%URL%" @java JmxMBeanLesenAufrufen "JMSServersTotalCount" "com.bea:ServerRuntime=%SRVNAM%,Name=%SRVNAM%.jms,Type=JMSRuntime" "%URL%" :noBea @echo. @endlocal
Falls Sie Teilausdrücke nicht wissen, können Sie "*" als Joker verwenden. Zum Beispiel so, falls Sie den Servernamen nicht wissen:
@echo. @setlocal set URL=localhost:7091 set APPSRV=BEA set JVM=JRockit set JDKBIN=C:\Programme\Java\jdk1.6\bin @set PATH=%JDKBIN%;%PATH% @set CLASSPATH=. @echo. javac JmxMBeanLesenAufrufen.java @echo. @java JmxMBeanLesenAufrufen "Uptime" "java.lang:type=Runtime" "%URL%" @java JmxMBeanLesenAufrufen "ProcessCpuTime" "java.lang:type=OperatingSystem" "%URL%" @java JmxMBeanLesenAufrufen "AvailableProcessors" "java.lang:type=OperatingSystem" "%URL%" @java JmxMBeanLesenAufrufen "CollectionCount" "java.lang:type=GarbageCollector,*" "%URL%" @java JmxMBeanLesenAufrufen "CollectionTime" "java.lang:type=GarbageCollector,*" "%URL%" @if not "%JVM%" == "JRockit" goto noJRockit @java JmxMBeanLesenAufrufen "VMGeneratedCPULoad" "oracle.jrockit.management:type=Runtime" "%URL%" @java JmxMBeanLesenAufrufen "CPULoad" "oracle.jrockit.management:type=Runtime" "%URL%" :noJRockit @if not "%APPSRV%" == "BEA" goto noBea @java JmxMBeanLesenAufrufen "State" "com.bea:Type=ServerRuntime,*" "%URL%" @java JmxMBeanLesenAufrufen "ListenAddress" "com.bea:Type=ServerRuntime,*" "%URL%" @java JmxMBeanLesenAufrufen "ListenPort" "com.bea:Type=ServerRuntime,*" "%URL%" @java JmxMBeanLesenAufrufen "DefaultURL" "com.bea:Type=ServerRuntime,*" "%URL%" @java JmxMBeanLesenAufrufen "ActivationTime" "com.bea:Type=ServerRuntime,*" "%URL%" @java JmxMBeanLesenAufrufen "ActiveTransactionsTotalCount" "com.bea:Name=JTARuntime,Type=JTARuntime,*" "%URL%" @java JmxMBeanLesenAufrufen "TransactionTotalCount" "com.bea:Name=JTARuntime,Type=JTARuntime,*" "%URL%" @java JmxMBeanLesenAufrufen "ConnectionsTotalCount" "com.bea:Type=JMSRuntime,*" "%URL%" @java JmxMBeanLesenAufrufen "JMSServersTotalCount" "com.bea:Type=JMSRuntime,*" "%URL%" :noBea @echo. @endlocal
Das Ergebnis könnte zum Beispiel für WebLogic 10.3.5 mit JRockit-JVM Folgendes beinhalten:
19311 = Uptime (java.lang:type=Runtime) 250849608 = ProcessCpuTime (java.lang:type=OperatingSystem) 8 = AvailableProcessors (java.lang:type=OperatingSystem) 30 = CollectionCount (java.lang:type=GarbageCollector,name=Garbage collection optimized for throughput Young Collector) 5 = CollectionCount (java.lang:type=GarbageCollector,name=Garbage collection optimized for throughput Old Collector) 986 = CollectionTime (java.lang:type=GarbageCollector,name=Garbage collection optimized for throughput Young Collector) 321 = CollectionTime (java.lang:type=GarbageCollector,name=Garbage collection optimized for throughput Old Collector) 0.17 = VMGeneratedCPULoad (oracle.jrockit.management:type=Runtime) 0.48 = CPULoad (oracle.jrockit.management:type=Runtime) RUNNING = State (com.bea:Name=AdminServer,Type=ServerRuntime) MeinPC/10.3.33.85 = ListenAddress (com.bea:Name=AdminServer,Type=ServerRuntime) 7001 = ListenPort (com.bea:Name=AdminServer,Type=ServerRuntime) t3://10.3.33.85:7001 = DefaultURL (com.bea:Name=AdminServer,Type=ServerRuntime) 1224695576725 = ActivationTime (com.bea:Name=AdminServer,Type=ServerRuntime) 9 = ActiveTransactionsTotalCount (com.bea:ServerRuntime=AdminServer,Name=JTARuntime,Type=JTARuntime) 640 = TransactionTotalCount (com.bea:ServerRuntime=AdminServer,Name=JTARuntime,Type=JTARuntime) 1 = ConnectionsTotalCount (com.bea:ServerRuntime=AdminServer,Name=AdminServer.jms,Type=JMSRuntime) 9 = JMSServersTotalCount (com.bea:ServerRuntime=AdminServer,Name=AdminServer.jms,Type=JMSRuntime)
Bitte beachten Sie folgende Versionsabhängigkeiten:
a) "oracle.jrockit.management": Dieser String lautete in den BEA-WebLogic-Version
(vor der Übernahme von BEA durch Oracle) "bea.jrockit.management".
b) "com.bea:...": Falls Sie diese Attribute nicht erhalten:
Ab WebLogic 10.3 müssen Sie beim WebLogic-Start den JAVA_OPTIONS-Parameter
-Djavax.management.builder.initial=weblogic.management.jmx.mbeanserver.WLSMBeanServerBuilder setzen,
siehe Startparameter.
Außerdem muss "PlatformMBeanServerUsed=True" gesetzt sein, siehe oben.
Der erste Programm-Kommandozeilenparameter bezeichnet den "MBean-AttributeNamen" und der zweite den "MBean-ObjectNamen". Sie finden die benötigten Namen in JConsole unter dem Tabulatorreiter "MBeans": Im linken Tree-Fenster öffnen Sie die gewünschte MBean. Mit der Java-5-JConsole schauen Sie in die Tabulatorreiter "Attributes" und "Info". Mit der Java-6-JConsole finden Sie die Informationen, wenn Sie auf den Typnamen bzw. auf "Attributes" klicken.
Bitte beachten Sie: Zur Abfrage der Standard-JVM-MBeans muss "name=" und "type=" in den ObjectNamen klein geschrieben werden. Für die Application-Server-spezifischen WebLogic-MBeans müssen "Name=" und "Type=" dagegen groß geschrieben werden.
In den Beispielen werden nur wenige MBeans und nur wenige Attribute ausgelesen. Natürlich gibt es viel mehr Interessantes auszulesen. Außerdem können einige MBean-Attribute nicht nur gelesen werden, sondern auch geschrieben werden und es können Methoden aufgerufen werden (über MBeanServerConnection.setAttribute() und invoke()).
Dieses Programm hat allerdings einige Schwächen:
Nagios ist ein sehr mächtiges Monitoring-Tool,
mit dem Server, Netzwerke und vieles mehr überwacht werden können.
Dabei nimmt Nagios in der Regel nicht selbst die Messungen vor, sondern stellt das übergreifende Framework dar und überlässt die einzelnen vielfältigen Prüfungen und Messungen Plug-ins.
Doku zu Nagios gibt es als
HTML und
PDF sowie in
Deutsch.
Falls Sie selbst ein Nagios-Plug-in schreiben wollen, müssen Sie sich die "Nagios plug-in development guidelines" ansehen. Die Vorgaben sind einfach. Die wichtigsten Regeln für das Plug-in-Skript bzw. ausführbare Programm sind:
Mit NRPE (Nagios Remote Plugin Executor) ist es möglich, Nagios-Plug-ins auf entfernten Rechnern auszuführen. Siehe hierzu das NRPE-Nagios-Wiki.
Um über JMX lesbare Attribute in Nagios erfassen zu können, gibt es verschiedene Plug-ins, zum Beispiel check_jmx / JMXQuery.
Im Folgenden wird beschrieben, wie Sie check_jmx / JMXQuery auch ohne Nagios testen können (auch unter Windows).
Downloaden Sie check_jmx.zip bzw. check_jmx.tgz und entzippen Sie das Archiv.
Öffnen Sie im check_jmx/jmxquery/nagios/plugin-Verzeichnis ein Kommandozeilenfenster.
Lassen Sie sich die Hilfe zu den Kommandozeilenparametern anzeigen:
java -cp jmxquery.jar org.nagios.JMXQuery -help
Starten Sie ein Java-Programm inklusive JMX-Freigabe, zum Beispiel obiges MBeanTest (passen Sie den Pfad /MeinWorkspace/MeinMBeanProjekt/bin an):
start java -Dcom.sun.management.jmxremote.port=4711 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -cp /MeinWorkspace/MeinMBeanProjekt/bin meinembeans.MBeanTest
Einfache Abfragen (ohne Schwellwertüberwachung) können folgendermaßen ausgeführt werden (bitte URL und Portnummer anpassen) (die beiden letzten MeineErsteMBean-Abfragen nur mit MBeanTest):
java -cp jmxquery.jar org.nagios.JMXQuery -U service:jmx:rmi:///jndi/rmi://localhost:4711/jmxrmi -O java.lang:type=Runtime -A Uptime java -cp jmxquery.jar org.nagios.JMXQuery -U service:jmx:rmi:///jndi/rmi://localhost:4711/jmxrmi -O java.lang:type=OperatingSystem -A ProcessCpuTime java -cp jmxquery.jar org.nagios.JMXQuery -U service:jmx:rmi:///jndi/rmi://localhost:4711/jmxrmi -O java.lang:type=OperatingSystem -A AvailableProcessors java -cp jmxquery.jar org.nagios.JMXQuery -U service:jmx:rmi:///jndi/rmi://localhost:4711/jmxrmi -O meine.mbeans:type=MeineErsteMBean -A MeinReadWriteWert java -cp jmxquery.jar org.nagios.JMXQuery -U service:jmx:rmi:///jndi/rmi://localhost:4711/jmxrmi -O meine.mbeans:type=MeineErsteMBean -A DatumUndUhrzeit
Die check_jmx-Readme.txt nennt folgende Abfrage inklusive Schwellwertüberwachung als Beispiel (ändern Sie die "-w"- und "-c"-Schwellwerte, um die verschiedenen Stati angezeigt zu bekommen):
java -cp jmxquery.jar org.nagios.JMXQuery -U service:jmx:rmi:///jndi/rmi://localhost:4711/jmxrmi -O java.lang:type=Memory -A HeapMemoryUsage -K used -I HeapMemoryUsage -J used -vvvv -w 10000000 -c 100000000
(Es ist allerdings fraglich, ob die vollständige Ausnutzung des angebotenen Heap-Speichers wirklich als kritisch gewertet werden kann.)
Die Ausgabe kann zum Beispiel so aussehen:
JMX OK Uptime=8183446 JMX OK ProcessCpuTime=995301760 JMX OK AvailableProcessors=4 JMX OK MeinReadWriteWert=4711 JMX CRITICAL For input string: "2009-01-01, 01:23:45 h" JMX CRITICAL HeapMemoryUsage.used=824547672{committed=2097152000;init=2097152000;max=2097152000;used=824547672}
Mit dem folgendem Programm "MeinBoesesMemoryLeak" können Sie hohen temporären Speicherverbrauch (häufige String-Concatenation bei meinString += i) und ein stetig anwachsendes Memory Leak (in meineListe) simulieren. Passen Sie den Schleifenzählerwert "i<1000" so an, dass das Programm genügend lange (z.B. 6 Minuten) läuft, bevor es sich mit OutOfMemoryError verabschiedet. Die Pause von 5 Sekunden zu Beginn ("Thread.sleep(5000)") dient nur dazu, schnell genug Monitoring-Programme aktivieren zu können.
import java.lang.management.*; import java.util.*; import javax.management.*; public class MeinBoesesMemoryLeak { long startUptime = -1; long startCpuTime = -1; public static void main( String[] args ) throws Exception { (new MeinBoesesMemoryLeak()).run(); } // Memory-Leak-Schleife: public void run() throws Exception { Thread.sleep( 5000 ); String meinString = ""; List<String> meineListe = new ArrayList<String>(); while( true ) { for( int i=0; i<1000; i++ ) { meinString += i; } meineListe.add( meinString ); long meinVerbrauchterSpeicher = 0; for( String str : meineListe ) { meinVerbrauchterSpeicher += str.length(); } System.out.println("Verbrauchter Speicher: " + (meinVerbrauchterSpeicher / 1024) + " KByte; Laenge letzter String / 1000: " + (meinString.length() / 1000) ); showGarbageCollection(); showCpuPercent(); } } // Anzeige der GarbageCollection: void showGarbageCollection() throws Exception { List<GarbageCollectorMXBean> gcList = ManagementFactory.getGarbageCollectorMXBeans(); for( Iterator<GarbageCollectorMXBean> iterator = gcList.iterator(); iterator.hasNext(); ) { GarbageCollectorMXBean gc = iterator.next(); System.out.println( "GarbageCollection: Count=" + gc.getCollectionCount() + ", Time=" + gc.getCollectionTime() + " (" + gc.getName() + ")" ); } } // Anzeige der Auslastung der CPUs: void showCpuPercent() throws Exception { RuntimeMXBean rt = ManagementFactory.getRuntimeMXBean(); OperatingSystemMXBean op = ManagementFactory.getOperatingSystemMXBean(); MBeanServer ms = ManagementFactory.getPlatformMBeanServer(); Long cpuTime = (Long) ms.getAttribute( new ObjectName( "java.lang:type=OperatingSystem" ), "ProcessCpuTime" ); if( startUptime == -1 && startCpuTime == -1 ) { startUptime = rt.getUptime(); startCpuTime = cpuTime.longValue(); } else { long cpuPercent = (cpuTime.longValue() - startCpuTime) / ((rt.getUptime() - startUptime) * op.getAvailableProcessors() * 10000); System.out.println( "CPU-Anzahl=" + op.getAvailableProcessors() + ", CPU-Last-Prozent=" + cpuPercent + "%" ); } } }
Erzeugen Sie MeinBoesesMemoryLeak.java in einem src-Unterverzeichnis, erzeugen Sie ein bin-Unterverzeichnis und kompilieren Sie die Sourcen:
javac -d bin src/MeinBoesesMemoryLeak.java
Starten Sie dieses Memory-Leak-Programm und JConsole kurz hintereinander mit folgenden Kommandos:
start java -Xmx64M -Dcom.sun.management.jmxremote.port=4711 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -cp bin MeinBoesesMemoryLeak
start jconsole -interval=1 localhost:4711
Vergleichen Sie die von MeinBoesesMemoryLeak gemeldeten Werte mit den in JConsole angezeigten.
Während bei diesem einfachen Programmierbeispiel die Garbage-Collection-Summenwerte und die gemittelte prozentuale CPU-Auslastung seit Programmstart gemessen werden, was bei langen Programmläufen immer weniger Sinn macht, finden Sie im Folgenden Programmierbeispiele, wie diese und weitere Kennwerte über definierbare Zeiträume periodisch gemessen werden können, um zum Beispiel Java EE Application Server zu monitoren. Dort wird auch MeinBoesesMemoryLeak in etwas abgewandelten Formen erneut verwendet.
Einfache Monitoring-Tools wie zum Beispiel JConsole können viele Kennwerte nur als Summe oder als Mittelwert seit Programmstart wiedergeben. Für langlaufende Programme wie zum Beispiel Java EE Application Server haben diese Werte kaum Aussagekraft und können nicht Änderungen in den letzten Minuten oder Stunden melden, was sie für aktuelles Monitoring unbrauchbar macht.
Wesentlich interessanter ist, wenn Kennwerte wie die prozentualen Garbage Collection, die CPU-Auslastung, die Anzahl der Transaktionen und weitere Kennwerte über periodische Zeiträume gemessen werden, zum Beispiel periodisch über die jeweils letzten 10 Minuten.
Unter JmxServerMonitoring finden Sie ein Programmierbeispiel, welches JMX-Abfragen und periodische Messungen von einem Remote-Client aus durchführt. Das Programm setzt einen aktivierten JMX-Port voraus. Es ist sehr generisch, kann beliebige per JMX zugängliche Werte abrufen, sowohl als Absolutwert, als auch als Differenz zu vorherigen Werten. Außerdem kann es, was in größeren Serverumgebungen wichtig ist, mehrere Server (z.B. im Cluster) gleichzeitig überwachen.
Anders als JmxServerMonitoring ist das folgende Programmierbeispiel kein Remote-Client, sondern wird als Servlet im zu überwachenden Java EE Application Server installiert. Es ist wesentlich einfacher als JmxServerMonitoring und erfasst nur wenige fest programmierte aus MBeans auslesbare Werte (darunter die wichtige prozentuale Garbage Collection, siehe hierzu das Amdahlsche Gesetz). Das Programmierbeispiel dient hauptsächlich dazu, das Prinzip zu verdeutlichen.
Bitte beachten Sie, dass die Anzahl der CPU-Kerne keine fixe Größe ist:
Die Anzahl der verwendeten CPUs kann niedriger als die Anzahl der vorhandenen CPUs eingestellt werden.
Diese Einstellung kann zum Beispiel für den PC im BIOS,
für Linux in die Boot-Parameterliste über maxcpus=...
und für Windows über msconfig/Systemkonfiguration | Start | Erweiterte Optionen... | Prozessoranzahl verändert werden.
Noch wichtiger: Sie kann sogar im laufenden Betrieb für einzelne Prozesse geändert werden,
zum Beispiel unter Windows im Task-Manager | Prozesse | rechte Maustaste auf Prozess | Zugehörigkeit festlegen...
Die CPU-Anzahl wird deshalb im folgenden Programmierbeispiel für jede Messung neu ermittelt.
Um den Buildvorgang zu vereinfachen und einfache Tests per Jetty zu ermöglichen, wird Maven verwendet.
Sie können das Programmierbeispiel als Zipdatei downloaden oder die im Folgenden beschriebenen Schritte durchführen.
Installieren Sie Maven wie beschrieben unter maven.htm#Installation.
Öffnen Sie ein Kommandozeilenfenster ('Windows-Taste' + 'R', 'cmd'), wechseln Sie in Ihr Projekte-Verzeichnis (z.B. D:\MeinWorkspace) und erzeugen Sie eine Webapp-Projektstruktur:
cd \MeinWorkspace
md MBeanServlet
cd MBeanServlet
md src\main\java\de\meinefirma\meinprojekt
md src\main\webapp\WEB-INF
tree /F
Sie erhalten:
[\MeinWorkspace\MBeanServlet] '- [src] '- [main] |- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] '- [webapp] '- [WEB-INF]
Erzeugen Sie im MBeanServlet-Projektverzeichnis die Projektkonfiguration: pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>MBeanServlet</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>MBeanServlet</name> <build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>9.2.6.v20141205</version> <configuration> <webAppConfig> <contextPath>/${project.artifactId}</contextPath> </webAppConfig> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>javaee</groupId> <artifactId>javaee-api</artifactId> <version>5</version> <scope>provided</scope> </dependency> </dependencies> </project>
Erzeugen Sie im src\main\webapp\WEB-INF-Verzeichnis die Web-App-Konfiguration: web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>MBeanServlet</display-name> <context-param> <description>Name der Logdatei (im CSV-Format)</description> <param-name>GcCpuTxLogFile</param-name> <param-value>GcCpuTxLog.csv</param-value> </context-param> <context-param> <description>Messintervall: Fuer Produktion z.B. 600 Sekunden, fuer Tests z.B. 10 Sekunden</description> <param-name>GcCpuTxMeasurePeriodSec</param-name> <param-value>10</param-value> </context-param> <listener> <listener-class>de.meinefirma.meinprojekt.GcCpuTxMBeanServletContextListener</listener-class> </listener> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
Erzeugen Sie im src\main\webapp-Verzeichnis die Webseite index.jsp (wird nur für Test benötigt):
<%@ page import="java.text.*" %> <%@ page import="java.util.*" %> <%@ page import="de.meinefirma.meinprojekt.GcCpuTxMBeanTimerTask" %> <%! /** Memory-Leak-Schleife. */ public void meinBoesesMemoryLeak( String speed, JspWriter out ) throws Exception { int n = Math.min( 100, Math.max( 1, Integer.parseInt( speed ) ) ); String meinString = ""; List<String> meineListe = new ArrayList<String>(); long time = System.currentTimeMillis() + 10000; out.println( "Zeit; CPUs; MemTotalMB; MemUsedMB; Garbage Collection; CPU-Auslastung<br>" ); while( true ) { for( int i=0; i<1000/n; i++ ) meinString += i; meineListe.add( meinString ); if( System.currentTimeMillis() > time ) { time = System.currentTimeMillis() + 10000; GcCpuTxMBeanTimerTask mbtt = (GcCpuTxMBeanTimerTask) getServletConfig().getServletContext().getAttribute( GcCpuTxMBeanTimerTask.class.getName() ); String[] ss = mbtt.getResult().split( ";" ); out.println( ( ss.length <= 5 ) ? mbtt.getResult() : (ss[0] + "; " + ss[1] + " CPUs; " + ss[2] + " MB; " + ss[3] + " MB; " + ss[4] + " % GC; " + ss[5] + " % CPU <br>") ); out.flush(); } } } %> <% String start = request.getParameter( "start" ); String speed = request.getParameter( "speed" ); if( speed == null ) speed = "10"; %> <html> <head><title>Garbage-Collection-Test</title></head> <body> <h2>Garbage-Collection-Test</h2> <form name="meinFormular" method="post"> Geschwindigkeit <input type="text" name="speed" value='<%= speed %>' size=10 maxlength=10> <input type="submit" name="start" value="Starte MeinBoesesMemoryLeak"><br> </form> <% try { if( start != null ) meinBoesesMemoryLeak( speed, out ); } catch( Throwable t ) { out.println( t ); out.flush(); } %> </body> </html>
Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis folgende drei Java-Klassen:
GcCpuTxMBeanServletContextListener.java
package de.meinefirma.meinprojekt; import java.util.Timer; import javax.servlet.*; public class GcCpuTxMBeanServletContextListener implements ServletContextListener { private static final int MILLISEK_PRO_SEK = 1000; private static final int MILLISEK_PRO_10_MIN = 1000 * 60 * 10; @Override public final void contextInitialized( ServletContextEvent servletContextEvent ) { String loggerName = servletContextEvent.getServletContext().getInitParameter( "GcCpuTxLoggerName" ); String logFile = servletContextEvent.getServletContext().getInitParameter( "GcCpuTxLogFile" ); String doGC = servletContextEvent.getServletContext().getInitParameter( "GcCpuTxDoGC" ); String periodSec = servletContextEvent.getServletContext().getInitParameter( "GcCpuTxMeasurePeriodSec" ); int periodMs; try { periodMs = MILLISEK_PRO_SEK * Math.max( 1, Integer.parseInt( periodSec ) ); } catch( Exception ex ) { periodMs = MILLISEK_PRO_10_MIN; } GcCpuTxMBeanTimerTask mBeanTimerTask = new GcCpuTxMBeanTimerTask( loggerName, logFile, doGC == null || doGC.trim().equalsIgnoreCase( "true" ) ); servletContextEvent.getServletContext().setAttribute( GcCpuTxMBeanTimerTask.class.getName(), mBeanTimerTask ); (new Timer()).scheduleAtFixedRate( mBeanTimerTask, 0, periodMs ); } @Override public final void contextDestroyed( ServletContextEvent servletContextEvent ) { GcCpuTxMBeanTimerTask mbtt = (GcCpuTxMBeanTimerTask) servletContextEvent.getServletContext().getAttribute( GcCpuTxMBeanTimerTask.class.getName() ); if( mbtt != null ) { mbtt.cancel(); } } }
GcCpuTxMBeanTimerTask.java
package de.meinefirma.meinprojekt; import java.lang.management.*; import java.text.SimpleDateFormat; import java.util.*; import java.util.logging.Logger; // oder: org.apache.log4j.Logger import javax.management.MBeanServerConnection; public class GcCpuTxMBeanTimerTask extends TimerTask { private static final long MILLISEK500 = 500; private final SimpleDateFormat yyyyMMddHHmmss = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); private final MBeanServerConnection mbs = ManagementFactory.getPlatformMBeanServer(); private final RuntimeMXBean rtMXBean = ManagementFactory.getRuntimeMXBean(); private final OperatingSystemMXBean opSystMXBean = ManagementFactory.getOperatingSystemMXBean(); private final List<GarbageCollectorMXBean> gcMXBeans = GcCpuTxMBeanUtil.getGarbageCollectorMXBeans( mbs ); private final String loggerName; private final String logFile; private final boolean doGC; private long lastUpTimeMs; private long lastGcTimeMs; private long lastCpuTimeNs; private long[] lastTxCounts = new long[2]; private String result; public GcCpuTxMBeanTimerTask( String loggerName, String logFile, boolean doGC ) { this.loggerName = loggerName; this.logFile = logFile; this.doGC = doGC; } @Override public final void run() { if( mbs == null || rtMXBean == null || gcMXBeans == null ) { return; } long cpuCount = Math.max( 1, ( opSystMXBean != null ) ? opSystMXBean.getAvailableProcessors() : 1 ); long upTimeMs = rtMXBean.getUptime(); if( upTimeMs - lastUpTimeMs < MILLISEK500 ) { return; } long gcTimeMs = GcCpuTxMBeanUtil.readGarbageCollectionTimeMs( gcMXBeans ); double gcTimePercent = GcCpuTxMBeanUtil.calculateGarbageCollectionTimePercent( gcTimeMs, lastGcTimeMs, upTimeMs, lastUpTimeMs, cpuCount ); long cpuTimeNs = GcCpuTxMBeanUtil.readCpuTimeNs( mbs ); int cpuTimePercent = GcCpuTxMBeanUtil.calculateCpuTimePercent( cpuTimeNs, lastCpuTimeNs, upTimeMs, lastUpTimeMs, cpuCount ); long[] cpuLoads = GcCpuTxMBeanUtil.readCpuLoadsJRockit( mbs ); long[] txCounts = GcCpuTxMBeanUtil.readTxCountsWebLogic( mbs ); String[] txPerSec = GcCpuTxMBeanUtil.calculatePerSecond( txCounts, lastTxCounts, upTimeMs, lastUpTimeMs ); StringBuilder sb = new StringBuilder(); String headerLine = "DatumZeit; CPUs; MemTotalMB; MemUsedMB; GC%; CPU%"; sb.append( yyyyMMddHHmmss.format( new Date() ) ).append( ";" ).append( cpuCount ); if( doGC ) { System.gc(); } long memTotal = Runtime.getRuntime().totalMemory(); long memFree = Runtime.getRuntime().freeMemory(); sb.append( ";" ).append( GcCpuTxMBeanUtil.doubleString1( memTotal / (1024. * 1024.) ) ); sb.append( ";" ).append( GcCpuTxMBeanUtil.doubleString1( (memTotal - memFree) / (1024. * 1024.) ) ); sb.append( ";" ).append( GcCpuTxMBeanUtil.doubleString1( gcTimePercent ) ).append( ";" ).append( cpuTimePercent ); if( cpuLoads != null && cpuLoads.length > 1 && cpuLoads[0] >= 0 && cpuLoads[1] >= 0 ) { headerLine += "; CPU-VM%; CPU-All%"; sb.append( ";" ).append( cpuLoads[0] ).append( ";" ).append( cpuLoads[1] ); } if( txCounts != null && txCounts.length > 1 && txCounts[0] >= 0 && txCounts[1] >= 0 ) { headerLine += "; Tx/Sec; TxRollback/Sec; TxSum; TxRollbackSum"; sb.append( ";" ).append( txPerSec[0] ).append( ";" ).append( txPerSec[1] ); sb.append( ";" ).append( txCounts[0] ).append( ";" ).append( txCounts[1] ); lastTxCounts[0] = txCounts[0]; lastTxCounts[1] = txCounts[1]; } lastUpTimeMs = upTimeMs; lastCpuTimeNs = cpuTimeNs; lastGcTimeMs = gcTimeMs; result = sb.toString(); // Wahlweise Ausgabe ueber Logger (z.B. im App-Server) oder CSV-Datei (z.B. fuer Test): if( loggerName != null && loggerName.trim().length() > 0 ) { Logger.getLogger( loggerName.trim() ).info( result ); } if( logFile != null && logFile.trim().length() > 0 ) { GcCpuTxMBeanUtil.storeInFile( result, headerLine, logFile.trim() ); } } public final String getResult() { return result; } }
GcCpuTxMBeanUtil.java
package de.meinefirma.meinprojekt; import java.io.*; import java.lang.management.*; import java.text.DecimalFormat; import java.util.*; import javax.management.*; public final class GcCpuTxMBeanUtil { private static final double NANOSEK_PRO_MILLISEK = 1000000.; private static final double MILLISEK_PRO_SEK = 1000.; private static final double PROZENT_MAX_100 = 100.; private static final double SCHWELLE_NACHKOMMA = 10.; /** Konvertiere double-Wert in String mit keiner oder einer Nachkommastelle. */ static String doubleString1( double d ) { final DecimalFormat df1 = new DecimalFormat( "0.0" ); return ( d < SCHWELLE_NACHKOMMA ) ? df1.format( d ) : ("" + Math.round( d )); } /** Berechne prozentuale CPU-Zeit. */ static int calculateCpuTimePercent( long cpuTimeNs, long lastCpuTimeNs, long upTimeMs, long lastUpTimeMs, long cpuCount ) { return (int) Math.round( Math.min( PROZENT_MAX_100, (cpuTimeNs - lastCpuTimeNs) / ((upTimeMs - lastUpTimeMs) * cpuCount * (NANOSEK_PRO_MILLISEK/PROZENT_MAX_100)) ) ); } /** Berechne prozentuale Garbage-Collection-Zeit. */ static double calculateGarbageCollectionTimePercent( long gcTimeMs, long lastGcTimeMs, long upTimeMs, long lastUpTimeMs, long cpuCount ) { return Math.min( PROZENT_MAX_100, ((gcTimeMs - lastGcTimeMs) * PROZENT_MAX_100) / ((upTimeMs - lastUpTimeMs) * cpuCount) ); } /** Berechne Anzahl pro Sekunde (z.B. Anzahl Transaktionen). */ static String[] calculatePerSecond( long[] counts, long[] lastCounts, long upTimeMs, long lastUpTimeMs ) { int n = Math.min( counts.length, lastCounts.length ); String[] result = new String[n]; for( int i = 0; i < n; i++ ) { result[i] = doubleString1( MILLISEK_PRO_SEK * (counts[i] - lastCounts[i]) / (upTimeMs - lastUpTimeMs) ); } return result; } /** Lies Garbage-Collection-Zeit (Millisekunden). */ static long readGarbageCollectionTimeMs( List<GarbageCollectorMXBean> gcMXBeans ) { long gcTimeMs = 0; for( GarbageCollectorMXBean gc : gcMXBeans ) { gcTimeMs += gc.getCollectionTime(); } return gcTimeMs; } /** Lies CPU-Zeit (Nanosekunden). */ static long readCpuTimeNs( MBeanServerConnection mbs ) { return readLongFromMBean( mbs, "java.lang:type=OperatingSystem", "ProcessCpuTime" ); } /** Falls WebLogic mit JRockit: Lies CPU-Last (Prozent). */ static long[] readCpuLoadsJRockit( MBeanServerConnection mbs ) { long[] cpuLoadValues = new long[2]; cpuLoadValues[0] = Math.round( PROZENT_MAX_100 * readDoubleFromMBean( mbs, "oracle.jrockit.management:type=Runtime", "VMGeneratedCPULoad" ) ); cpuLoadValues[1] = Math.round( PROZENT_MAX_100 * readDoubleFromMBean( mbs, "oracle.jrockit.management:type=Runtime", "CPULoad" ) ); return cpuLoadValues; } /** Falls WebLogic: Lies Anzahl Transaktionen. */ static long[] readTxCountsWebLogic( MBeanServerConnection mbs ) { long[] txValues = new long[2]; txValues[0] = readLongFromMBean( mbs, "com.bea:ServerRuntime=AdminServer,Name=JTARuntime,Type=JTARuntime", "TransactionTotalCount" ); txValues[1] = readLongFromMBean( mbs, "com.bea:ServerRuntime=AdminServer,Name=JTARuntime,Type=JTARuntime", "TransactionRolledBackTotalCount" ); return txValues; } /** Lies Long-Attribut von MBean. */ static long readLongFromMBean( MBeanServerConnection mbs, String objectName, String attributeName ) { Object attrObj = readAttrFromMBean( mbs, objectName, attributeName ); if( attrObj instanceof Long ) { return ((Long) attrObj).longValue(); } return -1; } /** Lies Double-Attribut von MBean. */ static double readDoubleFromMBean( MBeanServerConnection mbs, String objectName, String attributeName ) { Object attrObj = readAttrFromMBean( mbs, objectName, attributeName ); if( attrObj instanceof Double ) { return ((Double) attrObj).doubleValue(); } return -1; } /** Lies Attribut von MBean. */ static Object readAttrFromMBean( MBeanServerConnection mbs, String objectName, String attributeName ) { List<Object> lst = new ArrayList<Object>(); try { Set<ObjectName> objectNames = mbs.queryNames( new ObjectName( objectName ), null ); for( ObjectName on : objectNames ) { Object attr = mbs.getAttribute( on, attributeName ); if( objectNames.size() == 1 ) { return attr; } lst.add( attr ); } return lst; } catch( Exception ex ) { return null; } } /** Ermittle Garbage-Collector-MXBeans. */ static List<GarbageCollectorMXBean> getGarbageCollectorMXBeans( MBeanServerConnection mbs ) { try { List<GarbageCollectorMXBean> gcMXBeans = new ArrayList<GarbageCollectorMXBean>(); ObjectName gcAllObjectName = new ObjectName( ManagementFactory.GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE + ",*" ); Set<ObjectName> gcMXBeanObjectNames = mbs.queryNames( gcAllObjectName, null ); for( ObjectName on : gcMXBeanObjectNames ) { GarbageCollectorMXBean gc = ManagementFactory.newPlatformMXBeanProxy( mbs, on.getCanonicalName(), GarbageCollectorMXBean.class ); gcMXBeans.add( gc ); } return gcMXBeans; } catch( Exception ex ) { return null; } } /** Schreibe Ergebnisse in Logdatei (z.B. im CSV-Format). */ static void storeInFile( String textLine, String headerLine, String logFile ) { try { File f = new File( logFile ); boolean b = f.exists(); BufferedWriter out = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( f, true ) ) ); try { if( !b ) { out.write( headerLine ); out.newLine(); } out.write( textLine ); out.newLine(); } finally { out.close(); } } catch( IOException ex ) { throw new IllegalArgumentException( "Fehler mit Logdatei '" + logFile + "':\n" + ex ); } } }
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):
[\MeinWorkspace\MBeanServlet] |- [src] | '- [main] | |- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | |- GcCpuTxMBeanServletContextListener.java | | |- GcCpuTxMBeanTimerTask.java | | '- GcCpuTxMBeanUtil.java | '- [webapp] | |- [WEB-INF] | | '- web.xml | '- index.jsp '- pom.xml
Normalerweise werden die gezeigten Dateien (natürlich außer der index.jsp) einem vorhandenen Projekt hinzugefügt.
Falls Sie die gezeigten Dateien als eigene WAR-Datei in Ihrem Java EE Application Server installieren wollen, führen Sie aus:
cd \MeinWorkspace\MBeanServlet
mvn clean package
dir target\*.war
Die resultierende MBeanServlet.war können Sie in beliebigen Servlet-Containern bzw. Java EE Application Servern installieren
(Tomcat, JBoss, GlassFish, WebLogic, ...).
Falls Sie in einen
WebLogic-Server
deployen, könnte die resultierende URL beispielsweise lauten:
start http://localhost:7001/MBeanServlet
Die resultierende CSV-Datei finden Sie dann je nach Installation beispielsweise unter:
type \WebLogic\user_projects\domains\MeineDomain\GcCpuTxLog.csv
Im Folgenden wird stattdessen nur auf einfache Tests mit dem Jetty-Server eingegangen. Starten Sie testweise den Jetty-Server mit dem neuen ServletContextListener:
cd \MeinWorkspace\MBeanServlet
mvn jetty:run
Warten Sie mindestens 30 Sekunden und sehen Sie sich den Inhalt der Logdatei an:
type GcCpuTxLog.csv
Sie erhalten eine CSV-Logdatei, in der alle 10 Sekunden Einträge zur prozentualen CPU-Auslastung und Garbage Collection hinzugefügt werden (für produktiven Betrieb sollten Sie die Periodendauer von 10 Sekunden auf 600 Sekunden erhöhen, siehe oben web.xml).
Sie können die CSV-Datei in Tabellenkalkulationsprogramme laden und dort Grafiken erzeugen, z.B. in Excel so: Laden Sie die .csv-Datei, markieren Sie die linke Zeitstempel-Spalte und die gewünschten weiteren (oder alle) Spalten, und wählen Sie: 'Einfügen' | 'Diagramm...' | 'Punkt (XY)' | Klick auf mittleres Kurvendiagramm | 'Fertigstellen'.
Ohne weitere Aktivitäten erhalten Sie allerdings lediglich 0 % für die CPU-Auslastung und die prozentuale Garbage Collection.
Um testweise eine sehr hohe CPU-Belastung und Garbage-Collection-Aktivität bis hin zum OutOfMemoryError zu generieren, führen Sie meinBoesesMemoryLeak in index.jsp bei laufendem Jetty-Server aus:
start http://localhost:8080/MBeanServlet
start jvisualvm.exe
Klicken Sie in JVisualVM auf: "Window | Applications | Local | org.codehaus.plexus.classworlds.launcher.Launcher | Monitor".
Klicken Sie auf der http://localhost:8080/MBeanServlet-Webseite auf den "Starte MeinBoesesMemoryLeak"-Button.
Falls Ihre CPU über zwei Kerne verfügt, erhalten Sie in JVisualVM:
Vergleichen Sie die von JVisualVM angezeigten Werte mit dem, was die Webseite anzeigt (Achtung: die hier gezeigte Webseite passt nicht zu den oben gezeigten Grafiken, sondern soll nur das Prinzip verdeutlichen):
Vergleichen Sie mit den Einträgen in der CSV-Logdatei:
type GcCpuTxLog.csv
Laden Sie die CSV-Datei in Excel:
Beenden Sie Jetty mit "Strg + C".
Das obige JmxMBeanLesenAufrufen-Beispiel kommt ausschließlich mit JDK-Klassen aus, benötigt keine zusätzliche .jar-Library und sollte mit den meisten modernen Java EE Application Servern funktionieren.
Einige Java EE Application Server fügen eigene Erweiterungen hinzu oder empfehlen besondere Zugriffsmethoden.
Zum Beispiel für WebLogic:
Understanding WebLogic Server MBeans
Accessing WebLogic Server MBeans with JMX
Developing Custom Management Utilities With JMX for Oracle WebLogic Server
Die beiden im Folgenden vorgestellten Programme ermitteln für WebLogic alle zu einer Domain gehörenden Serverinstanzen und zu jedem Server einige Attribute aus der JTA-, JMS- und ServerRuntimeMBean.
Für den WebLogic-spezifischen Zugang gibt es verschiedene Wege. Zwei Varianten werden in den folgenden beiden Beispielen demonstriert:
import java.util.*; import javax.management.*; import javax.management.remote.*; import javax.naming.Context; public class Weblogic10ServerRuntimeMBean { public static void main( String[] args ) throws Exception { String url = "t3://localhost:7001"; String usr = "weblogic"; String pwd = "weblogic0"; JMXConnector jmxConnector = null; try { jmxConnector = getJMXConnector( url, usr, pwd ); MBeanServerConnection mBeanServerConn = jmxConnector.getMBeanServerConnection(); if( args != null && args.length > 0 && args[0].equals("all") ) { showAllObjectNames( mBeanServerConn ); } showServerRuntimesJtaJms( mBeanServerConn ); } finally { if( jmxConnector != null ) jmxConnector.close(); } } static void showAllObjectNames( MBeanServerConnection con ) throws Exception { System.out.println( "\n---- showAllObjectNames ----" ); Iterator<?> itr = con.queryNames( null, null ).iterator(); while( itr.hasNext() ) System.out.println( itr.next() ); } static void showServerRuntimesJtaJms( MBeanServerConnection con ) throws Exception { System.out.println( "\n---- showServerRuntimesJtaJms ----" ); ObjectName service = new ObjectName( "com.bea:Name=DomainRuntimeService," + "Type=weblogic.management.mbeanservers.domainruntime.DomainRuntimeServiceMBean" ); ObjectName[] srv = (ObjectName[]) con.getAttribute( service, "ServerRuntimes" ); for( int i = 0; i < srv.length; i++ ) { System.out.println("ServerRuntime: " + getDescription(srv[i], con)); System.out.println("Name: " + con.getAttribute(srv[i], "Name")); System.out.println(" State: " + con.getAttribute(srv[i], "State")); System.out.println(" ListenAddress: " + con.getAttribute(srv[i], "ListenAddress")); System.out.println(" ListenPort: " + con.getAttribute(srv[i], "ListenPort")); System.out.println(" DefaultURL: " + con.getAttribute(srv[i], "DefaultURL")); System.out.println(" ActivationTime: " + con.getAttribute(srv[i], "ActivationTime")); System.out.println(" HealthState: " + con.getAttribute(srv[i], "HealthState")); ObjectName jta = (ObjectName) con.getAttribute(srv[i], "JTARuntime"); System.out.println("JTARuntime: " + getDescription(jta, con)); System.out.println(" JTA-ActiveTransactionsTotalCount: " + con.getAttribute(jta, "ActiveTransactionsTotalCount")); System.out.println(" JTA-TransactionTotalCount: " + con.getAttribute(jta, "TransactionTotalCount")); System.out.println(" JTA-HealthState: " + con.getAttribute(jta, "HealthState")); ObjectName jms = (ObjectName) con.getAttribute(srv[i], "JMSRuntime"); System.out.println("JMSRuntime: " + getDescription(jms, con)); System.out.println(" JMS-ConnectionsTotalCount: " + con.getAttribute(jms, "ConnectionsTotalCount")); System.out.println(" JMS-JMSServersTotalCount: " + con.getAttribute(jms, "JMSServersTotalCount")); System.out.println(" JMS-HealthState: " + con.getAttribute(jms, "HealthState")); } } static JMXConnector getJMXConnector( String url, String usr, String pwd ) throws Exception { String serviceUrl = "service:jmx:" + url + "/jndi/weblogic.management.mbeanservers.domainruntime"; Map<String, Object> envMap = new HashMap<String, Object>(); envMap.put( Context.SECURITY_PRINCIPAL, usr ); envMap.put( Context.SECURITY_CREDENTIALS, pwd ); envMap.put( JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES, "weblogic.management.remote" ); return JMXConnectorFactory.connect( new JMXServiceURL( serviceUrl ), envMap ); } static String getDescription( ObjectName on, MBeanServerConnection con ) throws Exception { String s = con.getMBeanInfo( on ).getDescription(); int n = s.indexOf( '<' ); return (n > 10) ? s = s.substring( 0, n ) : s; } }
Dieses Programm erfordert zwei etwas ungewöhnliche Voraussetzungen:
a) Im Classpath muss sich weblogic.jar befinden, aber ab WebLogic 10 darf diese .jar-Bibliothek nicht kopiert werden,
sondern es muss auf die weblogic.jar im WebLogic-Installationsverzeichnis verwiesen werden,
da weitere Dateien aus dem Verzeichnis benötigt werden (oder Sie erstellen eine wlfullclient.jar).
b) Das Programm sollte exakt mit der Java-Version aufgerufen werden, mit der der WebLogic betrieben wird.
Dies kann je nach WebLogic-Konfiguration entweder das Sun JDK (im WebLogic-jdk...-Verzeichnis)
oder JRockit (im jrockit...-Verzeichnis) sein.
Die folgenden Kommandozeilen gelten für Oracle WebLogic 10 und für den Fall, dass das Sun JDK verwendet wird:
C:\WebLogic\jdk160_05\bin\javac Weblogic10ServerRuntimeMBean.java
C:\WebLogic\jdk160_05\bin\java -cp .;C:\WebLogic\wlserver_10.3\server\lib\weblogic.jar Weblogic10ServerRuntimeMBean
Falls Sie Oracle WebLogic 10.3.5 unter 64-bit-Windows und JRockit mit eigenständiger JRockit-Installation verwenden, könnten die Kommandos folgendermaßen lauten:
"C:\Program Files\Java\jrockit-jdk1.6.0_45-R28.2.7-4.1.0\bin\javac" Weblogic10ServerRuntimeMBean.java
"C:\Program Files\Java\jrockit-jdk1.6.0_45-R28.2.7-4.1.0\bin\java" -cp .;C:\WebLogic\wlserver_10.3\server\lib\weblogic.jar Weblogic10ServerRuntimeMBean
Das folgende Beispiel demonstriert den Zugang zu WebLogic-spezifischen MBean-Klassen über das WebLogic-spezifische MBeanHome-Interface, wie es für ältere WebLogic-Versionen üblich war und auch in WebLogic 9 und 10 noch funktioniert, obwohl mittlerweile als deprecated markiert. Die Tabelle listet JavaDoc-Links zu einigen MBeans für verschiedene WebLogic-Versionen:
ServerRuntimeMBean | WebLogic 8.1 | WebLogic 9 | WebLogic 10 |
JTARuntimeMBean | WebLogic 8.1 | WebLogic 9 | WebLogic 10 |
JMSRuntimeMBean | WebLogic 8.1 | WebLogic 9 | WebLogic 10 |
Das folgende Programm ermittelt über die Admin-MBeanHome die Server (zu einer Domain können mehrere Serverinstanzen gehören). Anschließend werden zu jedem Server Attribute aus der JTA-, JMS- und ServerRuntimeMBean gelesen. Das Programm funktioniert mit WebLogic 8, 9 und 10.
import java.util.*; import javax.naming.*; import weblogic.jndi.Environment; import weblogic.management.MBeanHome; import weblogic.management.runtime.*; public class Weblogic8ServerRuntimeMBean { public static void main( String[] args ) throws NamingException { String url = "t3://localhost:7001"; String usr = "weblogic"; String pwd = "weblogic0"; Environment env = new Environment(); env.setProviderUrl( url ); env.setSecurityPrincipal( usr ); env.setSecurityCredentials( pwd ); Context ctx = env.getInitialContext(); MBeanHome adminMBeanHome = (MBeanHome) ctx.lookup( MBeanHome.ADMIN_JNDI_NAME ); Set srvRuntMBeanSet = adminMBeanHome.getMBeansByType( "ServerRuntime" ); System.out.println( "Aktive Domain: " + adminMBeanHome.getActiveDomain().getName() + ", Anzahl Server: " + srvRuntMBeanSet.size() ); System.out.println( "Aktive Server: " ); Iterator srvRuntMBeanIterator = srvRuntMBeanSet.iterator(); while( srvRuntMBeanIterator.hasNext() ) { ServerRuntimeMBean srvMb = (ServerRuntimeMBean) srvRuntMBeanIterator.next(); if( srvMb.getState().equals( ServerStates.RUNNING ) ) { JTARuntimeMBean jtaMb = srvMb.getJTARuntime(); JMSRuntimeMBean jmsMb = srvMb.getJMSRuntime(); System.out.println(" Name: " + srvMb.getName()); System.out.println(" ListenAddress: " + srvMb.getListenAddress()); System.out.println(" ListenPort: " + srvMb.getListenPort()); System.out.println(" ActivationTime: " + srvMb.getActivationTime()); System.out.println(" HealthState: " + srvMb.getHealthState()); System.out.println(" JTA: ActiveTransactionsTotalCount: " + jtaMb.getActiveTransactionsTotalCount()); System.out.println(" JTA: TransactionTotalCount: " + jtaMb.getTransactionTotalCount()); System.out.println(" JTA: HealthState: " + jtaMb.getHealthState()); System.out.println(" JMS: ConnectionsTotalCount: " + jmsMb.getConnectionsTotalCount()); System.out.println(" JMS: JMSServersTotalCount: " + jmsMb.getJMSServersTotalCount()); System.out.println(" JMS: HealthState: " + jmsMb.getHealthState()); } } } }
Dieses Programm erfordert zwei etwas ungewöhnliche Voraussetzungen:
a) Im Classpath muss sich weblogic.jar befinden, aber ab WebLogic 10 darf diese .jar-Bibliothek nicht kopiert werden,
sondern es muss auf die weblogic.jar im WebLogic-Installationsverzeichnis verwiesen werden,
da weitere Dateien aus dem Verzeichnis benötigt werden (oder Sie erstellen eine wlfullclient.jar).
b) Das Programm sollte exakt mit der Java-Version aufgerufen werden, mit der der WebLogic betrieben wird.
Dies kann je nach WebLogic-Konfiguration entweder das Sun JDK (im WebLogic-jdk...-Verzeichnis)
oder JRockit (im jrockit...-Verzeichnis) sein.
Die folgenden Kommandozeilen gelten für Oracle WebLogic 10 und für den Fall, dass das Sun JDK verwendet wird:
C:\WebLogic\jdk160_05\bin\javac -cp C:\WebLogic\wlserver_10.3\server\lib\weblogic.jar Weblogic8ServerRuntimeMBean.java
C:\WebLogic\jdk160_05\bin\java -cp .;C:\WebLogic\wlserver_10.3\server\lib\weblogic.jar Weblogic8ServerRuntimeMBean
Falls Sie Oracle WebLogic 10.3.5 unter 64-bit-Windows und JRockit mit eigenständiger JRockit-Installation verwenden, könnten die Kommandos folgendermaßen lauten:
"C:\Program Files\Java\jrockit-jdk1.6.0_45-R28.2.7-4.1.0\bin\javac" -cp .;C:\WebLogic\wlserver_10.3\server\lib\weblogic.jar Weblogic8ServerRuntimeMBean.java
"C:\Program Files\Java\jrockit-jdk1.6.0_45-R28.2.7-4.1.0\bin\java" -cp .;C:\WebLogic\wlserver_10.3\server\lib\weblogic.jar Weblogic8ServerRuntimeMBean