JMX (Java Management Extensions)

+ andere TechDocs
+ GC-Monitoring per JMX
+ Java EE
+ GlassFish
+ Oracle WebLogic
+


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.



Inhalt

  1. Begriffe im Zusammenhang mit JMX
  2. Aktivierungsstartparameter und Portnummer
  3. MBeans in der JConsole
  4. Eigene MBean erstellen und verwenden
  5. Generischer Remote-Zugriff auf MBeans (über JMXConnector und MBeanServerConnection)
  6. Nagios-Plug-in check_jmx / JMXQuery
  7. Memory Leak mit den JVM-MXBeans GarbageCollector, Runtime und OperatingSystem beobachten
  8. Monitoring der prozentualen Garbage Collection, der CPU-Auslastung und weiterer Kennwerte über periodische Zeiträume
  9. JTA-, JMS- und ServerRuntimeMBean im WebLogic
    Vorbemerkung, BEA WebLogic 9 und Oracle WebLogic 10, BEA WebLogic 8



Begriffe im Zusammenhang mit JMX

JSR 3
Java Management Extensions (JMX) Specification.
JMX bietet eine Managementarchitektur, APIs und Services für eine verteilte, dynamische und modulare Verwaltung von Java-unterstützten Ressourcen.
JSR 77
J2EE Management.
Management-Spezifikation zur Vereinheitlichung und zur Unterstützung von Anbietern von Tools und Java EE Application Servern. Zum Beispiel werden die MEJB-Interfaces definiert, die per JNDI erreichbar sind (mögliche JNDI-Namen z.B.: "ejb/mgmt/MEJB", "java:comp/env/ejb/mgmt/MEJB", "jmx/rmi/RMIAdaptor", "weblogic/management/adminhome").
JSR 160
Java Management Extensions (JMX) Remote API.
Erweiterung des JMX-APIs um Remote-Zugriff auf JMX-MBean-Server.
JMX
Java Management Extensions.
JMX bietet ein API zum Management und Monitoring von Java-basierenden Ressourcen (Applikationen, Geräte, Services, JVMs, EJBs, ...). JMX basiert insbesondere auf JSR 3 und JSR 160.
JMX Tutorials: JMX Tutorial, Java SE 6, Java Tutorials: JMX.
JMX API: Package javax.management.
JMX Specification: JMX Specification, version 1.4.
MBean
MBeans sind Java-Objekte, die den JMX-Konventionen entsprechen und Zugriff auf eine "Manageable Ressource" ermöglichen. Es können Attribute gelesen und geschrieben werden und Methoden ausgeführt werden. Es gibt verschiedene MBean-Arten (Standard MBean, Dynamic MBean, Open MBean, Model MBean, MXBean). MBean-Tutorials finden Sie hier, hier und hier.
MBeanServer
Ein MBean-Server ist ein Java-Objekt, welches als Verzeichnisdienst für MBeans dient. Über das MBeanServerConnection-Interface können MBeans instanziiert und gesucht werden und wird der Zugriff ermöglicht.
JMX Agent
Der JMX Agent enthält den MBeanServer und weitere Services zur MBean-Verwaltung, zum Beispiel Konnektoren für den Remote-Zugriff. Typische Konnektoren basieren auf RMI oder JNDI. Über Adapter kann auch der Zugriff über HTTP oder SNMP ermöglicht werden.
JMXServiceURL
Eine JMXServiceURL ist ein Java-Objekt, welches einen JMX-Konnektor ("Connector Server") adressiert. Die JMXServiceURL kann auch als String dargestellt werden (als "Abstract Service URL for SLP" nach RFC 2609) mit folgender Syntax:
service:jmx:<protocol>://[<host>[:<port>]][<url-path>]
also zum Beispiel:
service:jmx:rmi:///jndi/rmi://localhost:8686/jmxrmi.
ObjectName
Ein vollständiger ObjectName ist ein eindeutiger Bezeichner für eine MBean (z.B. "java.lang:type=GarbageCollector,name=Copy"). Ein unvollständiger ObjectName dient als Suchausdruck (z.B. "java.lang:type=GarbageCollector,*"). ObjectNamen bestehen aus zwei durch einen Dppelpunkt (":") getrennten Teilen: Dem MBean-Domainnamen (z.B. "java.lang") und einem oder mehreren "Schlüssel=Wert"-Paaren (z.B. "type=GarbageCollector,name=Copy").
JMX-Architektur

            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 


Aktivierungsstartparameter und Portnummer

Startparameter zur Aktivierung des JMX-Remotezugriffs und die JMX-Portnummer

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



MBeans in der JConsole

Standard-JVM-MBeans

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-GarbageCollector.png

Java-EE-Application-Server-spezifische MBeans in der JConsole

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):

JConsole-GarbageCollector.png

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:



Eigene MBean erstellen und verwenden

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.



Generischer Remote-Zugriff auf MBeans (über JMXConnector und MBeanServerConnection)

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.

Anwendung von JmxMBeanLesenAufrufen auf eigene MBean

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)

Anwendung von JmxMBeanLesenAufrufen auf Java EE Application Server

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.

Bemerkungen zu JmxMBeanLesenAufrufen

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-Plug-in check_jmx / JMXQuery

Nagios

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.

check_jmx / JMXQuery

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}


Memory Leak mit den JVM-MXBeans GarbageCollector, Runtime und OperatingSystem beobachten

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.

JConsole.png

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.



Monitoring der prozentualen Garbage Collection, der CPU-Auslastung und weiterer Kennwerte über periodische Zeiträume (z.B. für Server)

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.

JMX-Abfragen und periodische Messungen von einem Remote-Client aus

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.

Als Servlet

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.

  1. Sie können das Programmierbeispiel als Zipdatei downloaden oder die im Folgenden beschriebenen Schritte durchführen.

  2. Installieren Sie Maven wie beschrieben unter maven.htm#Installation.

  3. Ö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]
    
  4. 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>
    
  5. 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>
    
  6. 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>
    
  7. 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 );
          }
       }
    }
    
  8. 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
    
  9. 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

  10. 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'.

  11. 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.

  12. Falls Ihre CPU über zwei Kerne verfügt, erhalten Sie in JVisualVM:

    JVisualVM-MBeanServlet-1.png
    JVisualVM-MBeanServlet-2.png
  13. 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):

    JMX-MBeanServlet.png
  14. Vergleichen Sie mit den Einträgen in der CSV-Logdatei:

    type GcCpuTxLog.csv

    Laden Sie die CSV-Datei in Excel:

    JMX-MBeanServlet-Excel.png
  15. Beenden Sie Jetty mit "Strg + C".



JTA-, JMS- und ServerRuntimeMBean im WebLogic

Vorbemerkung

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:

JTA-, JMS- und ServerRuntimeMBean im BEA WebLogic 9 und Oracle WebLogic 10

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

JTA-, JMS- und ServerRuntimeMBean im BEA WebLogic 8

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




Weitere Themen: andere TechDocs | Java EE | GlassFish | Oracle WebLogic
© 2008-2011 Torsten Horn, Aachen