Heim  >  Artikel  >  Java  >  Ausführliche Erläuterung der Optimierung der Garbage Collection für geschäftskritische Java-Anwendungen (Teil 2)

Ausführliche Erläuterung der Optimierung der Garbage Collection für geschäftskritische Java-Anwendungen (Teil 2)

黄舟
黄舟Original
2017-03-23 11:03:441058Durchsuche

Optimierung der Garbage Collection für geschäftskritische Java-Anwendungen (Teil 1)

Parallel Mark Sweep (CMS) Collector

CMS Garbage Collector ist der erste weit verbreitete Collector mit geringer Latenz. Obwohl es in Java 1.4.2 verfügbar ist, ist es zu Beginn nicht sehr stabil. Diese Probleme wurden erst mit Java 5 gelöst.

Wie aus dem Namen des CMS-Collectors hervorgeht, verwendet er eine parallele Methode: Der Großteil der Recyclingarbeit wird von einem GC-Thread erledigt, der parallel zum Worker-Thread ausgeführt wird, der Benutzeranfragen verarbeitet. Der ursprüngliche Single-stop-the-world-Recyclingprozess der alten Generation ist in zwei kürzere Stop-the-World-Pausen plus 5 parallele Phasen unterteilt. Während dieser parallelen Phasen läuft der ursprüngliche Arbeitsthread wie gewohnt (ohne angehalten zu werden).

Verwenden Sie die folgenden Parameter, um den CMS-Recycler zu aktivieren:

-XX:+UseConcMarkSweepGC

Eine erneute Anwendung des oben genannten Testprogramms (und eine Erhöhung der Last) führt zu folgenden Ergebnissen:

Abbildung 4 GC-Verhalten von JVM mit optimierter Heap-Größe und Verwendung von CMS in 50 Stunden (-Xms1200m -Xmx1200m -XX:NewSize=400m -XX:MaxNewSize=400m -XX:SurvivorRatio=6 -XX: + UseConcMarkSweepGC))

Wie Sie sehen können, ist die 8-Sekunden-Pause des GC der alten Generation verschwunden. Jetzt gibt es im Erfassungsprozess der alten Generation nur noch zwei Pausen (die vorherige führte zu 5 Pausen in 50 Stunden), und alle Pausen liegen innerhalb einer Sekunde.

Standardmäßig verwendet der CMS-Collector ParNew (GC-Algorithmus), um die Sammlung neuer Generationen zu verwalten. Wenn ParNew mit einem CMS ausgeführt wird, sind die Pausen etwas länger als ohne CMS, da zwischen ihnen eine zusätzliche Koordination erforderlich ist. Im Vergleich zu den letzten Testergebnissen ist dieses Problem an der leichten Erhöhung der durchschnittlichen Pausenzeit der neuen Generation zu erkennen. In der Pausenzeit der neuen Generation treten häufig Ausreißer auf, und dieses Problem ist auch hier zu finden. Ausreißer können etwa 0,5 Sekunden erreichen. Diese Pausen sind jedoch für viele Anwendungen kurz genug, sodass die CMS/ParNew-Kombination als gute Optimierungsoption mit geringer Latenz dienen kann.

Ein schwerwiegender Fehler des CMS-Kollektors besteht darin, dass das CMS nicht starten kann, wenn der Speicherplatz der alten Generation voll ist. Sobald die alte Generation voll ist, ist es zu spät, das CMS zu starten; die VM muss die übliche „Stop-the-World“-Strategie verwenden („Concurrent-Mode-Fehler“-Datensätze werden in den GC-Protokollen angezeigt). . Um das Ziel einer geringen Latenz zu erreichen, sollte der CMS-Kollektor gestartet werden, wenn die Speicherplatznutzung der alten Generation einen bestimmten Schwellenwert erreicht, was durch die folgenden Einstellungen erreicht wird:

-XX:CMSInitiatingOccupancyFraction=80

Dies bedeutet, dass sobald die alte Wenn der Generierungsraum zu 80 % belegt ist, wird der CMS-Kollektor ausgeführt. Verwenden Sie für unsere Anwendung einfach diesen Wert (der Standardwert). Wenn der Schwellenwert jedoch zu hoch eingestellt ist, kommt es zu einem „Fehler im gleichzeitigen Modus“, was zu langfristigen GC-Pausen der alten Generation führt. Wenn es andererseits zu niedrig eingestellt ist (kleiner als die Größe des aktiven Speicherplatzes), kann CMS immer parallel laufen, was dazu führt, dass ein bestimmter CPU-Kern vollständig für GC genutzt wird. Wenn sich das Objekt -Erstellungs- und Heap-Nutzungsverhalten einer Anwendung schnell ändert, beispielsweise durch das Starten spezieller Aufgaben über interaktive Methoden oder Timer, ist es schwierig, einen geeigneten Schwellenwert festzulegen und gleichzeitig die beiden oben genannten Probleme zu vermeiden.

