Monitoring der prozentualen Garbage Collection, der CPU-Auslastung und weiterer Kennwerte per JMX

+ andere TechDocs
+ 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 ein Hilfstool vorgestellt, welches die prozentualen Garbage Collection, die CPU-Auslastung und weitere Kennwerte per JMX-Remotezugriff erfasst.

Grundlagen zu JMX finden Sie in jmx.htm.



Inhalt

  1. Vorbemerkungen
    Durchsatzreduzierung / Amdahlsches Gesetz, JMX-Grundlagen, Startparameter, Portnummer, MBeans in der JConsole, Garbage Collectoren
  2. JmxServerMonitoring
    Monitoring mit JmxServerMonitoring, Parameter, Sourcecode
  3. MeinBoesesMemoryLeak
  4. Weitere Informationen



Vorbemerkungen

Durchsatzreduzierung wegen zu langer prozentualer Garbage Collection

Die folgende Grafik von Sun zeigt eindrucksvoll, wie viel Performance nach dem Amdahlschen Gesetz bei zu langer nicht parallelisierbarer Garbage Collection verloren geht.

Sun-GC-Durchsatzreduzierung.gif

Genaueres hierzu finden Sie unter http://java.sun.com/docs/performance und http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html.

JMX-Grundlagen

Grundlagen zu JMX finden Sie in jmx.htm.

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

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

Weiteres hierzu 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.

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

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

Garbage Collectoren (GC)

Java EE Application Server können unterschiedliche JVMs (z.B. JRockit für WebLogic) mit sehr verschiedenen Garbage-Collector-Strategien verwenden, die zudem auch noch umkonfiguriert werden können. Den meisten Garbage-Collector-Strategien ist gemeinsam, dass mehrere verschiedene Garbage Collectoren kombiniert werden. Für Diagnosezwecke wird meistens nur zwischen "kleinen" und "großen" Garbage Collections ("Minor"/"Major") unterschieden. Die "kleinen" Garbage Collections werden meistens häufig ausgeführt und benötigen nur wenig Zeit, da sie nur die einfachen Fälle mit meistens kleinen Speichermengen bearbeiten. Wenn dies nicht reicht, wird die "große" Garbage Collection bemüht, die mehr Ressoucen benötigt, da sie gründlicher aufräumt. Die Namen der "kleinen" und "großen" Garbage Collectoren sind nicht einheitlich. Beispiele:

 "Minor GC""Major GC"
GlassFish 2CopyMarkSweepCompact
WebLogic 10 (mit JRockit)Young CollectorOld Collector

Damit das im Folgenden vorgestellte Programm "JmxServerMonitoring" für verschiedene JVMs und Application Server verwendbar ist, geht es nicht von bestimmten Garbage-Collector-Namen aus, sondern ermittelt sie generisch. Da die für die Differenzermittlung benötigten vorherigen Messwerte auf bestimmte Garbage-Collector-Namen bezogen werden müssen, werden sie in einer HashMap ("lastMeasurement") gespeichert.



JmxServerMonitoring

Monitoring mit JmxServerMonitoring

Das JmxServerMonitoring-Programm ermittelt in vorgegebenen Zeitintervallen den prozentualen Anteil der einzelnen Garbage Collectoren und den Summenwert. Es kann entweder nur eine einzelne JVM (oder einen Application Server) oder mehrere (z.B. Cluster) überwachen.

Es können verschiedene Ergebnisse erzeugt werden (oder alle zusammen):

Weitere Parameter für JmxServerMonitoring

Die Parameter können auf drei Arten gesetzt werden:

Beispiele für Aufruf-Kommandozeilen (ein weiteres Beispiel finden Sie unten im Kapitel MeinBoesesMemoryLeak):

Beispiel für Properties-Datei JmxServerMonitoring.properties (für WebLogic mit JRockit-JVM):

Bitte beachten Sie: Falls Sie das Programm von einem entfernten Rechner aus mit vielleicht unüblichen Portnummern (8686, 7091, ...) starten, müssen Sie eventuell diese Portnummern in der Firewall freischalten.

JmxServerMonitoring.java

import java.io.*;
import java.lang.management.*;
import java.net.MalformedURLException;
import java.text.*;
import java.util.*;
import javax.management.*;
import javax.management.remote.*;
import javax.naming.Context;

