Profilierung der CPU-Auslastung in einer Java-Anwendung

8. November 2002

F: Wie bestimmen Sie die CPU-Auslastung in Java?

A: Also, hier sind die guten und die schlechten Nachrichten. Die schlechte Nachricht ist, dass eine programmgesteuerte Abfrage der CPU-Auslastung mit reinem Java nicht möglich ist. Dafür gibt es einfach keine API. Eine vorgeschlagene Alternative könnte verwendet werden Runtime.exec(), um die Prozess-ID (PID) der JVM zu bestimmen, einen externen, plattformspezifischen Befehl wie aufzurufen psund seine Ausgabe für die PID von Interesse zu analysieren. Dieser Ansatz ist jedoch bestenfalls fragil.

Die gute Nachricht ist jedoch, dass eine zuverlässige Lösung erreicht werden kann, indem Sie außerhalb von Java treten und einige C-Codezeilen schreiben, die über Java Native Interface (JNI) in die Java-Anwendung integriert werden. Ich zeige unten, wie einfach es ist, eine einfache JNI-Bibliothek für die Win32-Plattform zu erstellen. Der Abschnitt Ressourcen enthält einen Link zu der Bibliothek, die Sie an Ihre eigenen Bedürfnisse anpassen und auf andere Plattformen portieren können.

Im Allgemeinen ist die Verwendung von JNI etwas komplex. Wenn Sie jedoch nur in eine Richtung aufrufen - von Java in nativen Code - und mit primitiven Datentypen kommunizieren, bleiben die Dinge einfach. Es gibt viele gute Referenzen (siehe Ressourcen) zu JNI, daher biete ich hier kein JNI-Tutorial an. Ich skizziere lediglich meine Implementierungsschritte.

Ich beginne mit der Erstellung einer Klasse com.vladium.utils.SystemInformation, die eine native Methode deklariert, die die Anzahl der Millisekunden CPU-Zeit zurückgibt, die bisher vom aktuellen Prozess verwendet wurden:

 public static native long getProcessCPUTime (); 

Ich verwende das Javah-Tool aus dem JDK, um den folgenden C-Header für meine zukünftige native Implementierung zu erstellen:

JNIEXPORT jlong ​​JNICALL Java_com_vladium_utils_SystemInformation_getProcessCPUTime (JNIEnv * env, jclass cls) 

Auf den meisten Win32-Plattformen kann diese Methode mithilfe des GetProcessTimes()Systemaufrufs implementiert werden und besteht buchstäblich aus drei Zeilen C-Code:

JNIEXPORT jlong ​​JNICALL Java_com_vladium_utils_SystemInformation_getProcessCPUTime (JNIEnv * env, jclass cls) {FILETIME Erstellungszeit, exitTime, kernelTime, userTime; GetProcessTimes (s_currentProcess, & createdTime, & exitTime, & kernelTime, & userTime); return (jlong) ((fileTimeToInt64 (& kernelTime) + fileTimeToInt64 (& userTime)) / (s_numberOfProcessors * 10000)); }}

Diese Methode addiert die CPU-Zeit, die für die Ausführung des Kernels und des Benutzercodes im Namen des aktuellen Prozesses aufgewendet wurde, normalisiert ihn durch die Anzahl der Prozessoren und konvertiert das Ergebnis in Millisekunden. Dies fileTimeToInt64()ist eine Hilfsfunktion, die die FILETIMEStruktur in eine 64-Bit-Ganzzahl konvertiert s_currentProcessund s_numberOfProcessorsglobale Variablen sind, die bequem in einer JNI-Methode initialisiert werden können, die einmal aufgerufen wird, wenn die JVM die native Bibliothek lädt:

statischer HANDLE s_currentProcess; static int s_numberOfProcessors; JNIEXPORT jint JNICALL JNI_OnLoad (JavaVM * vm, void * reserviert) {SYSTEM_INFO systemInfo; s_currentProcess = GetCurrentProcess (); GetSystemInfo (& systemInfo); s_numberOfProcessors = systemInfo.dwNumberOfProcessors; return JNI_VERSION_1_2; }}