Schatten der Fragmentierung

Eines der größten Probleme mit CMS besteht jedoch darin, dass es den Heap-Speicher der alten Generation nicht aufräumt. Dadurch entsteht eine Heap-Fragmentierung, die im Laufe der Zeit zu einer erheblichen Beeinträchtigung des Dienstes führen kann. Zwei Faktoren können dies verursachen: knapper Platz für die alte Generation und häufiges CMS-Recycling. Der erste Faktor kann verbessert werden, indem der Heap-Speicherplatz der alten Generation vergrößert wird, der größer ist als der vom ParallelGC-Kollektor benötigte Speicherplatz (ich habe ihn von 1024 MB auf 1200 MB erhöht, wie Sie auf den ersten Bildern sehen können). Das zweite Problem kann, wie bereits erwähnt, durch eine entsprechende Aufteilung des Raums jeder Generation optimiert werden. Wir können tatsächlich sehen, wie sehr dies die Häufigkeit von GC der alten Generation reduzieren kann.

Um zu beweisen, dass es wichtig ist, die Heap-Größe jeder Generation vor der Verwendung von CMS angemessen anzupassen, schauen wir uns zunächst an, ob wir nicht den oben genannten Prinzipien folgen und den CMS-Kollektor basierend auf Abbildung 1 (fast) direkt verwenden keine Heap-Optimierung) Was passiert:

Abbildung 5 GC-Verhalten ohne optimierte Heap-Größe und Leistungsverschlechterung durch Speicherfragmentierung nach Verwendung von CMS (ab Stunde 14)

Es ist offensichtlich, dass die JVM mit dieser Einstellung unter dem Lasttest fast 14 Stunden lang stabil arbeiten kann (in der Produktionsumgebung und unter geringeren Lastbedingungen kann diese harmlose Phase der Instabilität länger dauern). Als nächstes kommt es plötzlich zu mehreren langen GC-Pausen, die fast die Hälfte der verbleibenden Zeit in Anspruch nehmen. Die Pausenzeit der alten Generation wird nicht nur mehr als 10 Sekunden erreichen, sondern auch die Pausenzeit der neuen Generation wird mehrere Sekunden erreichen. Denn der Sammler muss viel Zeit damit verbringen, den Raum der alten Generation zu durchsuchen, um die Objekte von der neuen Generation auf die alte Generation zu übertragen.

CMS低延迟优点的代价就是内存碎片。这个问题可以最小化,但是不会彻底消失。你永远不知道它什么时候会被触发。然而,通过合理的优化与监控可以控制它的风险。

G1(Garbage First)回收器的希望

G1回收器设计的目的就是保证低延迟的同时而没有堆碎片风险。因此,Oracle把它作为CMS的一个长期取代。G1可以避免碎片风险是因为它会整理堆空间。对于GC暂停来说,G1的目标并不是使暂停时间最小化,而是设置一个时间上限,使GC暂停尽量满足这一上限值。

在将G1回收器用于测试程序中并与上述其他经典回收器做对比之前,先总结两点关于G1的重要信息。

  • Oracle在Java 7u4中开始支持G1。为了使用G1你应该将Java 7更新到最新。Oracle的GC团队一直致力于G1的研发,在最新的Java更新中(本文编写时最新版本是7u7到7u9),G1的改进很显著。另一方面,G1无法在任何Java 6版本中使用,而且到目前更优越的Java 7不可能向后移植到Java 6中。

  • 前面关于调节各代空间大小的优化对G1来说已经淘汰了。设置各代空间大小与设置暂停目标时间相冲突会使G1回收器偏离原本的设计目标。使用G1时,可以使用“-Xms”和“-Xmx”设置整体的内存大小,也可以设置GC暂停目标时间(可选),对G1来说不用设置其他选项。与ParallelGC回收器的AdapativeSizingPolicy类似,它自适应地调整各代空间大小来满足暂停目标时间。

遵循这些原则后,G1回收器在默认配置下的结果如下:

图6 最小配置(-Xms1024m -Xmx1024 -XX:+UseG1GC)的JVM在G1下26小时内的GC性能

在这个例子中,我们使用了默认的GC暂停目标时间200ms。从图中可以看到,平均时间与这个目标比较吻合,最长GC暂停时间与使用CMS回收器差不多(图4)。G1明显可以很好地控制GC暂停,与平均时长相比,离群值也相当少。

另一方面,平均GC暂停时间要比CMS回收器长很多(270 vs 100ms),而且更频繁。这意味着GC累积暂停时间(也就是GC本身所占总时间)是使用CMS的4倍以上(6.96% vs 1.66%)。

与CMS一样,G1也分为GC暂停阶段和并行回收阶段(不暂停任务)。同样与CMS类似,当堆占用比达到一定门限后,它才启动并行回收阶段。从图6可以看到,1GB的可用内存到目前为止并没有完全使用。这是因为G1的默认占用比门限值要比CMS低很多。也有人指出,一般来说较小的堆空间就可以满足G1的需求。