public class JmxServerMonitoring
{
   static final String HELP_TEXT =
      "JmxServerMonitoring:\n" +
      "  Ermittlung der prozentualen Garbage Collection per JMX.\n" +
      "Verschiedene Ergebnisse koennen erzeugt werden:\n" +
      "  'console=true':\n" +
      "     Ausgabe im Kommandozeilenfenster.\n" +
      "  'nagiosfile=JmxServerMonitoring.nagios.txt':\n" +
      "     Nur letzte Ergebnisse (z.B. fuer Nagios).\n" +
      "  'csvfile=JmxServerMonitoring.csv':\n" +
      "     Alle Ergebnisse (.csv-Datei, z.B. fuer Excel).\n" +
      "  'errorfile=JmxServerMonitoring.error.log':\n" +
      "     Datei fuer Fehlermeldungen (z.B. Exceptions).\n" +
      "  'periodminutes=10':\n" +
      "     Messintervallzeit in Minuten.\n" +
      "Als URL kann die Hostadresse oder IP-Adresse angegeben werden, gefolgt von der Portnummer. " +
      "Es koennen nur eine JVM (bzw. Server) oder mehrere gleichzeitig ueberwacht werden:\n" +
      "  'url=localhost:8686 usr=admin pwd=adminadmin':\n" +
      "     Ein einzelner Server (z.B. mit GlassFish-Portnummer).\n" +
      "  'url=srv1:7091,srv2:7092,srv3:7093' usr=xy pwd=yz':\n" +
      "     Drei Server mit gleichem Benutzernamen und gleichem Kennwort.\n" +
      "  'url=srv1:7091,srv2:7092 usr=u1,u2 pwd=p1,p2':\n" +
      "     Zwei Server mit verschiedenen Benutzernamen/Kennwoertern.\n" +
      "  'usr=Benutzername pwd=Kennwort':\n" +
      "     Nur notwendig, wenn Authentifizierung erforderlich ist.\n" +
      "Parameter koennen per Kommandozeile oder Properties-Datei uebergeben werden:\n" +
      "  'propfile=JmxServerMonitoring.properties':\n" +
      "     Angabe der Properties-Datei.\n" +
      "Zwei Beispiele fuer Aufrufkommandos:\n" +
      "  java JmxServerMonitoring propfile=JmxServerMonitoring.properties\n" +
      "  java JmxServerMonitoring url=localhost:8686 console=true csvfile=JmxServerMonitoring.csv\n";
   static final String KEY_PROPFILE       = "propfile";
   static final String KEY_PERIODMINUTES  = "periodminutes";
   static final String KEY_SERVERNAME     = "servername";
   static final String KEY_URL            = "url";
   static final String KEY_USR            = "usr";
   static final String KEY_PWD            = "pwd";
   static final String KEY_CONSOLE        = "console";
   static final String KEY_ALLGCVALUES    = "allgcvalues";
   static final String KEY_NAGIOSFILE     = "nagiosfile";
   static final String KEY_CSVFILE        = "csvfile";
   static final String KEY_ERRORFILE      = "errorfile";
   static final String KEY_ATTR           = "attr";
   static final String DFLT_PERIODMINUTES = "10";
   static final String DFLT_PROPFILE      = "JmxServerMonitoring.properties";
   static final String DFLT_NAGIOSFILE    = "JmxServerMonitoring.nagios.txt";
   static final String DFLT_ERRORFILE     = "JmxServerMonitoring.error.log";
   static final String DFLT_CONSOLE       = "true";
   static final SimpleDateFormat YYYYMMDD_HHMMSS_STD = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
   static final SimpleDateFormat YYYYMMDD_HHMMSS_NAG = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss");
   static final DecimalFormat    DECIMAL_FORMAT1     = new DecimalFormat( "0.0" );
   static final DecimalFormat    DECIMAL_FORMAT2     = new DecimalFormat( "0.00" );

   // Main()
   public static void main( String[] args ) throws Exception
   {
      Properties props = readProperties( args, KEY_PROPFILE, DFLT_PROPFILE, new String[] {
            KEY_PERIODMINUTES, DFLT_PERIODMINUTES, KEY_CONSOLE, DFLT_CONSOLE,
            KEY_NAGIOSFILE, DFLT_NAGIOSFILE, KEY_ERRORFILE, DFLT_ERRORFILE } );

      int     periodMinutes = Math.max( Integer.parseInt( props.getProperty( KEY_PERIODMINUTES ) ), 1 );
      String  serverName    = props.getProperty( KEY_SERVERNAME );
      String  url           = props.getProperty( KEY_URL );
      String  usr           = props.getProperty( KEY_USR );
      String  pwd           = props.getProperty( KEY_PWD );
      String  nagiosFile    = props.getProperty( KEY_NAGIOSFILE );
      String  csvFile       = props.getProperty( KEY_CSVFILE );
      String  errorFile     = props.getProperty( KEY_ERRORFILE );
      String  s             = props.getProperty( KEY_CONSOLE );
      boolean console       = s != null && (s.equals( "1" ) || s.equalsIgnoreCase( "true" ));
      s                     = props.getProperty( KEY_ALLGCVALUES );
      boolean allGcValues   = s != null && (s.equals( "1" ) || s.equalsIgnoreCase( "true" ));

      System.out.println( "JmxServerMonitoring (periodminutes=" + periodMinutes +
            ", servername=" + serverName + ", url=" + url + ", usr=" + usr +
            ", nagiosfile=" + nagiosFile + ", csvfile=" + csvFile + ", errorfile=" + errorFile + ")\n" );

      if( url == null || url.trim().length() <= 0 ) {
         System.out.println( "Fehler: Unvollstaendige Angaben.\n" );
         System.out.println( HELP_TEXT );
         System.exit( 255 );
      }

      ServerData[] serverDataArr = convertSrvParameter( serverName, url, usr, pwd );
      AttributeValueAndName[] attributeNames = convertAttrParameter( props, KEY_ATTR );
      writeJmxServerMonitoring(
            periodMinutes, serverDataArr, attributeNames, console, allGcValues, nagiosFile, csvFile, errorFile );
   }

