4 Möglichkeiten zur Reduzierung der Latenz in Ihren Java-Anwendungen
Unter Latenz versteht man die Verarbeitungsverzögerung beim Ausführen Ihrer Anwendungen. Bei Ihren Java- Anwendungen kann dieser Mehraufwand normalerweise auf die Optimierung Ihrer Java Virtual Machine ( JVM ) zurückgeführt werden.
Das Problem der hohen Latenz besteht normalerweise darin, dass es nicht deterministische Fälle langer Verzögerungen gibt, selbst wenn die allgemeine Leistung gut ist. Bei Anwendungen mit geringer Latenz, wie z. B. Finanzhandelssystemen und Spieleanwendungen, müssen Sie sicherstellen, dass es keine langen Pausen gibt.
Als Java-Entwickler ist es wichtig, dass Sie wissen, wie Sie dies erreichen. In diesem Tutorial erfahren Sie, wie Sie Ihre JVM für eine niedrige Latenz optimieren können:
Bestimmen der Latenz für Ihre Anwendungen
Es gibt verschiedene Metriken , mit denen Sie als Entwickler den Overhead Ihrer Anwendungen messen können. Dazu gehört die Messung von Dingen wie der Anzahl der pro Minute durchgeführten Transaktionen oder der Zeit zur Ausführung eines bestimmten Szenarios.
Sie können eine Reihe von Benchmark-Tests verwenden, um sie mit allen Szenarien zu vergleichen, die den festgelegten Schwellenwert überschreiten. Ihr Interesse sollte an Transaktionen liegen, die den festgelegten Schwellenwert überschreiten. Es ist wichtig, dass Sie diese Tests mindestens 20 Minuten lang durchführen, um ein genaues Bild der Messung zu erhalten.
Um Ihren Java-Code zu vergleichen, können Sie ein Tool wie Java Microbenchmark Harness (JMH) verwenden. Sie können dieses Tool „zum Erstellen, Ausführen und Analysieren von Nano-/Mikro-/Milli-/Makro-Benchmarks verwenden, die in Java und anderen Sprachen geschrieben sind, die auf die JVM abzielen “.
Mit JMH können Sie Benchmarks für Leistungsmetriken wie durchschnittliche Zeit, Durchsatz und Zeit für jeden Vorgang abrufen . Wenn Sie über JDK 12 und höher verfügen, sollte es bereits in Ihrem Toolkit enthalten sein.
Reduzieren Sie die Latenz in Ihren Java-Anwendungen – Heap-Größe
Die Heap-Größe ist der Bereich im Speicher, in dem Java-Objekte zugewiesen werden. Eine höhere Heap-Größe führt direkt zu kürzeren Latenzen. Dies liegt daran, dass Ihre Anwendung über kürzere Garbage Collections verfügt, da sie über eine beträchtliche Zuweisung verfügt, um mehr Objekte einzubeziehen, ohne die vorhandenen regelmäßig zu entfernen.
Mit Java können Sie eine benutzerdefinierte Heap-Größe für Ihre JVM festlegen . Bevor Sie dies tun, müssen Sie Benchmark-Tests durchführen, um den maximalen Heap-Speicher abzuschätzen, den Ihre Anwendung benötigt.
Nachdem Sie diese Tests durchgeführt haben, können Sie nun den minimalen und maximalen Heap mit den Argumenten -Xms bzw. -Xmx festlegen.
java -Xms1g -Xmx2g JavaClassName # setzt den minimalen Heap auf 1 GB und den maximalen auf 2 GB
Sie müssen jedoch überwachen , wie Ihre Anwendung den Heap verwendet. Es ist möglich, dass ein Szenario auftritt, in dem Ihr Programm nicht mehr über den Heap-Speicher verfügt, was zu einer java.lang führt. OutOfMemoryError-Ausnahme. Dieser Fehler tritt auf, wenn die JVM versucht, neue Objekte zum Heap hinzuzufügen, ihr Speicher jedoch nicht ausreicht.
Um einem solchen Szenario entgegenzuwirken, können Sie das Heap- Speicherdiagramm (Ressourcen > Speicher-Heap) in Ihrem FusionReactor-Dashboard verwenden, um die Nutzung Ihres Heap-Speichers zu überwachen .
Wenn Sie anhand Ihres Heap-Speicherdiagramms feststellen, dass Ihr genutzter Speicher (MB) mit der Zeit zunimmt, besteht Grund zur Sorge.
Dies kann auf einen Speicherverlust hinweisen . Daher sollte Ihr nächster Schritt darin bestehen, Ihren Code zu debuggen .
Beim Debuggen Ihres Codes sollten Sie auf Finalizer-Methoden, sehr große Arrays und die übermäßige Verwendung statischer Felder achten. Klassen mit solchen Objekten werden bei der Garbage Collection nicht aus dem Speicher freigegeben und können daher zu einem Speicherverlust führen. Falls der Fehler „java.lang.OutOfMemoryError: Java heap space“ bereits aufgetreten ist, können Sie anhand der Stack-Trace-Informationen von FusionReactor ermitteln, welche Methode die Ausnahme verursacht hat.
Reduzieren Sie die Latenz in Ihren Java-Anwendungen – Kindergartengröße
Der Heap ist manchmal in zwei Abschnitte unterteilt: die junge Sammlung (neue Generation) und die alte Sammlung (alte Generation). Die neue Generation ist in den Eden- und den Überlebensraum unterteilt.
Das Eden dient der Aufbewahrung neu geschaffener Objekte. Der Survivor-Raum ist Teil des Eden-Raums. Hier werden Eden-Objekte während der Abholung übertragen. Von hier aus (dem Überlebenden) können sie entweder gesammelt oder zur alten Generation befördert werden.
Objekte, die lange in der neuen Generation verblieben sind, werden in die alte Sammlung übernommen (Tenured Generation).
Die Hauptmotivation für eine neue Generation besteht darin, dass sie Speicher schneller freigibt als die alte oder Garbage Collection ohne Kindergarten. Die alte Generation ist größer, füllt sich langsam und benötigt für jede Speicherbereinigung viel Zeit.
Es ist wichtig, dass Sie verstehen, dass eine große Nursery-Größe nicht automatisch zu kürzeren Latenzen führt. Aufgrund einer großen Nursery-Umgebung kann es bei Ihrer Anwendung zu langen Pausenzeiten für die Young-Sammlung kommen.
Daher ist es notwendig, dass Sie die Kindergartengrößen anpassen und dabei die Müllabfuhrzeiten beachten. Sie können die Größe der neuen Generation mit der Option -XX:NewSize festlegen . Die Option -XX:MaxNewSize legt ihren Maximalwert fest. Sie können das Verhältnis der jungen Generation zur alten Generation auch mit der Option -XX:NewRatio steuern .
Von Ihrem FusionReactor-Dashboard aus ( Ressourcen > Speicherplätze ) können Sie die Zuweisung Ihres Eden Space , Old Gen und Survivor Space überwachen . Bitte beachten Sie, dass die Benennung dieser Speicherplätze von der Art des von Ihnen verwendeten Garbage Collectors abhängt. Der Wortlaut ist unterschiedlich, da die Anordnung der Generationen in jedem Sammler unterschiedlich ist. Wenn Sie den G1 Garbage Collector (GC) verwenden, erhalten Sie unten die Grafik „Speicherplätze – G1 Eden Space“ . Die Generationen heißen: Eden Space , Old Gen und Survivor Space .
Wenn Sie den Serial GC verwenden, erhalten Sie unten das Diagramm „Memory Spaces – Eden Space“ . Die Generationen heißen: Eden Space , Survivor Space und Tenured Gen.
Wenn Sie den Parallel GC verwenden, erhalten Sie unten das Diagramm „Memory Spaces – PS Eden Space“ . Die Generationen heißen: PS Eden Space , PS Survivor Space und PS Old Gen.
Wenn Sie den Z Garbage Collector verwenden, werden keine Generationen angezeigt. Stattdessen sehen Sie eine Speicher-Heap-Zuordnung: Memory Spaces – ZHeap .
Wie Sie sehen, ist der Z Garbage Collector nicht wie die anderen Collectors. Es ist die neueste Ergänzung zu den GCs, die Sie verwenden können.
Der Z-Garbage Collector wurde speziell für Anwendungen mit geringer Latenz entwickelt. Je größer die Heap-Größe ist, die Sie ihm zuweisen, desto besser.
Dies bedeutet jedoch nicht, dass Sie Speicher verschwenden sollten. Sie müssen ein Gleichgewicht zwischen Speicherzuweisung und Speicherbereinigung finden. Sie können die Speicherzuteilung für den Z GC mit der zuvor besprochenen Option -Xmx festlegen.
Reduzieren Sie die Latenz in Ihren Java-Anwendungen – Garbage Collection
Wenn der Heap voll ist, muss die JVM Speicherplatz für die Zuweisung neuer Objekte freigeben. Dieser Vorgang wird als Garbage Collection bezeichnet. Der Typ des Garbage Collectors, den Sie verwenden, bestimmt, wie die Sammlung erfolgt.
Bei der Garbage Collection muss die Anwendung zunächst angehalten werden, während die JVM Speicher freigibt. Daher müssen Sie darauf achten, einen Kollektor auszuwählen, der minimale Pausenzeiten gewährleistet. Standardmäßig wählt die JVM den besten Collector für Ihre Anwendung aus.
Es kann jedoch erforderlich sein, selbst einen Müllsammler auszuwählen. Dies könnte daran liegen, dass Ihnen durch die Speicherbereinigung lange Pausenzeiten aufgefallen sind .
FusionReactor visualisiert Ihre Garbage-Collection- Häufigkeit und die Menge des freigegebenen Speichers ( Ressourcen>Garbage Collection ). Dadurch erhalten Sie aufschlussreiche Daten, mit denen Sie vergleichen können, während Sie verschiedene Garbage Collectors ausprobieren.
Es gibt vier Garbage Collectors, aus denen Sie wählen können, während Sie die GC-Zeiten über Ihr FusionReactor-Dashboard beobachten:
- Serieller Garbage Collector
- Paralleler Garbage Collector
- G1 Garbage Collector
- und Z-Garbage Collector
Hier erfahren Sie mehr über die einzelnen Garbage Collectors und die Kriterien für die Auswahl.
Reduzieren Sie die Latenz in Ihren Java-Anwendungen – Compaction
Dem Heap-Speicher können Objekte zugeordnet sein, die nicht zusammenhängend sind. Durch diese Zuweisung bleiben möglicherweise nur wenige freie Speicherbereiche übrig, was zu einer Fragmentierung des Heaps führt. Wenn diese freien Räume kleiner als der minimale lokale Threadbereich (TLA) sind, können sie nicht für die Objektzuweisung verwendet werden.
Diese kleinen freien Plätze bleiben ungenutzt, bis bei der nächsten Garbage Collection genügend Platz daneben platziert wird.
Die Verdichtung dient dazu, Objekte zusammenzuführen und einen großen Raum zu hinterlassen, der für die Objektzuordnung genutzt werden kann. Dies wird erreicht, indem Blöcke des zugewiesenen Speichers an das untere Ende des Heaps verschoben werden, sodass oben ein großer zusammenhängender freier Speicherplatz verbleibt.
Es ist wichtig zu beachten, dass die Komprimierung während der Speicherbereinigung erfolgt. Daher führt das Komprimieren großer Speicherblöcke zu langen Pausenzeiten. Eine geringe Komprimierung führt jedoch zu Fragmentierung und wirkt sich daher negativ auf die Leistung Ihrer Anwendung aus.
Im Allgemeinen leistet die JVM gute Arbeit bei der Komprimierung. Wenn Sie jedoch feststellen, dass die Leistung Ihrer Anwendung regelmäßig nachlässt, können Sie Maßnahmen ergreifen und beobachten, was passiert. Schwankende Werte für Durchsatz und Reaktionszeit für Ihre Anwendung können ein Anzeichen dafür sein. Auch große Garbage Collections mit langen Pausen dürften für Sie interessant sein.
Sie können Ihre JVM für die parallele Komprimierung optimieren , indem Sie das Flag -XX:+UseParallelOldGC aktivieren . Dadurch wird sichergestellt, dass sowohl kleinere als auch große Sammlungen parallel durchgeführt werden, wodurch der Aufwand für die Speicherbereinigung verringert wird.
Standardmäßig werden Hauptsammlungen von einem einzelnen Thread ausgeführt, während Nebensammlungen von mehreren Threads durchgeführt werden. Daher sollte die Verwendung von -XX:+UseParallelOldGC die Latenz auf einem Multiprozessorsystem reduzieren.
Abschluss
Dieses Tutorial hat Ihnen gezeigt, wie Sie die Analysen von FusionReactor zur Überwachung hoher Latenzen nutzen können und welche Maßnahmen Sie ergreifen können. Jetzt sind Sie an der Reihe! Wenn Sie neu bei FusionReactor sind, können Sie hier eine kostenlose Testversion starten oder einen Blick darauf werfen, wie Ihnen FusionReactor Ultimate sonst noch dabei helfen kann, Leistungsprobleme in Ihrer Java-Anwendung zu finden.