Als ich mir das Glossar der Speicherverwaltungsbegriffe ansah, entdeckte ich versehentlich die Definition von „Schwein in der Python (Hinweis: Es ist ein bisschen wie die gierige und unzureichende Schlange, die den Elefanten verschlingt)“ auf Chinesisch, also bin ich darauf gestoßen mit diesem Artikel. Oberflächlich betrachtet bezieht sich dieser Begriff darauf, dass der GC ständig große Objekte von einer Generation zur nächsten fördert. Dies ist so, als würde eine Python ihre Beute im Ganzen verschlucken, so dass sie sich während der Verdauung nicht bewegen kann.
In den nächsten 24 Stunden war mein Geist voller Bilder dieser erstickenden Python, die ich nicht loswerden konnte. Wie Psychiater sagen, besteht der beste Weg, Angst zu lindern, darin, sie auszusprechen. Daher dieser Artikel. Aber die nächste Geschichte, über die wir sprechen wollen, ist nicht Python, sondern GC-Tuning. Ich schwöre bei Gott.
Jeder weiß, dass GC-Pausen leicht zu Leistungsengpässen führen können. Moderne JVMs werden bei ihrer Veröffentlichung mit fortschrittlichen Garbage Collectors ausgeliefert, aber meiner Erfahrung nach ist es äußerst schwierig, die optimale Konfiguration für eine bestimmte Anwendung zu finden. Bei der manuellen Abstimmung gibt es vielleicht noch einen Hoffnungsschimmer, aber Sie müssen die genaue Mechanik des GC-Algorithmus verstehen. In diesem Zusammenhang wird Ihnen dieser Artikel hilfreich sein. Im Folgenden erläutere ich anhand eines Beispiels, wie sich eine kleine Änderung in der JVM-Konfiguration auf den Durchsatz Ihrer Anwendung auswirkt.
Beispiel
Die Anwendung, die wir verwenden, um die Auswirkung von GC auf den Durchsatz zu demonstrieren, ist nur ein einfaches Programm. Es enthält zwei Threads:
PigEater – Es wird den Vorgang nachahmen, bei dem eine Riesenpython ein großes, fettes Schwein frisst. Der Code tut dies, indem er 32 MB Bytes zu java.util.List hinzufügt und nach jeder Aufnahme 100 ms lang in den Ruhezustand versetzt.
PigDigester – Es simuliert den Prozess der asynchronen Verdauung. Der Code, der die Verdauung implementiert, setzt einfach die Liste der Schweine auf leer. Da dies ein ermüdender Prozess ist, schläft dieser Thread jedes Mal für 2000 ms, nachdem die Referenz gelöscht wurde.
Beide Threads laufen in einer While-Schleife und fressen und verdauen, bis die Schlange voll ist. Dafür müssten etwa 5.000 Schweine verzehrt werden.
package eu.plumbr.demo; public class PigInThePython { static volatile List pigs = new ArrayList(); static volatile int pigsEaten = 0; static final int ENOUGH_PIGS = 5000; public static void main(String[] args) throws InterruptedException { new PigEater().start(); new PigDigester().start(); } static class PigEater extends Thread { @Override public void run() { while (true) { pigs.add(new byte[32 * 1024 * 1024]); //32MB per pig if (pigsEaten > ENOUGH_PIGS) return; takeANap(100); } } } static class PigDigester extends Thread { @Override public void run() { long start = System.currentTimeMillis(); while (true) { takeANap(2000); pigsEaten+=pigs.size(); pigs = new ArrayList(); if (pigsEaten > ENOUGH_PIGS) { System.out.format("Digested %d pigs in %d ms.%n",pigsEaten, System.currentTimeMillis()-start); return; } } } } static void takeANap(int ms) { try { Thread.sleep(ms); } catch (Exception e) { e.printStackTrace(); } } }
Jetzt definieren wir den Durchsatz dieses Systems als „die Anzahl der Schweine, die pro Sekunde verdaut werden können“. Wenn man bedenkt, dass alle 100 ms ein Schwein in diese Python gestopft wird, können wir sehen, dass der theoretische maximale Durchsatz dieses Systems 10 Schweine/Sekunde erreichen kann.
GC-Konfigurationsbeispiel
Werfen wir einen Blick auf die Leistung bei der Verwendung zweier verschiedener Konfigurationssysteme. Unabhängig von der Konfiguration läuft die Anwendung auf einem Dual-Core-Mac (OS X10.9.3) mit 8 GB RAM.
Die erste Konfiguration:
1,4G Heap (-Xms4g -Xmx4g)
2. Verwenden Sie CMS, um die alte Generation zu bereinigen (-XX:+UseConcMarkSweepGC) und verwenden Sie den Parallelkollektor um die neue Generation zu bereinigen (-XX:+UseParNewGC)
3. Weisen Sie der neuen Generation 12,5 % des Heaps (-Xmn512m) zu und begrenzen Sie die Größe des Eden-Bereichs und des Survivor-Bereichs auf die gleiche Größe.
Die zweite Konfiguration ist etwas anders:
1,2G Heap (-Xms2g -Xms2g)
2 Sowohl die neue Generation als auch die alte Generation verwenden Parellel GC (-XX:+UseParallelGC)
3. Weisen Sie der neuen Generation 75 % des Heaps zu (-Xmn 1536 m)
4. Jetzt ist es an der Zeit, eine Wette abzuschließen, welche Konfiguration die bessere Leistung erbringt (d. h. wie viele Schweine pro Sekunde gefressen werden können). Erinnern)? Wer seine Chips auf die Erstkonfiguration setzt, wird enttäuscht sein. Die Ergebnisse sind genau das Gegenteil:
1. Die erste Konfiguration (großer Heap, große alte Generation, CMS GC) kann 8,2 Schweine pro Sekunde fressen
2. Die zweite Konfiguration (kleiner Heap, großer neuer Generation, Parellel GC) kann 9,2 Schweine pro Sekunde fressen
Nun schauen wir uns dieses Ergebnis objektiv an. Die zugewiesenen Ressourcen sind doppelt so hoch, aber der Durchsatz wird um 12 % erhöht. Dies widerspricht dem gesunden Menschenverstand, daher ist es notwendig, die Vorgänge weiter zu analysieren.
GC-Ergebnisse analysieren
Der Grund ist eigentlich nicht kompliziert. Sie müssen sich nur genau ansehen, was der GC tut, wenn Sie den Test durchführen, um die Antwort zu finden. Hier wählen Sie das Tool aus, das Sie verwenden möchten. Mit Hilfe von jstat habe ich das Geheimnis dahinter entdeckt. Der Befehl lautet wahrscheinlich wie folgt:
jstat -gc -t -h20 PID 1s
Durch die Analyse der Daten habe ich festgestellt, dass Konfiguration 1 1129 GC-Zyklen (YGCT_FGCT) durchlaufen hat und insgesamt ausgegeben hat von 63,723 Sekunden:
Timestamp S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT 594.0 174720.0 174720.0 163844.1 0.0 174848.0 131074.1 3670016.0 2621693.5 21248.0 2580.9 1006 63.182 116 0.236 63.419 595.0 174720.0 174720.0 163842.1 0.0 174848.0 65538.0 3670016.0 3047677.9 21248.0 2580.9 1008 63.310 117 0.236 63.546 596.1 174720.0 174720.0 98308.0 163842.1 174848.0 163844.2 3670016.0 491772.9 21248.0 2580.9 1010 63.354 118 0.240 63.595 597.0 174720.0 174720.0 0.0 163840.1 174848.0 131074.1 3670016.0 688380.1 21248.0 2580.9 1011 63.482 118 0.240 63.723
Die zweite Konfiguration pausierte insgesamt 168 Mal (YGCT+FGCT) und dauerte nur 11,409 Sekunden.
Timestamp S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT 539.3 164352.0 164352.0 0.0 0.0 1211904.0 98306.0 524288.0 164352.2 21504.0 2579.2 27 2.969 141 8.441 11.409 540.3 164352.0 164352.0 0.0 0.0 1211904.0 425986.2 524288.0 164352.2 21504.0 2579.2 27 2.969 141 8.441 11.409 541.4 164352.0 164352.0 0.0 0.0 1211904.0 720900.4 524288.0 164352.2 21504.0 2579.2 27 2.969 141 8.441 11.409 542.3 164352.0 164352.0 0.0 0.0 1211904.0 1015812.6 524288.0 164352.2 21504.0 2579.2 27 2.969 141 8.441 11.409
Wenn man bedenkt, dass die Arbeitsbelastung in beiden Fällen gleich ist, kann der GC in diesem Schweinefress-Experiment, wenn er keine langlebigen Objekte findet, das Müllobjekt schneller bereinigen. Bei der ersten Konfiguration beträgt die Häufigkeit des GC-Betriebs etwa das 6- bis 7-fache und die Gesamtpausenzeit das 5- bis 6-fache.
Das Erzählen dieser Geschichte hat zwei Zwecke. Das Erste und Wichtigste ist, dass ich diese zuckende Pythonschlange aus meinem Kopf verbannen möchte. Ein weiterer offensichtlicherer Vorteil besteht darin, dass das GC-Tuning eine sehr geschickte Erfahrung ist und ein gründliches Verständnis der zugrunde liegenden Konzepte erfordert. Obwohl es sich bei der in diesem Artikel verwendeten Anwendung nur um eine sehr häufige Anwendung handelt, haben die unterschiedlichen Ergebnisse der Auswahl auch große Auswirkungen auf Ihre Durchsatz- und Kapazitätsplanung. In realen Anwendungen wird der Unterschied hier noch größer sein. Es liegt also an Ihnen, ob Sie diese Konzepte beherrschen oder sich einfach auf Ihre tägliche Arbeit konzentrieren und Plumbr die für Ihre Anforderungen am besten geeignete GC-Konfiguration ermitteln lassen.
Weitere Artikel zum Testen der Auswirkungen von Garbage Collector GC auf den Durchsatz in Java finden Sie auf der chinesischen PHP-Website!