Beachten Sie, dass Sie bei der Implementierung getProcessCPUTime()auf einer Unix-Plattform wahrscheinlich den getrusageSystemaufruf als Ausgangspunkt verwenden würden.

Zurück zu Java: Das Laden der nativen Bibliothek ( silib.dllunter Win32) erfolgt am besten über den statischen Initialisierer in der SystemInformationKlasse:

private static final String SILIB = "silib"; statisch {try {System.loadLibrary (SILIB); } catch (UnsatisfiedLinkError e) {System.out.println ("native lib '" + SILIB + "' nicht in 'java.library.path' gefunden:" + System.getProperty ("java.library.path")); wirf e; // erneut werfen}}

Beachten Sie, dass getProcessCPUTime()die seit der Erstellung des JVM-Prozesses verwendete CPU-Zeit zurückgegeben wird. Diese Daten sind für sich genommen nicht besonders nützlich für die Profilerstellung. Ich benötige mehr Java-Dienstprogrammmethoden, um Datenschnappschüsse zu verschiedenen Zeiten aufzuzeichnen und die CPU-Auslastung zwischen zwei beliebigen Zeitpunkten zu melden:

öffentliche statische Endklasse CPUUsageSnapshot {private CPUUsageSnapshot (lange Zeit, lange CPUTime) {m_time = Zeit; m_CPUTime = CPUTime; } public final long m_time, m_CPUTime; } // Ende der öffentlichen statischen CPUUsageSnapshot der verschachtelten Klasse makeCPUUsageSnapshot () {neuen CPUUsageSnapshot zurückgeben (System.currentTimeMillis (), getProcessCPUTime ()); } public static double getProcessCPUUsage (CPUUsageSnapshot start, CPUUsageSnapshot end) {return ((double) (end.m_CPUTime - start.m_CPUTime)) / (end.m_time - start.m_time); }}

Die "CPU Monitor API" ist fast einsatzbereit! Als letzten Schliff erstelle ich eine Singleton-Thread-Klasse, CPUUsageThreaddie automatisch in regelmäßigen Abständen (standardmäßig 0,5 Sekunden) Daten-Snapshots erstellt und diese an eine Reihe von Listenern für CPU-Nutzungsereignisse meldet (das bekannte Observer-Muster). Die CPUmonKlasse ist ein Demo-Listener, der einfach die CPU-Auslastung druckt an System.out:

public static void main (String [] args) löst eine Ausnahme aus {if (args.length == 0) löst eine neue IllegalArgumentException aus ("usage: CPUmon"); CPUUsageThread monitor = CPUUsageThread.getCPUThreadUsageThread (); CPUmon _this = new CPUmon (); Klasse app = Class.forName (args [0]); Methode appmain = app.getMethod ("main", neue Klasse [] {String []. Class}); String [] appargs = new String [args.length - 1]; Systemarraycopy (args, 1, appargs, 0, appargs.length); monitor.addUsageEventListener (_this); monitor.start (); appmain.invoke (null, neues Objekt [] {appargs}); }}

Außerdem CPUmon.main()"umschließt" eine andere Java-Hauptklasse mit dem alleinigen Zweck, CPUUsageThreadvor dem Starten der ursprünglichen Anwendung zu starten.

Als Demonstration habe ich CPUmondie SwingSet2 Swing-Demo aus JDK 1.3.1 ausgeführt (vergessen Sie nicht, sie an silib.dlleinem Speicherort zu installieren , der entweder von der PATHUmgebungsvariablen des Betriebssystems oder der java.library.pathJava-Eigenschaft abgedeckt wird ):

> java -Djava.library.path =. -cp silib.jar; (mein JDK-Installationsverzeichnis) \ demo \ jfc \ SwingSet2 \ SwingSet2.jar CPUmon SwingSet2 [PID: 339] CPU-Auslastung: 46,8% [PID: 339] CPU-Auslastung: 51,4% [PID: 339] CPU Auslastung: 54,8% (während des Ladens verwendet die Demo fast 100% einer der beiden CPUs auf meinem Computer) ... [PID: 339] CPU-Auslastung: 46,8% [PID: 339] CPU-Auslastung: 0% [PID: 339] CPU-Auslastung: 0% (die Demo hat das Laden aller Panels beendet und ist größtenteils im Leerlauf) ... [PID: 339] CPU-Auslastung: 100% [PID: 339] CPU-Auslastung: 98,4% [PID: 339] CPU Auslastung: 97% (Ich habe zum ColorChooserDemo-Bedienfeld gewechselt, in dem eine CPU-intensive Animation ausgeführt wurde, in der beide CPUs verwendet wurden.) ... [PID: 339] CPU-Auslastung: 81,4% [PID: 339] CPU-Auslastung: 50% [PID : 339] CPU-Auslastung: 50% (Ich habe den Windows NT-Task-Manager verwendet, um die CPU-Affinität für den "Java" -Prozess anzupassen und eine einzelne CPU zu verwenden.) ...

Natürlich kann ich über den Task-Manager dieselben Nutzungsnummern anzeigen, aber der Punkt hier ist, dass ich jetzt eine programmatische Möglichkeit habe, dieselben Daten aufzuzeichnen. Es ist praktisch für lang laufende Tests und die Diagnose von Serveranwendungen. Die vollständige Bibliothek (verfügbar unter Ressourcen) fügt einige andere nützliche native Methoden hinzu, darunter eine zum Abrufen der Prozess-PID (zur Integration mit externen Tools).

Vladimir Roubtsov programmiert seit mehr als 12 Jahren in verschiedenen Sprachen, einschließlich Java seit 1995. Derzeit entwickelt er Unternehmenssoftware als leitender Entwickler für Trilogy in Austin, Texas. Beim Codieren zum Spaß entwickelt Vladimir Softwaretools, die auf Java-Bytecode oder Quellcode-Instrumentierung basieren.

Erfahren Sie mehr über dieses Thema

  • Laden Sie die komplette Bibliothek herunter, die diesem Artikel beiliegt

    //images.techhive.com/downloads/idge/imported/article/jvw/2002/11/01-qa-1108-cpu.zip

  • JNI-Spezifikation und Tutorials

    //java.sun.com/j2se/1.4/docs/guide/jni/index.html

  • Einen guten Überblick über JNI finden Sie in Stuart Dabbs Halloways Komponentenentwicklung für die Java-Plattform (Addison-Wesley, Dezember 2001; ISBN0201753065).

    //www.amazon.com/exec/obidos/ASIN/0201753065/javaworld

  • In "Java-Tipp 92Verwenden Sie die JVM-Profiler-Schnittstelle für genaues Timing" untersucht Jesper Gortz eine alternative Richtung für die Profilerstellung der CPU-Auslastung. (Die Verwendung von JVMPI erfordert jedoch mehr Arbeit zur Berechnung der CPU-Auslastung für den gesamten Prozess als die Lösung dieses Artikels.)

    //www.javaworld.com/javaworld/javatips/jw-javatip92.html

  • Auf der Java Q & A- Indexseite finden Sie den vollständigen Q & A-Katalog

    //www.javaworld.com/columns/jw-qna-index.shtml

  • Seit mehr als 100 interessante Java - Tipps finden Sie Javaworld‘ s Java Tipps Indexseite

    //www.javaworld.com/columns/jw-tips-index.shtml

  • Durchsuchen Sie den Java- Kernabschnitt des aktuellen JavaWorld- Index

    //www.javaworld.com/channel_content/jw-core-index.shtml

  • Höhere Effizienz Ihrer Fragen beantwortet in unserem Java Anfänger Diskussion

    //forums.devworld.com/[email protected]@.ee6b804

  • Melden Sie sich für den kostenlosen wöchentlichen E-Mail-Newsletter von JavaWorld an

    //www.javaworld.com/subscribe

  • Unter .net finden Sie eine Fülle von IT-Artikeln aus unseren Schwesterpublikationen

Diese Geschichte "Profilerstellung der CPU-Auslastung in einer Java-Anwendung" wurde ursprünglich von JavaWorld veröffentlicht.