   // Aufsplittung der Server-Parameter auf mehrere Server
   static ServerData[] convertSrvParameter( String serverName, String url, String usr, String pwd )
   {
      if( url == null || url.trim().length() <= 0 ) return null;
      String[] urlArr = url.split( ",|;|\\s" );
      String[] usrArr = ( usr != null ) ? usr.split( ",|;|\\s" ) : null;
      String[] pwdArr = ( pwd != null ) ? pwd.split( ",|;|\\s" ) : null;
      String[] serverNameArr = ( serverName != null ) ? serverName.split( ",|;|\\s" ) : null;
      boolean  sn = serverName != null && serverNameArr.length == urlArr.length;
      boolean  up = usr != null && usrArr.length == urlArr.length &&
                    pwd != null && pwdArr.length == urlArr.length;
      ServerData[] serverDataArr = new ServerData[urlArr.length];
      for( int i = 0; i < serverDataArr.length; i++ ) {
         serverDataArr[i] = new ServerData();
         serverDataArr[i].url = urlArr[i];
         serverDataArr[i].usr = ( up ) ? usrArr[i] : usr;
         serverDataArr[i].pwd = ( up ) ? pwdArr[i] : pwd;
         serverDataArr[i].serverName = ( sn ) ? serverNameArr[i] : null;
         serverDataArr[i].serverNameUndUrl = ( serverDataArr[i].serverName != null )
               ? serverDataArr[i].serverName + "-" + serverDataArr[i].url
               : serverDataArr[i].url;
         if(serverDataArr[i].serverName == null) {
            serverDataArr[i].serverName = serverDataArr[i].url;
         }
      }
      return serverDataArr;
   }

   // Aufsplittung eventueller zusaetzlicher MBean-Attribut-Abfragen
   static AttributeValueAndName[] convertAttrParameter( Properties props, String key )
   {
      List<AttributeValueAndName> attributeNameList = new ArrayList<AttributeValueAndName>();
      for( int i=1; i<1000; i++ ) {
         String s = props.getProperty( key + i );
         if( s == null || s.trim().length() <= 0 ) break;
         String[] ss = s.split( ";" );
         if( ss == null || ss.length < 4 ) break;
         for( int j=0; j<ss.length; j++ ) if( ss[j] != null ) ss[j] = ss[j].trim();
         AttributeValueAndName attributeName = new AttributeValueAndName();
         attributeName.diff = ss[0] != null && ss[0].toLowerCase().startsWith( "diff" );
         attributeName.title = ss[1];
         attributeName.attributeName = ss[2];
         attributeName.objectName = ss[3];
         if( ss.length > 4 ) attributeName.methodName = ss[4];
         if( ss.length > 5 ) {
            attributeName.methodParms = new String[ss.length - 5];
            System.arraycopy( ss, 5, attributeName.methodParms, 0, attributeName.methodParms.length );
         }
         attributeNameList.add( attributeName );
      }
      return attributeNameList.toArray( new AttributeValueAndName[attributeNameList.size()] );
   }

   // Zeitschleife ueber Garbage-Collection-Ermittlung und Ergebnisse-Schreiben
   static void writeJmxServerMonitoring( int periodMinutes,
         ServerData[] serverDataArr,
         AttributeValueAndName[] attributeNames,
         boolean showConsole, boolean writeAllGcValues, String nagiosFile, String csvFile, String errorFile )
   {
      long periodTime = (new Date()).getTime();
      JMXConnector jmxConnector = null;

      // Zeitschleife:
      while( true ) {
         // Schleife ueber alle Server:
         for( ServerData serverData : serverDataArr ) {
            try {
               // JMX- und MBeanServer-Connection:
               jmxConnector = getJMXConnector( serverData.url, serverData.usr, serverData.pwd );
               MBeanServerConnection mBeanServerConn = jmxConnector.getMBeanServerConnection();
               // Lies Garbage Collections:
               serverData.gcGroup = getGarbageCollectionGroup(
                     periodMinutes, serverData.lastMeasurement, mBeanServerConn );
               // Lies eventuell zusaetzliche MBean-Attribute:
               serverData.attributes = getAttributes( attributeNames,
                     periodMinutes, serverData.lastMeasurement, mBeanServerConn );
            } catch( Exception ex ) {
               serverData.lastMeasurement.clear();
               serverData.gcGroup = null;
               serverData.attributes = attributeNames;
               String s = YYYYMMDD_HHMMSS_STD.format( new Date() ) + ", Url=" + serverData.url + ": ";
               System.out.println( s );
               System.out.println( ex );
               writeErrorFile( s, ex, errorFile );
            } finally {
               try { if( jmxConnector != null ) jmxConnector.close(); } catch( Exception ex ) {/*ok*/}
               jmxConnector = null;
            }
         }
         // Schreibe Ergebnisse:
         writeConsole( serverDataArr, showConsole );
         writeNagiosFile( serverDataArr, nagiosFile );
         writeCsvFileOneForAllServers( serverDataArr, csvFile );
         writeCsvFilePerServerWithDifferentGcValues( serverDataArr, csvFile, writeAllGcValues );
         // Zeitintervall:
         periodTime += periodMinutes * 60 * 1000;
         long waitMilliseconds = periodTime - (new Date()).getTime();
         if( waitMilliseconds > 0 ) {
            try { Thread.sleep( waitMilliseconds ); } catch( InterruptedException ex ) {/*ok*/}
         }
      }
   }

   // Ermittlung einer Gruppe von Garbage-Collection-Werten zu einem einzelnen Server
   static GarbageCollectionGroup getGarbageCollectionGroup( int periodMinutes,
         Map<String,Long[]> lastMeasurement, MBeanServerConnection mBeanServerConn ) throws Exception
   {
      // Lies bisherige Laufzeit der JVM aus Remote-Runtime-MXBean:
      long rtUptimeMs = getRuntimeMXBeanFromRemote( mBeanServerConn ).getUptime();
      // Lies GarbageCollector-MXBeans von Remote:
      List<GarbageCollectorMXBean> gcMXBeans = getGarbageCollectorMXBeansFromRemote( mBeanServerConn );
      // Verschiedene Garbage-Collector-Arten:
      GarbageCollectionGroup gcGroup = new GarbageCollectionGroup();
      for( GarbageCollectorMXBean gc : gcMXBeans ) {
         GarbageCollectionSingle gcSingle = new GarbageCollectionSingle();
         gcSingle.gcName = gc.getName();
         if( gcSingle.gcName != null && gcSingle.gcName.indexOf( "Young" ) > 0 ) {
             gcSingle.gcName = gcSingle.gcName.substring( gcSingle.gcName.indexOf( "Young" ) );
         }
         if( gcSingle.gcName != null && gcSingle.gcName.indexOf( "Old" ) > 0 ) {
             gcSingle.gcName = gcSingle.gcName.substring( gcSingle.gcName.indexOf( "Old" ) );
         }
         Long[] gcLast = lastMeasurement.get( gcSingle.gcName );
         if( gcLast != null ) {
            gcSingle.gcCountPerPeriod =  gc.getCollectionCount() - gcLast[0].longValue();
            gcSingle.gcTimePercent    = ((gc.getCollectionTime() - gcLast[1].longValue()) /
                                                               (periodMinutes * 60)) / 10.;
         }
         if( gcLast == null || gcSingle.gcCountPerPeriod < 0 || gcSingle.gcTimePercent < 0 ) {
            // Erstmaliger Aufruf (oder Server-Reboot):
            gcSingle.gcCountPerPeriod = gc.getCollectionCount() * periodMinutes * 60 * 1000 / rtUptimeMs;
            gcSingle.gcTimePercent    = (gc.getCollectionTime() * 1000 / rtUptimeMs) / 10.;
         }
         lastMeasurement.put( gcSingle.gcName, new Long[] { new Long( gc.getCollectionCount() ),
                                                            new Long( gc.getCollectionTime() ) } );
         gcGroup.gcSingles.add( gcSingle );
         gcGroup.gcTimePercentSum += gcSingle.gcTimePercent;
      }
      // CPU-Zeit:
      gcGroup.cpuTimePercent = calculateCpuTimePercent( rtUptimeMs, lastMeasurement, mBeanServerConn );
      return gcGroup;
   }

   // CPU-Zeit
   static int calculateCpuTimePercent( long rtUptimeMs, Map<String,Long[]> lastMeasurement,
         MBeanServerConnection mBeanServerConn ) throws Exception
   {
      final String CPUTIME_ATTRIBUTENAME = "ProcessCpuTime";
      final String CPUTIME_OBJECTNAME    = "java.lang:type=OperatingSystem";
      final String CPUTIME_KEY = CPUTIME_ATTRIBUTENAME + "::" + CPUTIME_OBJECTNAME;
      try {
         Long   cpuTime = (Long) mBeanServerConn.getAttribute(
                                 new ObjectName( CPUTIME_OBJECTNAME ), CPUTIME_ATTRIBUTENAME );
         if( cpuTime == null ) return -1;
         Long[] lastCpuTimeVals = lastMeasurement.get( CPUTIME_KEY );
         lastMeasurement.put( CPUTIME_KEY, new Long[] { new Long( rtUptimeMs ), cpuTime } );
         OperatingSystemMXBean op = getOperatingSystemMXBeanFromRemote( mBeanServerConn );
         long cpuCount = Math.max( 1, op.getAvailableProcessors() );
         long lastRtUptimeMs = 0;
         long lastCpuTime = 0;
         if( lastCpuTimeVals != null && lastCpuTimeVals.length > 1 ) {
            lastRtUptimeMs = lastCpuTimeVals[0].longValue();
            lastCpuTime    = lastCpuTimeVals[1].longValue();
         }
         return (int) Math.min( 99, (cpuTime.longValue() - lastCpuTime) /
                                    ((rtUptimeMs - lastRtUptimeMs) * cpuCount * 10000) );
      } catch( Exception ex ) {
         return -1;
      }
   }

   // Eventuell zusaetzliche MBean-Attribut-Abfragen
   static AttributeValueAndName[] getAttributes( AttributeValueAndName[] attributeNames, int periodMinutes,
         Map<String,Long[]> lastMeasurement, MBeanServerConnection mBeanServerConn ) throws Exception
   {
      if( attributeNames == null || attributeNames.length <= 0 ) return null;
      List<AttributeValueAndName> attributesList = new ArrayList<AttributeValueAndName>();
      for( AttributeValueAndName attrNam : attributeNames ) {
         boolean attrFound = false;
         createJRockitConsoleMBean( attrNam.objectName, mBeanServerConn );
         Set<ObjectName> objectNames = mBeanServerConn.queryNames( new ObjectName( attrNam.objectName.trim() ), null );
         for( ObjectName objectName : objectNames ) {
            Object obj = null;
            if( attrNam.attributeName.trim().equalsIgnoreCase( "invoke" ) ) {
               obj = invoke( attrNam.methodName, attrNam.methodParms, objectName, mBeanServerConn );
            } else {
               obj = mBeanServerConn.getAttribute( objectName, attrNam.attributeName.trim() );
            }
            AttributeValueAndName attrVal = new AttributeValueAndName();
            attrVal.diff          = attrNam.diff;
            attrVal.title         = attrNam.title;
            attrVal.attributeName = attrNam.attributeName;
            attrVal.objectName    = "" + objectName;
            long actVal = -1;
            try {
               actVal = Long.parseLong( "" + obj );
            } catch( Exception ex ) {/*ok*/}
            if( !attrVal.diff || actVal < 0 || periodMinutes <= 0 ) {
               // Keine Differenzberechnung:
               attrVal.value = ( obj instanceof Double ) ? DECIMAL_FORMAT2.format( obj ) : ("" + obj);
            } else {
               // Differenzberechnung und Umrechnung auf Anzahl pro Sekunde:
               String key = attrVal.attributeName + "::" + attrVal.objectName;
               Long[] lastVal = lastMeasurement.get( key );
               lastMeasurement.put( key, new Long[] { new Long( actVal ) } );
               long v = 0;
               if( lastVal != null && lastVal[0] != null && (v = actVal - lastVal[0].longValue()) >= 0 ) {
                  // Es gibt einen gueltigen letzten Wert:
                  v = v / periodMinutes / 6;
               } else {
                  // Lies bisherige Laufzeit der JVM aus Remote-Runtime-MXBean:
                  long rtUptimeMs = getRuntimeMXBeanFromRemote( mBeanServerConn ).getUptime();
                  v = actVal * 10000 / rtUptimeMs;
               }
               attrVal.value = DECIMAL_FORMAT1.format( v / 10. );
            }
            attributesList.add( attrVal );
            attrFound = true;
         }
         if( !attrFound ) {
            attributesList.add( attrNam );
         }
      }
      return attributesList.toArray( new AttributeValueAndName[attributesList.size()] );
   }