垃圾回收器的定量比较

下面的表格总结了Oracle Java 7中4种最重要的垃圾回收器在测试中的关键性能指标。在同样的应用程序上,进行相同的负载测试,但是负载的级别不同(由第2列的垃圾创建速率体现)。

表 几种垃圾回收器的比较

所有的回收器都运行在1GB的堆空间上。传统的回收器(ParallelGC、ParNewGC和CMS)另外使用下面的堆设置:

-XX:NewSize=400m -XX:MaxNewSize=400m -XX:SurvivorRatio=6

而G1回收器没有额外的堆大小设置,并且使用默认的暂停目标时间200ms,也可以显示设置:

-XX:MaxGCPauseMillis=200

从表中可以看到,传统回收器在新生代回收上(第3列)时间差不多。对ParallelGC和ParNewGC来说是差不多的,而CMS实际上也是使用ParNewGC去回收新生代。然而,在新生代GC暂停中,将新生代存活对象移入老年代需要ParNewGC和CMS的协同。这样的协同引入额外的代价,也就导致CMS的新生代GC暂停时间要略长。

第7列是GC暂停所耗费的时间占总时间的百分比,这个值可以很好地反映GC的总时间代价。因为并行GC总时间(最后一列)以及引入的CPU占用代价可以忽略。按前文所述,优化堆大小后老年代GC次数会变得很少,这样第7列的值主要由新生代GC暂停总时间所决定。新生代暂停总时间是新生代暂停(连续)时长(第3列)与暂停次数的乘积。新生代暂停频率与新生代空间大小有关,对传统回收器来说,这个大小是相同的(400MB)。因此,对传统回收器来说,第7列的值或多或少地反映着第3列的值(负载差不多的情况)。

Die Vorteile von CMS sind aus Spalte 6 deutlich ersichtlich: Es tauscht etwas längere Gesamtzeitkosten gegen kürzere (eine Größenordnung niedrigere) GC-Pausen der alten Generation ein. Für viele reale Anwendungen ist dies ein guter Kompromiss.

Wie funktioniert der G1-Kollektor für unsere Anwendung? Wie in Spalte 6 (und Spalte 5) zu sehen ist, leistet der G1-Kollektor bei der Reduzierung der GC-Pausenzeit der alten Generation eine bessere Arbeit als der CMS-Kollektor. Doch wie man auch aus Spalte 7 sieht, zahlt es einen sehr hohen Preis: Bei gleicher Auslastung beträgt der Gesamtzeitaufwand von GC 7 %, während CMS nur 1,6 % ausmacht.

In nachfolgenden Artikeln werde ich die Bedingungen untersuchen, die dazu führen, dass G1 höhere GC-Zeitkosten verursacht, und auch die Vor- und Nachteile von G1 im Vergleich zu anderen Kollektoren (insbesondere dem CMS-Kollektor) analysieren. Das ist ein großes und wertvolles Thema.

Zusammenfassung und Ausblick

Für alle klassischen Java GC-Algorithmen (SerialGC, ParallelGC, ParNewGC und CMS) ist es jedoch in vielen praktischen Anwendungen wichtig, die Heap-Space-Größe jeder Generation zu optimieren Das Programm hat nicht genügend sinnvolle Optimierungen vorgenommen. Das Ergebnis ist eine unzureichend optimierte Anwendungsleistung und eine Verschlechterung des Betriebs (was zu Leistungseinbußen und sogar zu einer vorübergehenden Unterbrechung des Programms führt, wenn es nicht gut überwacht wird).

Durch die Optimierung der Heap-Speicherplatzgröße jeder Generation kann die Anwendungsleistung erheblich verbessert und die Anzahl langer GC-Pausen minimiert werden. Um lange GC-Pausen zu vermeiden, muss dann ein Kollektor mit geringer Latenz verwendet werden. CMS war (bislang) der bevorzugte und effiziente Collector mit geringer Latenz. In vielen Fällen reicht ein CMS aus. Bei angemessener Optimierung kann die Langzeitstabilität weiterhin gewährleistet werden, es besteht jedoch die Gefahr einer Heap-Fragmentierung.

Als Alternative ist der G1-Kollektor derzeit (Java 7u9) eine unterstützte und verfügbare Option, es gibt jedoch noch Raum für Verbesserungen. Die Ergebnisse sind für viele Anwendungen akzeptabel, können aber nicht ganz mit dem CMS-Kollektor verglichen werden. Die Einzelheiten seiner Vor- und Nachteile verdienen eine sorgfältige Untersuchung

Das obige ist der detaillierte Inhalt vonAusführliche Erläuterung der Optimierung der Garbage Collection für geschäftskritische Java-Anwendungen (Teil 2). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn