Einleitung: Warum Java-Code profilieren?
\n\nJava-Entwicklung bedeutet nicht nur, korrekten Code zu schreiben, sondern auch schnelle, skalierbare Anwendungen zu erstellen. Selbst ein perfekt entworfenes System kann aufgrund einer einzigen nicht optimalen Zeile langsamer werden. Wie findet man diesen „Flaschenhals“? Die Antwort: Profilierung.
\n\nProfilierung ist der Prozess der Datenerfassung über die Arbeitsweise einer Anwendung: Ausführungszeit von Methoden, Speichernutzung, Threads und CPU-Auslastung. Ohne sie wird die Optimierung zum Ratespiel. In diesem Artikel werden wir praktische Beispiele für die Profilierung von Java-Code durchgehen, lernen, Ergebnisse zu interpretieren und Engpässe zu finden.
\n\nWir betrachten zwei beliebte Tools: VisualVM (kostenlos, im JDK integriert) und JProfiler (kommerziell, aber leistungsstark). Alle Beispiele werden mit echtem Code sein.
\n\nHauptteil: Praxis der Profilierung
\n\n1. Einrichtung der Umgebung und Start des Profilers
\n\nStellen Sie zunächst sicher, dass Sie JDK (8 oder höher) installiert haben. VisualVM wird mit JDK 8 und 9 mitgeliefert, für neuere Versionen muss es separat von der offiziellen Website heruntergeladen werden.
\n\nSchreiben wir einen einfachen „langsamen“ Code zum Testen. Erstellen Sie die Klasse SlowApp.java:
import java.util.ArrayList;\nimport java.util.List;\nimport java.util.Random;\n\npublic class SlowApp {\n public static void main(String[] args) throws InterruptedException {\n List<String> data = new ArrayList<>();\n Random random = new Random();\n\n // Simulation eines langen Datenladens\n for (int i = 0; i < 100_000; i++) {\n data.add(generateString(random));\n }\n\n // Datenverarbeitung\n long sum = 0;\n for (String s : data) {\n sum += processString(s);\n }\n\n System.out.println("Result: " + sum);\n Thread.sleep(10000); // Um Zeit zum Verbinden mit dem Profiler zu haben\n }\n\n private static String generateString(Random random) {\n StringBuilder sb = new StringBuilder(100);\n for (int i = 0; i < 100; i++) {\n sb.append((char) ('a' + random.nextInt(26)));\n }\n return sb.toString();\n }\n\n private static long processString(String s) {\n long result = 0;\n for (char c : s.toCharArray()) {\n result += c;\n // Künstliche Verzögerung\n for (int j = 0; j < 100; j++) {\n result += Math.sqrt(j);\n }\n }\n return result;\n }\n}\n\nStarten Sie die Anwendung:
\njavac SlowApp.java\njava SlowApp\n\nWährend das Programm läuft (es schläft für 10 Sekunden), öffnen Sie VisualVM. Suchen Sie im linken Bereich Ihren Prozess (SlowApp) und doppelklicken Sie darauf. Gehen Sie zum Tab Sampler oder Profiler – Sie sehen Statistiken zu CPU und Speicher.
\n\n2. CPU-Analyse: Finden der schwersten Methode
\n\nKlicken Sie in VisualVM im Bereich Sampler auf die Schaltfläche CPU. Nach einigen Sekunden erhalten Sie eine Tabelle mit Methoden, sortiert nach Ausführungszeit (Self Time).
\n\nIn unserem Beispiel werden Sie sehen, dass die Methode processString mehr als 80% der Zeit belegt. Darin befindet sich die verschachtelte Schleife Math.sqrt(j). Dies ist der Engpass.
Wie interpretiert man das?
\n- \n
- Self Time – Zeit, die direkt für die Ausführung der Methode aufgewendet wurde (ohne Berücksichtigung von Aufrufen untergeordneter Methoden). \n
- Total Time – Gesamtzeit, einschließlich aller verschachtelten Aufrufe. \n
- Invocations – Anzahl der Aufrufe der Methode. \n
Beispielausgabe (vereinfacht):
\nMethod Self Time (ms) Invocations\nSlowApp.processString 8500 100000\nSlowApp.generateString 1200 100000\njava.lang.Math.sqrt 6000