   // Rufe MBean-Methode auf
   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 );
   }

   // JMX-Connection
   static JMXConnector getJMXConnector( String url, String usr, String pwd )
   throws MalformedURLException, IOException
   {
      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 );
   }

   // Lies Runtime-MXBean von Remote
   static RuntimeMXBean getRuntimeMXBeanFromRemote( MBeanServerConnection mBeanServerConn )
   throws IOException
   {
      return ManagementFactory.newPlatformMXBeanProxy( mBeanServerConn,
             ManagementFactory.RUNTIME_MXBEAN_NAME, RuntimeMXBean.class );
   }

   // Lies OperatingSystem-MXBean von Remote
   static OperatingSystemMXBean getOperatingSystemMXBeanFromRemote( MBeanServerConnection mBeanServerConn )
   throws IOException
   {
      return ManagementFactory.newPlatformMXBeanProxy( mBeanServerConn,
             ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME, OperatingSystemMXBean.class );
   }

   // Lies GarbageCollector-MXBeans von Remote
   static List<GarbageCollectorMXBean> getGarbageCollectorMXBeansFromRemote( MBeanServerConnection mBeanServerConn )
   throws MalformedObjectNameException, NullPointerException, IOException
   {
      List<GarbageCollectorMXBean> gcMXBeans = new ArrayList<GarbageCollectorMXBean>();
      ObjectName gcAllObjectName = new ObjectName( ManagementFactory.GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE + ",*" );
      Set<ObjectName> gcMXBeanObjectNames = mBeanServerConn.queryNames( gcAllObjectName, null );
      for( ObjectName on : gcMXBeanObjectNames ) {
         GarbageCollectorMXBean gc = ManagementFactory.newPlatformMXBeanProxy(
               mBeanServerConn, on.getCanonicalName(), GarbageCollectorMXBean.class );
         gcMXBeans.add( gc );
      }
      return gcMXBeans;
   }

   // 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( "bea.jrockit.management" ) ) return;
      try {
         mBeanServerConn.getMBeanInfo( new ObjectName( "bea.jrockit.management:type=JRockitConsole" ) );
      } catch( InstanceNotFoundException ex ) {
         mBeanServerConn.createMBean( "bea.jrockit.management.JRockitConsole", null );
      }
   }

   // Anzeige im Kommandozeilenfenster
   static void writeConsole( ServerData[] serverDataArr, boolean showConsole )
   {
      if( serverDataArr == null || serverDataArr.length <= 0 || !showConsole ) return;
      for( ServerData serverData : serverDataArr ) {
         if( serverData.gcGroup == null ) continue;
         System.out.print( YYYYMMDD_HHMMSS_STD.format( serverData.gcGroup.dateTime ) + ": " );
         System.out.print( serverData.serverNameUndUrl + ": " );
         System.out.println( "GarbageCollectionPercent = " +
               DECIMAL_FORMAT1.format( serverData.gcGroup.gcTimePercentSum ) + " %" );
      }
      for( ServerData serverData : serverDataArr ) {
         if( serverData.gcGroup == null ) continue;
         System.out.print( YYYYMMDD_HHMMSS_STD.format( serverData.gcGroup.dateTime ) + ": " );
         System.out.print( serverData.serverNameUndUrl + ": " );
         System.out.println( "CpuTimePercent = " + serverData.gcGroup.cpuTimePercent + " %" );
      }
      for( ServerData serverData : serverDataArr ) {
         if( serverData.attributes == null || serverData.attributes.length <= 0 ) continue;
         for( AttributeValueAndName attr : serverData.attributes ) {
            if( attr.value == null || attr.value.length() <= 0 ||
                attr.value.equals( AttributeValueAndName.ERR_VALUE ) ) continue;
            System.out.print( YYYYMMDD_HHMMSS_STD.format( attr.dateTime ) + ": " );
            System.out.print( serverData.serverNameUndUrl + ": " );
            System.out.println( attr.title + " = " + attr.value );
         }
      }
      System.out.println();
   }

   // Schreibe letzte Summenergebnisse in Datei (z.B. fuer Nagios)
   static void writeNagiosFile( ServerData[] serverDataArr, String nagiosFile )
   {
      if( serverDataArr == null || serverDataArr.length <= 0 ||
            nagiosFile == null || nagiosFile.trim().length() <= 0  ) return;
      BufferedWriter out = null;
      try {
         Date dat = new Date();
         out = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( nagiosFile ) ) );
         out.write( "SecondsSince1970=" + (dat.getTime() / 1000) );
         out.newLine();
         out.write( "DateTime=" + YYYYMMDD_HHMMSS_NAG.format( dat ) );
         out.newLine();
         for( ServerData serverData : serverDataArr ) {
            if( serverData.gcGroup == null) continue;
            out.write( serverData.serverName.replaceAll( "[:-]", "." ) + "." );
            out.write( "GarbageCollectionPercent=" +
                  DECIMAL_FORMAT1.format( serverData.gcGroup.gcTimePercentSum ).replace( ',', '.' ) );
            out.newLine();
         }
         for( ServerData serverData : serverDataArr ) {
            if( serverData.gcGroup == null) continue;
            out.write( serverData.serverName.replaceAll( "[:-]", "." ) + "." );
            out.write( "CpuTimePercent=" + serverData.gcGroup.cpuTimePercent );
            out.newLine();
         }
         for( ServerData serverData : serverDataArr ) {
            if( serverData.attributes == null || serverData.attributes.length <= 0 ) continue;
            for( AttributeValueAndName attr : serverData.attributes ) {
               if( attr.value == null || attr.value.length() <= 0 ||
                   attr.value.equals( AttributeValueAndName.ERR_VALUE ) ) continue;
               out.write( serverData.serverName.replaceAll( "[:-]", "." ) + "." );
               out.write( attr.title + "=" + attr.value.replace( ',', '.' ).replaceAll( "\r\n|\r|\n", ". " ) );
               out.newLine();
            }
         }
      } catch( Exception exWrite ) {
         System.out.println( "Fehler beim Schreiben der Nagios-Datei '" + nagiosFile + "': " + exWrite );
      } finally {
         if( out != null ) try { out.close(); } catch( Exception exClose ) {/*ok*/}
      }
   }

   // Ausgabe in einzelne CSV-Datei (Comma Separated Values, z.B. fuer Excel):
   // Fuer alle Server eine gemeinsame CSV-Datei (GC: nur mit den Summenwerten)
   static void writeCsvFileOneForAllServers( ServerData[] serverDataArr, String csvFile )
   {
      if( serverDataArr == null || serverDataArr.length <= 0 ||
            csvFile == null || csvFile.trim().length() <= 0 ) return;
      BufferedWriter out = null;
      try {
         boolean exists = (new File( csvFile )).exists();
         out = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( csvFile, true ) ) );
         if( !exists ) {
            out.write( "Datum/Zeit;" );
            for( ServerData serverData : serverDataArr ) {
               out.write( " " + (( serverData != null ) ? ("GC-" + serverData.serverName) : "?") + ";" );
            }
            for( ServerData serverData : serverDataArr ) {
               out.write( " " + (( serverData != null ) ? ("CPU-" + serverData.serverName) : "?") + ";" );
            }
            for( ServerData serverData : serverDataArr ) {
               if( serverData != null && serverData.attributes != null ) {
                  for( AttributeValueAndName attr : serverData.attributes ) {
                     out.write( " " + attr.title + "-" + serverData.serverName + ";" );
                  }
               }
            }
            out.newLine();
         }
         out.write( YYYYMMDD_HHMMSS_STD.format( new Date() ) + ";" );
         for( ServerData serverData : serverDataArr ) {
            double d = ( serverData != null && serverData.gcGroup != null ) ? serverData.gcGroup.gcTimePercentSum : -0.1;
            out.write( " " + DECIMAL_FORMAT1.format( d ) + ";" );
         }
         for( ServerData serverData : serverDataArr ) {
            out.write( (( serverData != null && serverData.gcGroup != null ) ?
                        (" " + serverData.gcGroup.cpuTimePercent) : " -0.1") + ";" );
         }
         for( ServerData serverData : serverDataArr ) {
            if( serverData != null && serverData.attributes != null ) {
               for( AttributeValueAndName attr : serverData.attributes ) {
                  out.write( " " + attr.value.replaceAll( "\r\n|\r|\n", ". " ) + ";" );
               }
            }
         }
         out.newLine();
      } catch( Exception exWrite ) {
         System.out.println( "Fehler beim Schreiben der CSV-Datei '" + csvFile + "': " + exWrite );
      } finally {
         if( out != null ) try { out.close(); } catch( Exception exClose ) {/*ok*/}
      }
   }

   // Ausgabe in mehrere CSV-Dateien (Comma Separated Values, z.B. fuer Excel):
   // Pro Server eine CSV-Datei mit allen einzelnen GC-Werten
   static void writeCsvFilePerServerWithDifferentGcValues( ServerData[] serverDataArr, String csvFileOhneUrl, boolean writeAllGcValues )
   {
      if( !writeAllGcValues || serverDataArr == null || serverDataArr.length <= 0 ||
            csvFileOhneUrl == null || csvFileOhneUrl.trim().length() <= 0 ) return;
      for( ServerData serverData : serverDataArr ) {
         String urlInsert = "-" + serverData.url.replace( ':', '.' );
         int e = csvFileOhneUrl.lastIndexOf( '.' );
         String csvFileMitUrl = ( e > 0 && e < csvFileOhneUrl.length() - 1 )
               ? csvFileOhneUrl.substring( 0, e ) + urlInsert + csvFileOhneUrl.substring( e )
               : csvFileOhneUrl + urlInsert + ".csv";
         BufferedWriter out = null;
         try {
            if( serverData.gcGroup == null || serverData.gcGroup.gcSingles == null ||
                  serverData.gcGroup.gcSingles.size() <= 0 ) continue;
            boolean exists = (new File( csvFileMitUrl )).exists();
            out = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( csvFileMitUrl, true ) ) );
            if( !exists ) {
               out.write( "Datum/Zeit; " );
               for( GarbageCollectionSingle gcSingle : serverData.gcGroup.gcSingles ) {
                  int n;
                  String s = gcSingle.gcName;
                  if( s == null ) s = "";
                  if( (n = s.lastIndexOf( " Collector" )) > 1 ) s = s.substring( 0, n );
                  s = s.trim();
                  if( s.length() > 0 ) s = s + "-";
                  out.write( s + "CountPerPeriod; " + s + "TimePercent; " );
               }
               out.write( "TimePercentSum;" );
               out.newLine();
            }
            out.write( YYYYMMDD_HHMMSS_STD.format( serverData.gcGroup.dateTime ) + "; " );
            for( GarbageCollectionSingle gcSingle : serverData.gcGroup.gcSingles ) {
               out.write( gcSingle.gcCountPerPeriod + "; " +
                     DECIMAL_FORMAT1.format( gcSingle.gcTimePercent ) + "; " );
            }
            out.write( DECIMAL_FORMAT1.format( serverData.gcGroup.gcTimePercentSum ) + "; " );
            out.newLine();
         } catch( Exception exWrite ) {
            System.out.println( "Fehler beim Schreiben der CSV-Datei '" + csvFileMitUrl + "': " + exWrite );
         } finally {
            if( out != null ) try { out.close(); } catch( Exception exClose ) {/*ok*/}
         }
      }
   }

   // Schreibe Exceptions in Fehlerdatei
   static void writeErrorFile( String s, Exception ex, String errorFile )
   {
      if( errorFile == null || errorFile.trim().length() <= 0 ) return;
      BufferedWriter out = null;
      try {
         out = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( errorFile, true ) ) );
         out.newLine();
         if( s != null ) out.write( s );
         out.newLine();
         if( ex != null ) out.write( ex.toString() );
         out.newLine();
         out.close();
      } catch( Exception exWrite ) {
         System.out.println( "Fehler beim Schreiben in die Datei '" + errorFile + "': " + exWrite );
      } finally {
         if( out != null ) try { out.close(); } catch( Exception exClose ) {/*ok*/}
      }
   }

   // Lies Parameter in ein Properties-Objekt:
   //   a) Parameter aus der Kommandozeile (haben Vorrang),
   //   b) Parameter aus einer Properties-Datei,
   //   c) Eventuell Default-Parameter (falls Parameter nicht anderweitig gesetzt wurde).
   // Falls ueber die Kommandozeile eine Properties-Datei definiert wurde, muss diese vorhanden und lesbar sein
   // (Fehler fuehren zu Fehlermeldungen).
   // Falls nicht: Es wird nach der als Methodenparameter uebergebenen (Default-)Properties-Datei gesucht.
   // Gibt es diese nicht, erscheint keine Fehlermeldung.
   static Properties readProperties( String[] args, String keyPropFile, String propFileTry, String[] defaultProps )
   throws Exception
   {
      Properties props = new Properties();
      String propFilePrio = null;
      String s;

      // Wenn Properties-Dateiname als Kommandozeilenparameter uebergeben wurde, dann hat dies Vorrang:
      if( args != null && args.length > 0 && keyPropFile != null && keyPropFile.trim().length() > 0 &&
            args[0].toLowerCase().startsWith( keyPropFile + "=" ) ) {
         propFilePrio = args[0].substring( keyPropFile.length() + 1 );
      }

      // Der als Methodenparameter uebergebene Properties-Dateiname wird nur verwendet,
      // wenn diese Datei existiert (ansonsten keine Fehlermeldung):
      if( propFilePrio == null && propFileTry != null && propFileTry.trim().length() > 0 &&
            (new File( propFileTry )).exists() ) {
         propFilePrio = propFileTry;
      }

      // Falls ein Properties-Dateiname ermittelt werden konnte:
      // Lies 'Key=Value'-Paare aus Property-Datei (Fehler fuehrt zu Fehlermeldung):
      if( propFilePrio != null && propFilePrio.trim().length() > 0 ) {
         try {
            props.load( new FileInputStream( propFilePrio ) );
            System.out.println( "Property-Datei: '" + propFilePrio + "'." );
         } catch( FileNotFoundException ex ) {
            throw new Exception( "Fehler: Property-Datei '" + propFilePrio + "' fehlt: ", ex );
         } catch( IOException ex ) {
            throw new Exception( "Fehler beim Lesen der Datei '" + propFilePrio + "': ", ex );
         }
      }

      // Lies 'Key=Value'-Paare aus Kommandozeilenparametern
      // (koennen Parameter aus obiger Property-Datei ueberschreiben):
      for( int i = 0; args != null && i < args.length; i++ ) {
         int delimPos = args[i].indexOf( '=' );
         if( delimPos > 0 && delimPos <= args[i].length() - 2 ) {
            props.put( args[i].substring( 0, delimPos ).trim().toLowerCase(), args[i]
                  .substring( delimPos + 1 ).trim() );
         }
      }

      // Lade eventuell Defaultwerte:
      if( defaultProps != null && defaultProps.length / 2 > 0 ) {
         for( int i = 0; i < defaultProps.length / 2; i++ ) {
            if( (s = props.getProperty( defaultProps[i * 2] )) == null
                  || s.trim().length() == 0 ) {
               props.put( defaultProps[i * 2], defaultProps[i * 2 + 1] );
            }
         }
      }
      return props;
   }
}

