Optimierung der Leistung von Java-Anwendungen: Best Practices und Tools

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einleitung

Die Optimierung der Leistung von Java-Anwendungen ist keine einmalige Aufgabe, sondern ein kontinuierlicher Prozess, der sowohl ein Verständnis der Java Virtual Machine (JVM) als auch der Besonderheiten des Codes selbst erfordert. In der heutigen Welt, in der Reaktionsgeschwindigkeit und Durchsatz die Benutzererfahrung und Geschäftskennzahlen direkt beeinflussen, wird die Fähigkeit, Engpässe zu identifizieren und zu beseitigen, zu einer kritischen Fähigkeit. In diesem Artikel betrachten wir die wichtigsten Optimierungsansätze: von der Konfiguration der Garbage Collection und der Speicherverwaltung bis hin zur effizienten Nutzung von Multithreading und Profiling. Jeder Abschnitt wird mit praktischen Beispielen in Java untermauert.



1. Profiling und Identifizierung von Engpässen

Bevor man etwas optimiert, muss man genau bestimmen, wo Zeit oder Speicher verbraucht werden. Blindes Optimieren kann nicht nur ergebnislos bleiben, sondern die Leistung sogar verschlechtern. Hierfür gibt es Profiler.



1.1. Profiling-Werkzeuge

Die beliebtesten Werkzeuge für Java:

  • VisualVM — ein kostenloses, in das JDK integriertes Tool. Ermöglicht die Überwachung von CPU, Speicher, Threads und die Erstellung von Heap-Dumps.
  • JProfiler und YourKit — kommerzielle Lösungen mit erweiterten Analysefunktionen.
  • Async Profiler — ein leichtgewichtiger Profiler für CPU und Speicher, ideal für den Produktionseinsatz.


1.2. Beispiel: Suche nach "heißen" Methoden

Angenommen, wir haben eine Methode, die häufig aufgerufen wird und langsam läuft. Mit einem Profiler sehen wir, dass 70% der Zeit für String-Konkatenation in einer Schleife aufgewendet wird:

public class StringConcatenation {    public static String buildString(int n) {        String result = "";        for (int i = 0; i < n; i++) {            // Schlecht: Es wird jedes Mal ein neues String-Objekt erstellt            result += "item" + i + ",";        }        return result;    }

public static void main(String[] args) { long start = System.nanoTime(); String s = buildString(100_000); long end = System.nanoTime(); System.out.println("Time: " + (end - start) / 1_000_000 + " ms"); }}

Nach dem Profiling verstehen wir, dass die Konkatenation durch StringBuilder ersetzt werden muss:

public static String buildStringOptimized(int n) {    StringBuilder sb = new StringBuilder(n * 10); // Vorabzuweisung    for (int i = 0; i < n; i++) {        sb.append("item").append(i).append(",");    }    return sb.toString();}

Dies ist ein sehr einfaches Beispiel, aber es zeigt anschaulich die Bedeutung des Profilings: Ohne es wüssten wir nicht, was genau korrigiert werden muss.



2. Konfiguration der JVM und der Garbage Collection

Die JVM ist die Ausführungsumgebung, und ihre Konfiguration kann einen enormen Leistungsschub bringen. Die wichtigsten Parameter betreffen den Speicher und die Garbage Collection (GC).



2.1. Grundlegende Speicherparameter

  • -Xms — Anfangsgröße des Heaps.
  • -Xmx — Maximale Größe des Heaps.
  • -Xss — Stack-Größe für jeden Thread.

Es wird empfohlen, -Xms und -Xmx gleich zu setzen, um den Overhead durch die Heap-Erweiterung während der Laufzeit zu vermeiden.



2.2. Auswahl der Garbage Collection

Moderne Java-Versionen bieten mehrere GCs:

  • G1GC (Garbage First) — Standard seit Java 9. Gute Balance zwischen Durchsatz und Latenz.
  • ZGC — Für Anwendungen mit sehr niedrigen Latenzen (Pausen unter 10 ms).
  • Shenandoah — Ähnlich wie ZGC, aber mit einem anderen Algorithmus.
  • Parallel GC — Für Anwendungen, bei denen maximaler Durchsatz wichtiger ist als Reaktionszeit.

Beispiel für den Start mit G1GC und Logging der Arbeit

Blogs

Buchempfehlungen