// Zugangsparameter und Ergebnisse eines Servers
class ServerData
{
   String                  serverNameUndUrl;
   String                  serverName;
   String                  url;
   String                  usr;
   String                  pwd;
   Map<String,Long[]>      lastMeasurement = new HashMap<String,Long[]>();
   GarbageCollectionGroup  gcGroup = null;
   AttributeValueAndName[] attributes = null;
}

// Gruppe von Garbage-Collection-Werten (verschiedene Garbage-Collection-Arten) und CPU-Zeit
class GarbageCollectionGroup
{
   Date                          dateTime = new Date();
   List<GarbageCollectionSingle> gcSingles = new ArrayList<GarbageCollectionSingle>();
   double                        gcTimePercentSum;
   long                          cpuTimePercent;
}

// Messwerte einer einzelnen Garbage-Collection-Art
class GarbageCollectionSingle
{
   String gcName;
   long   gcCountPerPeriod;
   double gcTimePercent;
}

// Falls Abfrage zusaetzlicher MBean-Attribute: Namen und Messwert
class AttributeValueAndName
{
   static final String ERR_VALUE = "-0,1";
   Date     dateTime = new Date();
   boolean  diff;
   String   value = ERR_VALUE;
   String   title;
   String   attributeName;
   String   objectName;
   String   methodName;
   String[] methodParms;
}

Eine Anwendung und ein Beispiel für einen Kommandozeilenaufruf finden Sie im folgenden Kapitel MeinBoesesMemoryLeak.



MeinBoesesMemoryLeak

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.util.*;

public class MeinBoesesMemoryLeak
{
   public static void main( String[] args ) throws InterruptedException
   {
      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) );
      }
   }
}

Erzeugen Sie MeinBoesesMemoryLeak.java und JmxServerMonitoring.java in einem src-Unterverzeichnis, erzeugen Sie ein bin-Unterverzeichnis und kompilieren Sie die Sourcen:

javac -d bin src/*.java

Starten Sie dieses Memory-Leak-Programm, das obige Monitoring-Programm und JConsole kurz hintereinander mit folgenden Kommandos (passen Sie die Classpathes "-cp bin" an):

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 java -cp bin JmxServerMonitoring periodminutes=1 console=true allgcvalues=true csvfile=JmxServerMonitoring.csv url=localhost:4711

start jconsole -interval=1 localhost:4711

Circa einmal pro Minute während MeinBoesesMemoryLeak läuft:

type  JmxServerMonitoring.nagios.txt

type  JmxServerMonitoring-localhost.4711.csv

Nach Beendigung von MeinBoesesMemoryLeak die .csv-Datei zum Beispiel in Excel o.ä. laden:

start JmxServerMonitoring-localhost.4711.csv

In der .csv-Datei JmxServerMonitoring-localhost.4711.csv finden Sie alle GC-Ergebnisse: Zusätzlich zur summarischen prozentualen Garbage-Collection-Zeit auch die Zeiten aufgeteilt nach Minor und Major Collection und auch die Anzahl der Collections. Diese Datei können Sie in Excel laden und Kurvenverlaufsgrafiken erstellen, wie oben beschrieben ist.

Die prozentuale GC-Zeit sollte normalerweise deutlich kleiner als 10 % sein. Bei diesem Beispiel steigt sie bis über 90 %, bevor sich MeinBoesesMemoryLeak mit OutOfMemoryError verabschiedet:

2008-09-06 00:00:00: localhost:4711: GarbageCollectionPercent = 0.4 %

2008-09-06 00:01:00: localhost:4711: GarbageCollectionPercent = 63.0 %

2008-09-06 00:02:00: localhost:4711: GarbageCollectionPercent = 45.4 %

2008-09-06 00:03:00: localhost:4711: GarbageCollectionPercent = 34.8 %

2008-09-06 00:04:00: localhost:4711: GarbageCollectionPercent = 75.1 %

2008-09-06 00:05:00: localhost:4711: GarbageCollectionPercent = 90.7 %

Bitte beachten: JmxServerMonitoring meldet die Collection-Anzahl und die verbrauchte Zeit pro Messintervall, während JConsole die Summe seit JVM-Start anzeigt.

JConsole.png

In jmx.htm finden Sie eine längere Version von MeinBoesesMemoryLeak, welche zusätzlich auch intern die prozentuale Garbage Collection und die CPU-Auslastung ermittelt.



Weitere Informationen

JEE-Applikations-Monitoring per MBean, EJB Timer Service und DB

Im Javamagazin-Artikel Überwachung von JEE-Applikationen bei 1&1 stellt Dr. Dietmar Posselt vor, wie Zustandsinformationen aus den JEE-Applikationenen von einem EJB Timer Service gesteuert periodisch über MBeans gesammelt und in einer Datenbank aggregiert und gespeichert werden können.

Monitoring, Management und Profiling Tools

Hierzu finden Sie eine Liste von Matthew Quinlan unter: http://www.jboss.org/community/docs/DOC-11440.




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